前言
因为现在负责的项目,要开始做组件化了。首先就是要根据项目的具体业务来重新封装一个网络库组件。目前项目中使用的是volley,所以封装前,就对volley的源码进行了阅读。
Volley解析开始
注意:这里阅读的源码是以项目中使用的版本为主,可能会与最新的volley代码有些出入。
-
首先从Volley的静态方法newRequestQueue开始
-
解释上图newRequestQueue(Context context, HttpSatck stack)方法中出现的类:
- HttpStack:是一个实现网络请求的抽象接口协议,实现类HurlStack等,也可替换为OkHttp。
- Network:是一个执行网络请求的抽象接口协议,具体实现类为BasicNetwork。
- Cache:是一个缓存网络请求数据的抽象接口协议,具体实现类为DiskBasedCache。
- RequestQueue:一个带有线程数组的网络请求调度队列,接下来看下它的queue.start()方法,主要是开启一个了缓存分发线程CacheDispatcher,和通过for循环来创建若干个网络分发线程NetworkDispatcher。
-
解释上图start()方法中出现的类:
- PriorityBlockingQueue:一个带优先级的无界阻塞队列,所以Request抽象类实现了Comparable接口。
- PriorityBlockingQueue-mCacheQueue:缓存请求队列。
- PriorityBlockingQueue-mNetworkQueue:网络请求队列。
- Cache-mCache:将网络请求的信息缓存到本地磁盘,具体的实现类为DiskBasedCache。
- ResponseDelivery-mDelivery:将网络请求的响应数据发送到业务层的抽象接口协议,具体实现类为ExecutorDelivery,内部是通过Handler将数据发送到主线程。
- Request抽象类:主要就是设置请求方式,处理上传给服务端的请求数据,解析请求结果等,具体的实现类由自己去实现,当然volley也提供了JsonObjectRequest,ImageRequest等。
-
前期的创建工作完成了,接下来分析如何处理网络请求的,先从RequestQueue中的add方法开始,它的作用是将网络请求添加到相应的队列中,再去相应的处理,一般由业务层发起。
- 解释下上图add方法中的逻辑
:
Set<Request<?>> mCurrentRequests:将Request添加到当前请求的集合中,主要用于cancelAll方法中取消所有的网络请求。:
判断是否将请求添加到缓存队列中,否则添加到网络请求队列,默认是true。:
Map<String, Queue<Request<?>>> mWaitingRequests是一个等待请求的集合,key值通过Request中getCacheKey()获取,默认是Url,也可覆盖重写,value是一个请求队列,它主要用于finish方法。继续分析3处的代码,判断mWaitingRequests是否包含当前请求队列,包含则添加到队列中覆盖之前的请求,不包含则添加到mCacheQueue队列和mWaitingRequests集合中。
- 注意:RequesetQueue中的cancel,finish,stop方法的区别
- cancel:是业务层调用,调用之后不会将请求的结果返回出去。
- finish:是网络请求完成时调用的,主要是在NetworkDispatcher中符合请求结束条件之后调用的,且内部做了置空释放资源的操作。
- stop:通过线程的interrupt方法中断缓存分发线程,网络分发线程,这个方法一旦调用,volley也就停止工作了,必须通过start方法重新启用。
其实当时在看这段代码的时候是有疑问的,怎么都添加到缓存队列中了,网络请求队列呢,网络请求怎么执行的?下面接着分析两个重要的类CacheDispatcher和NetworkDispatcher。
CacheDispatcher:继承于线程,并且在run方法中开启了一个while死循环,不停的监听缓存请求队列(mCacheQueue),所以下面主要看它的run方法,代码比较长,直接贴源码。
@Override
public void run() {
if (DEBUG) VolleyLog.v("start new dispatcher");
//设置线程的优先级
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
// 初始化DiskBaseCache
mCache.initialize();
//while死循环,不停的监听mCacheQueue,处理网络请求
while (true) {
// 因为是线程数组,不是真的线程池,不能动态的创建新的线程,这里也必须使用try catch来捕获异常
try {
//从缓存队列中取出request
final Request<?> request = mCacheQueue.take();
request.addMarker("cache-queue-take");
// 如果请求已经取消就直接finish
if (request.isCanceled()) {
request.finish("cache-discard-canceled");
continue;
}
// 根据CacheKey(默认是url),获取缓存的数据
Cache.Entry entry = mCache.get(request.getCacheKey());
if (entry == null) {
request.addMarker("cache-miss");
// 没有缓存直接添加到网络队列中去执行网络请求
mNetworkQueue.put(request);
continue;
}
// 如果缓存的数据已经过期也直接添加到网络队列中去执行网络请求
if (entry.isExpired()) {
request.addMarker("cache-hit-expired");
request.setCacheEntry(entry);
mNetworkQueue.put(request);
continue;
}
// 解析网络数据
request.addMarker("cache-hit");
Response<?> response = request.parseNetworkResponse(
new NetworkResponse(entry.data, entry.responseHeaders));
request.addMarker("cache-hit-parsed");
if (!entry.refreshNeeded()) {
//如果不需要刷新数据源就直接返回
mDelivery.postResponse(request, response);
} else {
//需要刷新数据,先将中间响应数据返回给用户,然后再将请求添加到网络队列中,执行网络请求。
request.addMarker("cache-hit-refresh-needed");
request.setCacheEntry(entry);
response.intermediate = true;
mDelivery.postResponse(request, response, new Runnable() {
@Override
public void run() {
try {
mNetworkQueue.put(request);
} catch (InterruptedException e) {
}
}
});
}
} catch (InterruptedException e) {
if (mQuit) {
return;
}
continue;
}
}
}
NetworkDispatcher:继承于线程,同时也在run方法中开启了一个while死循环,不停监听网络请求队列(mNetworkQueue),所以直接看run方法。
@Override
public void run() {
//设置线程优先级
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
while (true) {
long startTimeMs = SystemClock.elapsedRealtime();
Request<?> request;
try {
//从网络请求队列中取出reuqest
request = mQueue.take();
} catch (InterruptedException e) {
if (mQuit) {
return;
}
continue;
}
try {
request.addMarker("network-queue-take");
// 若请求取消就直接finish
if (request.isCanceled()) {
request.finish("network-discard-cancelled");
continue;
}
//为当前请求线程打上一个tag,统计线程中网络请求的数据流量
addTrafficStatsTag(request);
// 这里就是通过mNetwork(BasicNetwork)调用网络请求,并返回数据的地方
NetworkResponse networkResponse = mNetwork.performRequest(request);
request.addMarker("network-http-complete");
//若服务端返回304或者响应数据已经发送出去,就直接finish当前请求
if (networkResponse.notModified && request.hasHadResponseDelivered()) {
request.finish("not-modified");
continue;
}
//解析响应数据
Response<?> response = request.parseNetworkResponse(networkResponse);
request.addMarker("network-parse-complete");
// 若请求开启了缓存,并且响应数据不为null,就缓存到本地文件磁盘中
if (request.shouldCache() && response.cacheEntry != null) {
mCache.put(request.getCacheKey(), response.cacheEntry);
request.addMarker("network-cache-written");
}
//标记当前请求已经将响应数据发送出去了
request.markDelivered();
//将响应数据发送出去,并通过handler发送到主线程。
mDelivery.postResponse(request, response);
} catch (VolleyError volleyError) {
volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
parseAndDeliverNetworkError(request, volleyError);
} catch (Exception e) {
VolleyLog.e(e, "Unhandled exception %s", e.toString());
VolleyError volleyError = new VolleyError(e);
volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
mDelivery.postError(request, volleyError);
}
}
}
Volley解析总结
- Volley的基本模型:缓存分发线程(CacheDispatcher)与网络分发线程(NetworkDispatcher)开启死循环不停的监听对应的缓存队列和网络请求队列,一旦有请求将要加入队列中时,先判断有没有开启缓存,没开启则直接添加到网络请求队列中由网络分发线程处理,否则,就由缓存分发线程处理完之后再确定是否添加到网络请求队列中交由网络分发线程来处理。基本的流程就是这样,这里可能有一些东西没有分析到,比如字节数组缓冲池ByteArrayPool等,主要是还是以volley执行流程为主。
- 代码的封装角度来看Volley:它制定了很多接口协议,调用时都只与上层接口接触,而不与具体的实现类交互,好处就是,比如无论你的网络请求是由HttpClient,HttpUrlConnection,还是OkHtpp实现,它的核心代码都不会变,你只要新增类实现HttpStack接口即可,而不会去影响其它类,因为新增是比修改要好的,所以当你封装自己的代码时,要知道如何去制定和封装我们的上层接口协议,要隔离具体的实现类与逻辑代码,让你的代码变得可维护,可扩展。
- 最后:若有分析错误的地方,欢迎指证讨论。