您的位置:首页 > 理论基础 > 计算机网络

Volley源码解析

2016-01-22 16:36 405 查看
Volley支持网络图片、文本获取,内部会自动在内存,文件中缓存图片。

Volley还可以使用第三方的网络请求类库,只需在创建请求队列时实现相应接口即可。

Volley基本用法:

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);

// 创建 RequestQueue
RequestQueue mQueue = Volley.newRequestQueue(this);

//构造请求,并加入到RequestQueue中。只需加入,即可自动执行网络请求
mQueue.add(new StringRequest("http://www.baidu.com", new Listener<String>() {
@Override
public void onResponse(String response) {
String txt = new String(response.getBytes());
((TextView) findViewById(R.id.tv)).setText(txt);
System.out.println(txt);

}
}, new ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
}
}));

//一个请求队列可加入多个请求,默认会选择4个线程中的一个去执行网络请求
mQueue.add(
new ImageRequest("http://imgstatic.baidu.com/img/image/shouye/fanbingbing.jpg", new Listener<Bitmap>() {
@Override
public void onResponse(Bitmap response) {
((ImageView) findViewById(R.id.iv1)).setImageBitmap(response);
}
}, 0, 0, Config.RGB_565, new ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {

}
}));

//第2种图片加载方式(推荐使用)
ImageView imageView = (ImageView) findViewById(R.id.iv2);
ImageListener listener = ImageLoader.getImageListener(imageView, android.R.drawable.ic_menu_rotate,
android.R.drawable.ic_delete);

ImageLoader mImageLoader = new ImageLoader(mQueue, new BitmapCache());
mImageLoader.get("http://imgstatic.baidu.com/img/image/shouye/fanbingbing.jpg", listener);

}




源码分析

使用Volley的第一步是创建请求队列,即:

RequestQueue mQueue = Volley.newRequestQueue(this);


Volley.newRequestQueue(getApplicationContext(), new HttpStack() {

@Override
public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
throws IOException, AuthFailureError {
/*
* 这里可以使用第三方的网络请求类库,网络请求所需的参数都已经封装在了request中,
* additionalHeaders中是部分请求头信息。
*
* 执行完网络请求后只需构造一个org.apache.http.HttpResponse,
* 并把返回的数据(相应头,状态码,body等)封装在里面并返回即可
*/

return null;
}
});


第二种方法中使用了自定义的网络请求类库,当该mQueue中add请求时,就会使用自定义的网络请求类库去执行网络请求。接下来看一下请求队列的创建过程 
public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);

String userAgent = "volley/0";
try {
String packageName = context.getPackageName();
PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
userAgent = packageName + "/" + info.versionCode;
} catch (NameNotFoundException e) {
}

if (stack == null) {
if (Build.VERSION.SDK_INT >= 9) {
stack = new HurlStack();
} else {
// Prior to Gingerbread, HttpUrlConnection was unreliable.
// See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
}
}

Network network = new BasicNetwork(stack);

RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
queue.start();

return queue;
}
可以看到当stack不是null时就使用自定义的网络请求类库,否则就使用默认的,当Build.VERSION.SDK_INT >= 9时就使用Volley中的HurlStack,打开HurlStack可以看到用的是HttpURLConnection ,当<9时用的是Volley中的HttpClientStack,打开HttpClientStack可以看到用的是HttpUriRequest



HurlStack中重写的public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)throws IOException, AuthFailureError  

 

@Override
public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
throws IOException, AuthFailureError {
String url = request.getUrl();
HashMap<String, String> map = new HashMap<String, String>();
map.putAll(request.getHeaders());
map.putAll(additionalHeaders);
if (mUrlRewriter != null) {
String rewritten = mUrlRewriter.rewriteUrl(url);
if (rewritten == null) {
throw new IOException("URL blocked by rewriter: " + url);
}
url = rewritten;
}
URL parsedUrl = new URL(url);
HttpURLConnection connection = openConnection(parsedUrl, request);
for (String headerName : map.keySet()) {
connection.addRequestProperty(headerName, map.get(headerName));
}
setConnectionParametersForRequest(connection, request);
// Initialize HttpResponse with data from the HttpURLConnection.
ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1);
int responseCode = connection.getResponseCode();
if (responseCode == -1) {
// -1 is returned by getResponseCode() if the response code could not be retrieved.
// Signal to the caller that something was wrong with the connection.
throw new IOException("Could not retrieve response code from HttpUrlConnection.");
}
StatusLine responseStatus = new BasicStatusLine(protocolVersion,
connection.getResponseCode(), connection.getResponseMessage());
BasicHttpResponse response = new BasicHttpResponse(responseStatus);
response.setEntity(entityFromConnection(connection));
for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) {
if (header.getKey() != null) {
Header h = new BasicHeader(header.getKey(), header.getValue().get(0));
response.addHeader(h);
}
}
return response;
}


