之前的博客中已经记录过Volley的基本使用方法了,
从本篇博客开始,我会比较仔细地分析整个Volley框架的源码。
对于一个APK开发者而言,细致地了解整个Volley源码可能用处不大,
但对于一个Framework开发者而言,阅读和分析源码的能力还是时不时地锻炼一下为好,
况且如此广泛被使用的通信框架,它的源码应该是营养丰富的,仔细看看一定会有所收获的。
1. Volley.newRequestQueue
我们从Volley中RequestQueue的初始化入手,开始进行分析。
//应用利用Volley.java的静态方法,获取RequestQueue,开启使用Volley框架的大门
public static RequestQueue newRequestQueue(Context context) {
return newRequestQueue(context, null);
}
//最终调用到该函数
//HttpStack是个接口,定义了performRequest函数
public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
//创建一个File文件,指向应用默认的Cache路径,子路径名称为“volley”
//例如之前博客的demo,最终会建立一个/data/data/stark.a.is.zhang.volleytest/cache/volley的cache文件
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名称,这个将用在Http通信中
userAgent = packageName + "/" + info.versionCode;
} catch (NameNotFoundException e) {
}
//创建通信使用的协议栈
//在Volley框架中,协议栈应该是进行通信的实体对象
//之后分析Volley处理Request的流程时,应该还会遇到
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 network = new BasicNetwork(stack);
//创建DiskBasedCache对象,指向之前的cacheDir,默认缓存最大值为5MB
//注意到构造RequestQueue时,同时传入了缓存对象和网络对象,因此缓存和下载就是RequestQueue的核心功能
RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
//创建完RequestQueue后,调用其start方法启动
queue.start();
return queue;
}
从上述代码来看,即使我们在同一个应用中多次调用newRequestQueue,
得到的多个RequestQueue也将共用同一个缓存文件和userAgent,
即对于网络服务端,一个应用所有的RequestQueue都只是一个客户。
从这里可以看出,同一个应用使用一个RequestQueue就够了。
后文可以看到,RequestQueue底层已经支持并发特性,
多个RequestQueue进一步加大并发力度,对于移动终端而言,用处不大。
在这一部分的最后,我们看看RequestQueue的构造函数:
public RequestQueue(Cache cache, Network network) {
//DEFAULT_NETWORK_THREAD_POOL_SIZE的值为4
this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);
}
public RequestQueue(Cache cache, Network network, int threadPoolSize) {
this(cache, network, threadPoolSize,
//ExecutorDelivery用于向主线程传递下载结果
new ExecutorDelivery(new Handler(Looper.getMainLooper())));
}
public RequestQueue(Cache cache, Network network, int threadPoolSize,
ResponseDelivery delivery) {
mCache = cache;
mNetwork = network;
//创建NetworkDispatcher对应的数组
mDispatchers = new NetworkDispatcher[threadPoolSize];
mDelivery = delivery;
}
接下来,我们看看RequestQueue的start方法。
2. RequestQueue的start方法
.................
public void start() {
//如果已经存在处于运行态的CacheDispatcher和NetworkDispatcher,则进行停止操作
//相当于先复位现有状态
stop(); // Make sure any currently running dispatchers are stopped.
// Create the cache dispatcher and start it.
// 创建新的CacheDispatcher,并启动
// CacheDispatcher具有cache和network对应的BlockingQueue
mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
mCacheDispatcher.start();
// Create network dispatchers (and corresponding threads) up to the pool size.
// 默认创建4个NetworkDispatcher
//这个四个NetworkDispatcher与上文的CacheDispatcher共用network对应的BlockingQueue
for (int i = 0; i < mDispatchers.length; i++) {
NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
mCache, mDelivery);
mDispatchers[i] = networkDispatcher;
networkDispatcher.start();
}
}
.................
CacheDispatcher与NetworkDispatcher均继承自Thread,我们看看这些线程启动后的情况。
2.1 CacheDispatcher的run方法
@Override
public void run() {
..............
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
// Make a blocking call to initialize the cache.
// 初始化缓存区,即调用DiskBasedCache的initialize方法
mCache.initialize();
..............
在分析后续流程前,先看看DiskBasedCache在initialize方法中的工作。
2.1.1 DiskBasedCache的initialize方法
@Override
public synchronized void initialize() {
//如果应用对应的cache/volly不存在,就创建对应的目录
if (!mRootDirectory.exists()) {
if (!mRootDirectory.mkdirs()) {
VolleyLog.e("Unable to create cache dir %s", mRootDirectory.getAbsolutePath());
}
return;
}
//得到目录下所有的文件
File[] files = mRootDirectory.listFiles();
if (files == null) {
return;
}
//如果目录下已经存在文件,就对文件进行解析
for (File file : files) {
BufferedInputStream fis = null;
try {
fis = new BufferedInputStream(new FileInputStream(file));
//从所有的文件中解析出对应的CacheHeader对象
//readHeader方法就是从InputStream中,读取CacheHeader所需的字段
CacheHeader entry = CacheHeader.readHeader(fis);
entry.size = file.length();
//文件被放置到LinkedHaspMap中
//LinkedHaspMap的初始化形式为:new LinkedHashMap<String, CacheHeader>(16, .75f, true);
//最后的参数表示accessOrder,当为true时,以LRU算法保存对象
//初始向LinkedHaspMap加入数据时,按加入顺序依次保存
//一旦访问LinkedHashMap时,被访问的对象将被移动到尾部
//当LinkedHaspMap满了,需要释放空间时,将从前面开始移除最老的对象
putEntry(entry.key, entry);
} catch (IOException e) {
if (file != null) {
file.delete();
}
} finally {
try {
if (fis != null) {
fis.close();
}
} catch (IOException ignored) { }
}
}
}
至此,DiskBasedCache的initialize方法分析完毕。
容易看出,在该方法中将创建出物理的文件存储目录;
如果该目录已经存在,则形成并保存当前Cache文件的CacheHeader。
DiskBasedCache将以LinkedHashMap来保存CacheHeader,以呈现出LRU算法的特性。
2.1.2 CacheDispatcher的服务端循环
分析完初始化缓存目录后,我们回到CacheDispatcher的run方法,看看后续的代码:
...................
//无限循环
//CacheDispatcher线程类似于一个处理Request的服务端
while (true) {
try {
// Get a request from the cache triage queue, blocking until
// at least one is available.
// mCacheQueue是一个BlockingQueue,意味着无数据时,将一直阻塞在这里
final Request<?> request = mCacheQueue.take();
request.addMarker("cache-queue-take");
// If the request has been canceled, don't bother dispatching it.
// 当取出Request后,首先判断是否已经取消
if (request.isCanceled()) {
//若Request已取消,则调用其finish接口
request.finish("cache-discard-canceled");
continue;
}
// Attempt to retrieve this item from cache.
//根据Request的CacheKey,判断DiskBaseCache中是否已经进行过存储
Cache.Entry entry = mCache.get(request.getCacheKey());
if (entry == null) {
request.addMarker("cache-miss");
// Cache miss; send off to the network dispatcher.
// 没有存储的话,就将该Request加入到NetworkQueue中
// 注意到NetworkDispatcher保存着NetworkQueue的引用,将负责处理
mNetworkQueue.put(request);
continue;
}
// If it is completely expired, just send it to the network.
// 如果Request有对应的Cache,但Cache too old
// 那么也要将Request重新加入到NetworkQueue中
if (entry.isExpired()) {
request.addMarker("cache-hit-expired");
request.setCacheEntry(entry);
mNetworkQueue.put(request);
continue;
}
// We have a cache hit; parse its data for delivery back to the request.
request.addMarker("cache-hit");
// 如果有可用的Cache,那么从Cache中解析出NetworkResponse
Response<?> response = request.parseNetworkResponse(
new NetworkResponse(entry.data, entry.responseHeaders));
request.addMarker("cache-hit-parsed");
//如果缓存不需要更新
if (!entry.refreshNeeded()) {
// Completely unexpired cache hit. Just deliver the response.
//利用ExecutorDelivery将结果返回给UI线程
mDelivery.postResponse(request, response);
} else {
// 缓存可用,但需要refresh时
// Soft-expired cache hit. We can deliver the cached response,
// but we need to also send the request to the network for
// refreshing.
request.addMarker("cache-hit-refresh-needed");
request.setCacheEntry(entry);
// Mark the response as intermediate.
response.intermediate = true;
// Post the intermediate response back to the user and have
// the delivery then forward the request along to the network.
// 将结果递交给界面,同时提交Request给NetworkQueue
mDelivery.postResponse(request, response, new Runnable() {
@Override
public void run() {
try {
mNetworkQueue.put(request);
} catch (InterruptedException e) {
// Not much we can do about this.
}
}
});
}
} catch(InterruptedException e) {
// We may have been interrupted because it was time to quit.
//异常时,除非调用过quit接口,否则继续循环
if (mQuit) {
return;
}
continue;
}
}
.......................
至此,CacheDispatcher的服务端介绍完毕。
上面代码的处理逻辑还是清晰易懂的:
当CacheDispatcher得到一个Request时,试着从Cache中获取对应的Response信息。
如果获取失败或着Cache超时,那么将Request加入到NetworkQueue中,等待下载。
如果成功获取到Cache,那么解析出对应的Response,发送给UI线程;
如果Cache还要求refresh,那么将Response递交给UI线程的同时,仍将Request递交给NetworkQueue处理。
2.2 NetworkDispatcher的run方法
现在我们再来看看NetworkDispatcher的run方法:
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
while (true) {
//要是我写这个框架,我就将这行代码移到mQueue.take下面
//感觉这样更准确些吧
long startTimeMs = SystemClock.elapsedRealtime();
Request<?> request;
try {
// Take a request from the queue.
// 从NetworkQueue中取出Request
request = mQueue.take();
} catch (InterruptedException e) {
// We may have been interrupted because it was time to quit.
if (mQuit) {
return;
}
continue;
}
try {
request.addMarker("network-queue-take");
// If the request was cancelled already, do not perform the
// network request.
if (request.isCanceled()) {
request.finish("network-discard-cancelled");
continue;
}
.............
// Perform the network request.
// 交给BasicNetwork的performRequest处理
// 将与网络端通信,直到获得返回结果
// 下一篇博客再分析这些细节
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.
// 当向服务端发送了同样的NetworkRequest,服务端回应not modified
// 并且这个NetworkRequest已经向UI线程发送过Response时,不再进行后续处理
if (networkResponse.notModified && request.hasHadResponseDelivered()) {
request.finish("not-modified");
continue;
}
// Parse the response here on the worker thread.
// 由Request的子类处理,解析返回结果
Response<?> response = request.parseNetworkResponse(networkResponse);
request.addMarker("network-parse-complete");
//如果需要,则将Response中的信息加入到缓存中
if (request.shouldCache() && response.cacheEntry != null) {
mCache.put(request.getCacheKey(), response.cacheEntry);
request.addMarker("network-cache-written");
}
// Post the response back.
request.markDelivered();
//向UI线程返回结果
mDelivery.postResponse(request, response);
} catch (VolleyError volleyError) {
volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
//如果出现的是VolleyError,则根据Request的类型解析VolleyError,再递交给UI线程
parseAndDeliverNetworkError(request, volleyError);
} catch (Exception e) {
//出异常时,直接封装异常并递交给UI线程
VolleyLog.e(e, "Unhandled exception %s", e.toString());
VolleyError volleyError = new VolleyError(e);
volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
mDelivery.postError(request, volleyError);
}
}
}
至此,NetworkDispatcher的run方法分析完毕。
作为一个服务端,NetworkDispatcher将具体的下载工作递交给BasicNetwork处理;
下载完毕后的结果递交给具体的Request子类分析,并将处理结果通过ExecutorDelivery发送给UI线程。
3. 总结
了解Volley的Cache和Network服务端处理流程后,
我们基本上已经大致知道了Volley处理网络请求的脉络了。
结合上图,我们再来整体回顾一下。
从整体架构来看,Volley就是一个供APK调用的工具类。
当Volley的接口被调用,用于创建RequetQueue时,
Volley会负责创建下载需要的HttpStack,并指定存储缓存的Cache区域。
然后,Volley将管理HttpStack的BasicNetwork对象,及管理Cache的DiskBasedCache对象
一并递交给RequestQueue的构造函数。
RequestQueue会负责启动检索缓存的CacheDispatcher对象,
执行网络下载工作的NetworkDispatcher对象,
及为UI线程传递Response信息的ExecutorDelivery对象。
CacheDispatcher启动后,将负责创建DiskBasedCache对应的文件存储目录;
收到Request后,根据对应目录下的缓存信息,决定是否进行实际的网络下载工作。
NetworkDispatcher启动,收到Request后,将利用BasicNetwork进行实际的下载工作。
CacheDispatcher和NetworkDispatcher均利用ExecutorDelivery向UI线程返回Response信息。
在下一篇博客中,我们再从向RequestQueue加入Request的流程开始分析,
从客户端流程入手,看看Volley是如何分配Request的,并深入看看BasicNetwork的实际下载流程。