更多关于安卓源码分析文章,请看:安卓源码分析
Volley源码分析系列:
1.聊下Volley源码(整体流程)
2.聊聊Volley源码(网络请求过程)
3.聊聊Volley源码(缓存流程)
最近过年回来工作很清闲,于是变想找点事做,想起虽然看分析Volley源码的文章也不算少,但是还没有真正去研究过Volley源码,所以就好好研究它。
Volley大家想必都比较熟悉,作为谷歌官方推荐的网络请求库,必定很强大,可以满足广大群众的需求,像很多介绍Volley的博客一样,我也来一张官方的图片:
Volley正如图片所示一群弓箭手射箭一样,适合频繁但量小的请求。
这是官方的工作流程图:
从图中可以看出Volley工作时总共有三种线程,分别是主线程、请求线程、缓存线程,图中的流程大致如下:首先将请求放入缓存队列(根据优先级排序),缓存线程将请求从缓存队列中取出,如果命中缓存(即该请求的缓存存在),则将缓存的数据取出解析并将结果投递到主线程。如果没有命中,则将请求放入请求队列去执行网络请求。(这里需要对线程的生产者消费者模式知识比较熟悉,不熟悉的话可以看我这篇 java多线程设计模式之消费者生产者模式)
没看懂没关系,接下来就尽可能用白话方式分析下源码。
首先从最基本的使用说起,比如请求String数据:
RequestQueue mQueue = Volley.newRequestQueue(context);
StringRequest stringRequest = new StringRequest("http://www.baidu.com",
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
Log.d("TAG", response);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.e("TAG", error.getMessage(), error);
}
});
mQueue.add(stringRequest);
这样一个请求就“发”出去了,相信用过Volley的朋友都不陌生吧。那接下来我们就扒开这些代码的表皮,进去看个究竟。
首先从第一行代码看起:
RequestQueue mQueue = Volley.newRequestQueue(context);
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;
}
这里先是创建了一个应用的缓存文件目录和一个userAgent字符串,熟悉http的朋友就知道这个请求头吧,然后会判断sdk版本是否大于9,大于等于9则创建HurlStack对象,否则创建HttpClientStack,这两个是我们的请求类,都是实现了Stack接口,前者封装了HttpUrlConnection,后者封装了HttpClient。之所以这样,是因为安卓2.3(sdk版本9)以下HttpClient的bug少,HttpUrlConnection有一些比较严重的Bug,而在安卓2.3之后,HttpUrlConnection的很多bug得到修复,由于其体积小,API简单,省流量性能又较高,所以成为首选(安卓6.0已经废弃了HttpClient的使用)。
然后将创建的Stack实现类又传入BasicNetwork,这是对实现Stack的请求类进一步的封装,它实现NetWork接口,这部分下篇会讲到。
接下来就是非常重要的RequestQueue隆重登场,构造它的参数一个是DiskBasedCache,看名字就能猜出它是硬盘缓存的类,传入之前创建的缓存文件目录,另一个就是BasicNetwork对象,那这个RequestQueue究竟扮演怎样的角色呢?
从RequestQueue的start方法入手:
public void start() {
stop(); // Make sure any currently running dispatchers are stopped.
// Create the cache dispatcher and start it.
mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
mCacheDispatcher.start();
// Create network dispatchers (and corresponding threads) up to the pool size.
for (int i = 0; i < mDispatchers.length; i++) {
NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
mCache, mDelivery);
mDispatchers[i] = networkDispatcher;
networkDispatcher.start();
}
}
这里的主角就是NetworkDispatcher 和CacheDispatcher,这两个是什么,进入发现NetworkDispatcher和CacheDispatcher 都是Thread的子类,并且注意到它们都持有一个优先级阻塞队列PriorityBlockingQueue<Request<?>>,从上面的代码可以看到正是RequestQueue将这个阻塞队列传给进来的。
进入NetworkDispatcher的run方法:
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
while (true) {
long startTimeMs = SystemClock.elapsedRealtime();
Request<?> request;
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;
}
run方法比较长,这是前面一部分,可以看到在一个无限循环中,不断从mQueue(就是RequestQueue传进来的优先级阻塞队列)中取出Request,如果看过生产者消费者模式应该知道能明白了。记得客户端发起请求执行的mQueue.add()方法么,就是将Request投入这个队列中(细节下篇再讲),在NetworkDispatcher线程中取出来,再看后面:
// Perform the network request.
NetworkResponse networkResponse = mNetwork.performRequest(request);
request.addMarker("network-http-complete");
// If the server returned 304 AND we delivered a response already,
// we're done -- don't deliver a second identical response.
if (networkResponse.notModified && request.hasHadResponseDelivered()) {
request.finish("not-modified");
continue;
}
// Parse the response here on the worker thread.
Response<?> response = request.parseNetworkResponse(networkResponse);
request.addMarker("network-parse-complete");
注意第二行,mNetwork就是BasicNetwork,从方法名字也可以猜出这是执行网络请求,再看第十二行,也是从方法名字可以看出是对刚才的网络请求的结果进行解析得到一个Response的对象。在run的末尾:
mDelivery.postResponse(request, response);
这是一个ExecutorDelivery对象,它是将请求结果传递到主线程,记得在客户端请求代码中
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
Log.d("TAG", response);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.e("TAG", error.getMessage(), error);
}
});
就是将结果回调到这里。
其实请求这里就是一个生产者消费者模型,RequestQueue启动的时候开启若干条请求线程NetworkDispatcher ,线程无限循环从阻塞队列中取请求,每当客户端将请求添加到这个阻塞队列时,就会有一个NetworkDispatcher 将请求取出并调用一个请求类Network请求数据,并解析数据然后将解析完的结果传递到主线程的客户端程序。
而CacheDispatcher也是类似的原理,只是取数据不是从网络,而是从本地的缓存取出。
因为Volley的类确实比较多,这里附上codeKK某大牛对Volley一些重要的类的说明:
**Volley**:Volley 对外暴露的 API,通过 newRequestQueue(…) 函数新建并启动一个请求队列RequestQueue。
**Request**:表示一个请求的抽象类。StringRequest、JsonRequest、ImageRequest 都是它的子类,表示某种类型的请求。
**RequestQueue**:表示请求队列,里面包含一个CacheDispatcher(用于处理走缓存请求的调度线程)、NetworkDispatcher数组(用于处理走网络请求的调度线程),一个ResponseDelivery(返回结果分发接口),通过 start() 函数启动时会启动CacheDispatcher和NetworkDispatchers。
**CacheDispatcher**:一个线程,用于调度处理走缓存的请求。启动后会不断从缓存请求队列中取请求处理,队列为空则等待,请求处理结束则将结果传递给ResponseDelivery去执行后续处理。当结果未缓存过、缓存失效或缓存需要刷新的情况下,该请求都需要重新进入NetworkDispatcher去调度处理。
**NetworkDispatcher**:一个线程,用于调度处理走网络的请求。启动后会不断从网络请求队列中取请求处理,队列为空则等待,请求处理结束则将结果传递给ResponseDelivery去执行后续处理,并判断结果是否要进行缓存。
**ResponseDelivery**:返回结果分发接口,目前只有基于**ExecutorDelivery**的在入参 handler 对应线程内进行分发。
**HttpStack**:处理 Http 请求,返回请求结果。目前 Volley 中有基于 HttpURLConnection 的HurlStack和 基于 Apache HttpClient 的HttpClientStack。
**Network**:调用HttpStack处理请求,并将结果转换为可被ResponseDelivery处理的NetworkResponse。
**Cache**:缓存请求结果,Volley 默认使用的是基于 sdcard 的DiskBasedCache。NetworkDispatcher得到请求结果后判断是否需要存储在 Cache,CacheDispatcher会从 Cache 中取缓存结果。
本文基本就是带各位溜了一圈Volley,大概知道Volley是怎么玩的,后续会对请求的缓存流程做比较详细的分析([聊聊Volley源码(网络请求过程)](http://blog.csdn.net/sinat_23092639/article/details/55673513))