HttpClientStack中重写的public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)throws IOException, AuthFailureError  

 

@Override
public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
throws IOException, AuthFailureError {
HttpUriRequest httpRequest = createHttpRequest(request, additionalHeaders);
addHeaders(httpRequest, additionalHeaders);
addHeaders(httpRequest, request.getHeaders());
onPrepareRequest(httpRequest);
HttpParams httpParams = httpRequest.getParams();
int timeoutMs = request.getTimeoutMs();
// TODO: Reevaluate this connection timeout based on more wide-scale
// data collection and possibly different for wifi vs. 3G.
HttpConnectionParams.setConnectionTimeout(httpParams, 5000);
HttpConnectionParams.setSoTimeout(httpParams, timeoutMs);
return mClient.execute(httpRequest);
}


  ps:所以如果要使用第三方的网络请求类库时也可以仿照上面的写法      

创建请求队列时有这么一句话

Network network = new BasicNetwork(stack);
BasicNetwork中最重要的一个方法就是重写的Network中的
public NetworkResponse performRequest(Request<?> request) throws VolleyError


该方法内部只是调用了stack.performRequest(request, headers);  
从而获取到网络数据,然后封装成一个Volley专用的NetworkResponse并返回,NetworkResponse中只是记录了http响应的状态码,响应头,返回的byte数组以及是否缓存。

现在只剩下创建请求队列的最后三步了

RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
queue.start();

return queue;


RequestQueue构造函数中最终执行了如下构造函数

public RequestQueue(Cache cache, Network network, int threadPoolSize,
ResponseDelivery delivery) {
mCache = cache;
mNetwork = network;
mDispatchers = new NetworkDispatcher[threadPoolSize];
mDelivery = delivery;
}


NetworkDispatcher是Thread的直接子类,即该请求队列创建了几个线程(默认是4个),(当然还有一个CacheDispatcher线程)

run方法中mQueue是一个jdk中的线程安全的阻塞队列PriorityBlockingQueue。

由此可知四个线程都是死循环,不断从mQueue中获取request,没有就阻塞,有就执行网络请求,获取网络数据

@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
Request request;
while (true) {
try {
// Take a request from the queue.
request = mQueue.take();
} catch (InterruptedException e) {
// We may have been interrupted because it was time to quit.
if (mQuit) {
return;
}
continue;
}

try {
省略部分代码。。。
// Perform the network request.
NetworkResponse networkResponse = mNetwork.performRequest(request);


RequestQueue中的add方法就是往线程安全的阻塞队列PriorityBlockingQueue中添加请求的,所以你可以同时调用请求队列的add方法添加各种请求

public Request add(Request request) {
// Tag the request as belonging to this queue and add it to the set of current requests.
request.setRequestQueue(this);
synchronized (mCurrentRequests) {
mCurrentRequests.add(request);
}

// Process requests in the order they are added.
request.setSequence(getSequenceNumber());
request.addMarker("add-to-queue");

// If the request is uncacheable, skip the cache queue and go straight to the network.
if (!request.shouldCache()) {
mNetworkQueue.add(request);
return request;
}


执行完网络请求后需要对返回的数据进行处理(parseNetworkResponse方法是Request中需要重写的方法。networkResponse 中记录的不是字符串也不是图片,而是原始的byte数组, 如果你想获取图片,那你就需要使用ImageRequest,想获取字符串就使用StringRequest,所以到底该怎么处理数据由你的请求来决定,所以数据处理工作就放在了Request中而不是Response中)

Response<?> response = request.parseNetworkResponse(networkResponse);


处理完后就该回调监听了。NetworkDispatcher中run方法最后又这么一句

mDelivery.postResponse(request, response);


通过查找代码我们发现mDelivery是在构造请求队列时创建的,同时使用UI线程的Looper创建了一个Handler,这样其他线程使用该handler发送的消息就会跑到UI线程中了。

public RequestQueue(Cache cache, Network network, int threadPoolSize) {
this(cache, network, threadPoolSize,
new ExecutorDelivery(new Handler(Looper.getMainLooper())));
}


在ExecutorDelivery中我们看到了最终调用了

// Deliver a normal response or error, depending.
if (mResponse.isSuccess()) {
mRequest.deliverResponse(mResponse.result);
} else {
mRequest.deliverError(mResponse.error);
}
request中最终又调用了 mListener.onResponse(response);(以StringRequest为例)
@Override
protected void deliverResponse(String response) {
mListener.onResponse(response);
}
Request中:
public void deliverError(VolleyError error) {
if (mErrorListener != null) {
mErrorListener.onErrorResponse(error);
}
}


到这为止Volley的主要流程就分析完毕了。总结一下主要流程:

RequestQueue mQueue = Volley.newRequestQueue(this);   ->

mQueue.add(Request)到PriorityBlockingQueue中   ->

多个NetworkDispatcher线程循环从PriorityBlockingQueue 读取Requset  ->

得到Requset然后执行网络请求 NetworkResponse networkResponse = mNetwork.performRequest(request);  ->

得到networkResponse ,对networkResponse 中的原始数据进行处理(如 转换成图片或字符串)得到Response->

通过ExecutorDelivery转移到主线程去回调监听器 ->

得到结果,请求结束




图片加载源码解析

图片加载源码只不过是对封装了一下,底层实现类似。

先看一下如何使用Volley加载图片

ImageView imageView = (ImageView) findViewById(R.id.iv2);
ImageListener listener = ImageLoader.getImageListener(imageView, android.R.drawable.ic_menu_rotate,
android.R.drawable.ic_delete);

ImageLoader mImageLoader = new ImageLoader(mQueue, new BitmapCache());
mImageLoader.get("http://imgstatic.baidu.com/img/image/shouye/fanbingbing.jpg", listener);


下面这句话没啥特殊的,就是new了一个Listener并返回,并在重写的方法中设置图片下载失败、成功时的图片
ImageLoader.getImageListener(imageView, android.R.drawable.ic_menu_rotate,
android.R.drawable.ic_delete);


主要看这个函数

mImageLoader.get("http://imgstatic.baidu.com/img/image/shouye/fanbingbing.jpg", listener);
详见如下注释
public ImageContainer get(String requestUrl, ImageListener imageListener,
int maxWidth, int maxHeight) {
// only fulfill requests that were initiated from the main thread.
// 是否在主线程中执行,不是就抛异常
throwIfNotOnMainThread();

/*
* 把图片路径以及大小拼接成一个字符串,作为map中的键,图片作为值,从
* 而可以根据键来快速从内存中获取图片(即 缓存到内存中,同时使用LRU算法对bitmap进行替换)
*/
final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight);

// Try to look up the request in the cache of remote images.
Bitmap cachedBitmap = mCache.getBitmap(cacheKey);
if (cachedBitmap != null) {
// Return the cached bitmap.
//把图片,url等封装一下作为参数传递给监听器
ImageContainer container = new ImageContainer(cachedBitmap, requestUrl, null, null);
imageListener.onResponse(container, true);
return container;
}

// The bitmap did not exist in the cache, fetch it!
//如果内存中之前没有这张图片
ImageContainer imageContainer =
new ImageContainer(null, requestUrl, cacheKey, imageListener);

// Update the caller to let them know that they should use the default bitmap.
imageListener.onResponse(imageContainer, true);

// Check to see if a request is already in-flight.
BatchedImageRequest request = mInFlightRequests.get(cacheKey);
if (request != null) {
// If it is, add this request to the list of listeners.
request.addContainer(imageContainer);
return imageContainer;
}

// The request is not already in flight. Send the new request to the network and
// track it.
/*
* 创建一个ImageRequest,加入到请求队列中下载图片,下载成功后会把bitmap和cacheKey保
* 存到map中进行缓存。
* ImageRequest中的parseNetworkResponse会对图片进行压缩处理
*/

Request<?> newRequest =
new ImageRequest(requestUrl, new Listener<Bitmap>() {
@Override
public void onResponse(Bitmap response) {
onGetImageSuccess(cacheKey, response);
}
}, maxWidth, maxHeight,
Config.RGB_565, new ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
onGetImageError(cacheKey, error);
}
});

mRequestQueue.add(newRequest);
mInFlightRequests.put(cacheKey,
new BatchedImageRequest(newRequest, imageContainer));
return imageContainer;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息