Volley源码解析——从实现角度深入剖析volley
Posted Beanvy
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Volley源码解析——从实现角度深入剖析volley相关的知识,希望对你有一定的参考价值。
本文从实现角度详细讲解了volley框架,读者需要有对基本的volley使用和handler机制有一定的了解,主要目的是研究volley的实现思路,在理解代码的基础上会思考为什么要这么写,很多地方都需要读者先大概地阅读一下贴出的源码。由于第一次写框架分析的博文,有些地方解释得可能不是很清楚,希望大家能谅解
1.入口类Volley
像很多框架、SDK一样,volley使用了facade设计模式,提供Volley类来简化volley的使用,Volley.newRequestQueue源码如下
public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
File cacheDir = new File(context.getCacheDir(), "volley");
String userAgent = "volley/0";
try {
String network = context.getPackageName();
PackageInfo queue = context.getPackageManager().getPackageInfo(network, 0);
userAgent = network + "/" + queue.versionCode;
} catch (NameNotFoundException var6) {
;
}
if(stack == null) {
if(VERSION.SDK_INT >= 9) {
stack = new HurlStack();
} else {
stack = new HttpClientStack(androidHttpClient.newInstance(userAgent));
}
}
BasicNetwork network1 = new BasicNetwork((HttpStack)stack);
RequestQueue queue1 = new RequestQueue(new DiskBasedCache(cacheDir), network1);
queue1.start();
return queue1;
}
public static RequestQueue newRequestQueue(Context context) {
return newRequestQueue(context, (HttpStack)null);
}
容易看出使用的cache文件夹是context.getCacheDir()/volley的,当APP卸载后会自动删除,使用的userAgent正常情况下是packageName/appVersionCode,在使用
Volley.newRequestQueue(context)接口初始化RequestQueue的时候使用的是HurlStack,查看了一下HurlStack和BasicNetwork的源码,大概是用于执行网络请求的,我们先跳过,在获取requestQueue的时候requestQueue就已经调用start方法启动了,先查看RequestQueue这个最重要的类,看里面主要是做什么的
2.RequestQueue
RequestQueue通过start方法启动,我们先查看一下这个方法做了什么
public void start() {
this.stop();
this.mCacheDispatcher = new CacheDispatcher(this.mCacheQueue, this.mNetworkQueue, this.mCache, this.mDelivery);
this.mCacheDispatcher.start();
for(int i = 0; i < this.mDispatchers.length; ++i) {
NetworkDispatcher networkDispatcher = new NetworkDispatcher(this.mNetworkQueue, this.mNetwork, this.mCache, this.mDelivery);
this.mDispatchers[i] = networkDispatcher;
networkDispatcher.start();
}
}
首先调用了stop方法,这个方法主要用于防止开发者使用Volley不当,重复调用了start方法(这里是实现时候需要考虑的一个细节),stop方法会将mCacheDispatcher和NetworkDispatcher停止(如果非null的话),代码比较简单,就不贴出来了。
mCacheDispatcher从名字上可以看出是用于缓存网络请求的,networkDispatcher是用于执行网络请求的,CacheDispatcher和NetworkDispatcher继承了Thread类,可以看出有一个缓存线程,在RequestDispatcher初始化的时候mNetworkDispatcher被初始化为一个长度为4的数组,所以有4个网络请求线程
理解了start的代码,剩下的RequestQueue代码也好理解,我们先看一下CacheDispatcher和NetworkDispatcher的代码
3.CacheDispatcher
从RequestQueue.start方法中CacheDispatcher的初始化代码可以看出CacheDispatcher需要传去mCacheQueue(缓存请求队列)、mNetworkQueue(网络请求队列),mCacheQueue和mNetworkQueue直接使用了java.util.concurrent包下的PriorityBlockingQueue来处理优先级和线程同步的问题,类型代码如下
private final PriorityBlockingQueue<Request> mCacheQueue;
private final PriorityBlockingQueue<Request> mNetworkQueue;
mCache(缓存,从1.Volley中可以看出这里是个缓存在context.getCacheDir()/volley的DiskCache)
mDelivery的类型为ResponseDelivery,ResponseDelivery的代码如下:
public interface ResponseDelivery {
void postResponse(Request<?> var1, Response<?> var2); //request,response
void postResponse(Request<?> var1, Response<?> var2, Runnable var3); //request,response,runnable
void postError(Request<?> var1, VolleyError var2); //request, response
}
mDelivery的实际类型为ExecutorDelivery,通过RequestQueue的构造器可以看出:
public RequestQueue(Cache cache, Network network, int threadPoolSize) {
this(cache, network, threadPoolSize, new ExecutorDelivery(new Handler(Looper.getMainLooper())));
}
从上面代码大概可以猜出WxecutorDelivery里面使用了Handler来实现网络线程和主线程的通信,源码如下
public class ExecutorDelivery implements ResponseDelivery {
private final Executor mResponsePoster;
public ExecutorDelivery(final Handler handler) {
this.mResponsePoster = new Executor() {
public void execute(Runnable command) {
handler.post(command);
}
};
}
public ExecutorDelivery(Executor executor) {
this.mResponsePoster = executor;
}
public void postResponse(Request<?> request, Response<?> response) {
this.postResponse(request, response, (Runnable)null);
}
public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
request.markDelivered();
request.addMarker("post-response");
this.mResponsePoster.execute(new ExecutorDelivery.ResponseDeliveryRunnable(request, response, runnable));
}
public void postError(Request<?> request, VolleyError error) {
request.addMarker("post-error");
Response response = Response.error(error);
this.mResponsePoster.execute(new ExecutorDelivery.ResponseDeliveryRunnable(request, response, (Runnable)null));
}
private class ResponseDeliveryRunnable implements Runnable {
private final Request mRequest;
private final Response mResponse;
private final Runnable mRunnable;
public ResponseDeliveryRunnable(Request request, Response response, Runnable runnable) {
this.mRequest = request;
this.mResponse = response;
this.mRunnable = runnable;
}
public void run() {
if(this.mRequest.isCanceled()) {
this.mRequest.finish("canceled-at-delivery");
} else {
if(this.mResponse.isSuccess()) {
this.mRequest.deliverResponse(this.mResponse.result);
} else {
this.mRequest.deliverError(this.mResponse.error);
}
if(this.mResponse.intermediate) {
this.mRequest.addMarker("intermediate-response");
} else {
this.mRequest.finish("done");
}
if(this.mRunnable != null) {
this.mRunnable.run();
}
}
}
}
}
我们看一下postResponse和postError的代码,其中Request.markDeliveried的代码如下,可以看出是设置了一个标记deliver成功的标志位
public void markDelivered() {
this.mResponseDelivered = true;
}
addMarker是用于打印调试用的,这里比较次要,就略过了,mResponsePoster是一个Executor,这里使用Executor而不是直接使用Handler我暂且想成是为了提供代码的可扩展性
从ResponseDelivery的run方法中可以看出,如果Request被取消了(通过Request.cancel()取消请求,在Activity销毁的时候取消网络请求是一个比较好的编码习惯,否则如果Request成功了调用Request.deliverResponse(request.result),而deliverResponse是一个抽象方法,由开发者自行实现,默认实现中这个方法是在主线程中执行,所以我们一般在这个方法中回调UI监听器。除了success还有一个intermediate中间状态,如果不是中间状态就会调用Request.finish(),意味着请求结束,Request.finish()的代码先是将当前request从RequestQueue中移除,然后检查是否需要打印调试信息,还有如果请求时间超过了3s的话就会在打印这个请求。
最后还有一个runnable钩子,我们找一下哪里使用到了这个钩子,只有直接调用ResponseDelivery.postResponse(request, response, runnable)runnable才有可能不是null,ReponseDelivery只在NetworkDispatcher和 CacheDispatcher中使用到了,其中NetworkDispatcher没有使用这个方法,只在CacheDispatcher中一处使用到了,代码如下
if(entry.refreshNeeded()) {
e.addMarker("cache-hit-refresh-needed");
e.setCacheEntry(entry);
response.intermediate = true;
this.mDelivery.postResponse(e, response, new Runnable() {
public void run() {
try {
CacheDispatcher.this.mNetworkQueue.put(e);
} catch (InterruptedException var2) {
;
}
}
});
}
这里runnable是将需要刷新的缓存(注意这时缓存已经命中了)重新加入mNetworkQueue队列中,具体会在后面讲解,通过添加一个runnable钩子也使代码的灵活性提高了很多
理解了上面关于CacheDispatcher构造参数的介绍,CacheDispatcher就相对好理解了,我们先任性地看一下CacheDispatcher有哪些字段
private static final boolean DEBUG;
private final BlockingQueue<Request> mCacheQueue;
private final BlockingQueue<Request> mNetworkQueue;
private final Cache mCache;
private final ResponseDelivery mDelivery;
private volatile boolean mQuit = false;
中间的四个我们已经详细了解过了,DEBUG很明显是调试信息打印的开关,mQuit也很明显,注意这里使用volatile关键字来进行线程同步,关于volatile可以查看这篇文章
quit方法的源码,比较简单,这里就不解释了
public void quit() {
this.mQuit = true;
this.interrupt();
}
run源码:
public void run() {
if(DEBUG) {
VolleyLog.v("start new dispatcher", new Object[0]);
}
Process.setThreadPriority(10);
this.mCache.initialize();
while(true) {
while(true) {
while(true) {
while(true) {
try {
while(true) {
final Request e = (Request)this.mCacheQueue.take();
e.addMarker("cache-queue-take");
if(e.isCanceled()) {
e.finish("cache-discard-canceled");
} else {
Entry entry = this.mCache.get(e.getCacheKey());
if(entry == null) {
e.addMarker("cache-miss");
this.mNetworkQueue.put(e);
} else if(entry.isExpired()) {
e.addMarker("cache-hit-expired");
e.setCacheEntry(entry);
this.mNetworkQueue.put(e);
} else {
e.addMarker("cache-hit");
Response response = e.parseNetworkResponse(new NetworkResponse(entry.data, entry.responseHeaders));
e.addMarker("cache-hit-parsed");
if(entry.refreshNeeded()) {
e.addMarker("cache-hit-refresh-needed");
e.setCacheEntry(entry);
response.intermediate = true;
this.mDelivery.postResponse(e, response, new Runnable() {
public void run() {
try {
CacheDispatcher.this.mNetworkQueue.put(e);
} catch (InterruptedException var2) {
;
}
}
});
} else {
this.mDelivery.postResponse(e, response);
}
}
}
}
} catch (InterruptedException var4) {
if(this.mQuit) {
return;
}
}
}
}
}
}
}
mCache的实际类型为DiskBaseCache,DiskBaseCache的intialize方法是解析出中硬盘中cache的网络的key(url)和CacheHeader(在后面解释HttpHeaderParser时解释),详细不解释
(我很好奇为什么这里用了那么多个while(true),难道while(true)四次会召唤神龙...不应该是七次吗)
addMarker方法都是用于打印调试信息的,这里的流程是先检测Request是否已经被取消了,如果没有被取消检查是否缓存命中,如果没有命中直接加入到网络请求队列中(mNetworkQueue)。如果过期了也重新加入网络请求队列中,Cache.Entry中过期检测的代码如下
public boolean isExpired() {
return this.ttl < System.currentTimeMillis();
}
ttl表示过期时间(time to live),由http协议中的cache-control头或expires计算出来的,代码如下(HttpHeaderParser中)
if(hasCacheControl) {
softExpire = now + maxAge * 1000L;
} else if(serverDate > 0L && serverExpires >= serverDate) {
softExpire = now + (serverExpires - serverDate);
}
serverDate字段对应Http响应头中的Date头部,serverExpires对应Expires头部,maxAge对应cache-control头部中的max-age属性
在缓存命中后会回调子类实现的parseNetworkResponse方法,我们先看一下entry.refreshNeeded方法的代码
public boolean refreshNeeded() {
return this.softTtl < System.currentTimeMillis();
}
softTtl默认等于ttl,我们可以从HttpHeaderParser的代码中写出
var20.softTtl = softExpire;
var20.ttl = var20.softTtl;
上面说过ttl等于根据http协议计算出来的过期时间,从refreshNeeded的代码可以看出softTtl是用于再一次检测是否过期的,如果过期了就重新提交一个网络请求,从下面NetworkDispatcher的代码可以看出如果服务器返回的是304 NOT Modified,就不会重新deliver response,否则会重新deliver一次response
4.NetworkDispatcher
下面我们来看一下NetworkDispatcher,代码如下:
public class NetworkDispatcher extends Thread {
private final BlockingQueue<Request> mQueue;
private final Network mNetwork;
private final Cache mCache;
private final ResponseDelivery mDelivery;
private volatile boolean mQuit = false;
public NetworkDispatcher(BlockingQueue<Request> queue, Network network, Cache cache, ResponseDelivery delivery) {
this.mQueue = queue;
this.mNetwork = network;
this.mCache = cache;
this.mDelivery = delivery;
}
public void quit() {
this.mQuit = true;
this.interrupt();
}
public void run() {
Process.setThreadPriority(10);
while(true) {
Request request;
while(true) {
try {
request = (Request)this.mQueue.take();
break;
} catch (InterruptedException var4) {
if(this.mQuit) {
return;
}
}
}
try {
request.addMarker("network-queue-take");
if(request.isCanceled()) {
request.finish("network-discard-cancelled");
} else {
if(VERSION.SDK_INT >= 14) {
TrafficStats.setThreadStatsTag(request.getTrafficStatsTag());
}
NetworkResponse e = this.mNetwork.performRequest(request);
request.addMarker("network-http-complete");
if(e.notModified && request.hasHadResponseDelivered()) {
request.finish("not-modified");
} else {
Response response = request.parseNetworkResponse(e);
request.addMarker("network-parse-complete");
if(request.shouldCache() && response.cacheEntry != null) {
this.mCache.put(request.getCacheKey(), response.cacheEntry);
request.addMarker("network-cache-written");
}
request.markDelivered();
this.mDelivery.postResponse(request, response);
}
}
} catch (VolleyError var5) {
this.parseAndDeliverNetworkError(request, var5);
} catch (Exception var6) {
VolleyLog.e(var6, "Unhandled exception %s", new Object[]{var6.toString()});
this.mDelivery.postError(request, new VolleyError(var6));
}
}
}
private void parseAndDeliverNetworkError(Request<?> request, VolleyError error) {
error = request.parseNetworkError(error);
this.mDelivery.postError(request, error);
}
}
有一个代码细节是run方法里面只在取Request的时候检测是否quit了,原因应该是在执行网络请求时候quit的话情况会很复杂
首先检测请求是否已经被取消了,然后如果当前SDK版本大于14就TrafficStats.setThreadStatsTag(request.getTrafficStatsTag()),request.getTrafficStatsTag()返回url的host部分的 哈希值,这个主要用于配合ddms的网络流量检测工具(https://developer.android.com/tools/help/am-network.html)来监控流量使用,如下所示,我测试时用的host是120.26.65.167,hashcode后的十六进制刚好是0xb73c64ee,如下
然后是执行网络请求,网络请求执行的逻辑是封装在Network类里面的,从篇幅上考虑我们就不详讲了
然后如果是response.notModified(服务器返回304)并且已经deliver过(对应CacheDispatcher最后我们说的refresh的情况),就直接Request.finish,后面的代码比较简单,就不解释了
5.RequestQueue的其他方法和字段
RequestQueue的所有字段如下
private AtomicInteger mSequenceGenerator;
private final Map<String, Queue<Request>> mWaitingRequests;
private final Set<Request> mCurrentRequests;
private final PriorityBlockingQueue<Request> mCacheQueue;
private final PriorityBlockingQueue<Request> mNetworkQueue;
private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;
private final Cache mCache;
private final Network mNetwork;
private final ResponseDelivery mDelivery;
private NetworkDispatcher[] mDispatchers;
private CacheDispatcher mCacheDispatcher;
mSequenceGenerator是用来生成Request的序列号的,mWaitingRequests和mCurrentRequest的作用我们在后面解析的时候可以发现,其他的之前都解释过了,这里不再赘述
还有其他几个方法,cancelAll(),cancelAll(filter),add(request),finish(request),cancelAll的意思和代码都比较简单,这里就不解释了,下面我们来看看add代码
public Request add(Request request) {
request.setRequestQueue(this);
Set var2 = this.mCurrentRequests;
synchronized(this.mCurrentRequests) {
this.mCurrentRequests.add(request);
}
request.setSequence(this.getSequenceNumber());
request.addMarker("add-to-queue");
if(!request.shouldCache()) {
this.mNetworkQueue.add(request);
return request;
} else {
Map var7 = this.mWaitingRequests;
synchronized(this.mWaitingRequests) {
String cacheKey = request.getCacheKey();
if(this.mWaitingRequests.containsKey(cacheKey)) {
Object stagedRequests = (Queue)this.mWaitingRequests.get(cacheKey);
if(stagedRequests == null) {
stagedRequests = new LinkedList();
}
((Queue)stagedRequests).add(request);
this.mWaitingRequests.put(cacheKey, stagedRequests);
if(VolleyLog.DEBUG) {
VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", new Object[]{cacheKey});
}
} else {
this.mWaitingRequests.put(cacheKey, (Object)null);
this.mCacheQueue.add(request);
}
return request;
}
}
}
当Request不需要缓存的时候(根据Request的mShouldCache判断,默认为true,我们可以设置为false让request不缓存),就直接将请求放到网络请求队列中执行,如果需要缓存,就将请求放到缓存队列中先查找缓存然后在缓存未命中或过期时重新提交网络请求,如果我们知道我们的API服务器并没有什么缓存控制,比如Apache默认返回Expires为1981年的某个日期,我们就可以是指request为不缓存,这样就可以提高一点网络请求的速度,因为缓存线程只有一个,相当于需要串行执行一段根本不需要的代码
从上面我们可以看出mWaitingRequests是用来存放需要检查缓存的请求的,主要目的是延迟提交开发者短时间内重复提交的需要缓存的网络请求,以免发重复的网络请求。为了解释这点,我们可以假想开发者同时提交了两个相同的都需要缓存并且当前没有缓存的网络请求t1、t2,假设t1在t2前先add,由add的代码可知,t1的cacheKey会记录到mWaitingRequest中并同时添加到mCacheQueue中,add(t2)时,mWatingRequest中记录了t1的cacheKey,因为t1和t2是相同的网络请求,所以t2会被添加到mWaitingRequest中,并且不会添加到mCacheQueue中,在t1完成后,检查mWaitingRequest发现存在相同的网络请求t2,这时才将t2添加到mCacheQueue中,因为t1在NetworkDispatcher中执行结束后已经被记录到cache中,所以这个时候t2请求会命中。如果没有设计mWaitingRequests,这时候t1和t2都会发一个网络请求,这就造成了浪费
finish的代码如下
void finish(Request request) {
Set var2 = this.mCurrentRequests;
synchronized(this.mCurrentRequests) {
this.mCurrentRequests.remove(request);
}
if(request.shouldCache()) {
Map var7 = this.mWaitingRequests;
synchronized(this.mWaitingRequests) {
String cacheKey = request.getCacheKey();
Queue waitingRequests = (Queue)this.mWaitingRequests.remove(cacheKey);
if(waitingRequests != null) {
if(VolleyLog.DEBUG) {
VolleyLog.v("Releasing %d waiting requests for cacheKey=%s.", new Object[]{Integer.valueOf(waitingRequests.size()), cacheKey});
}
this.mCacheQueue.addAll(waitingRequests);
}
}
}
}
volley中还有一些其他的东西比如说ImageLoader我还没有深入研究过代码,计划会在近期研究一些
以上是关于Volley源码解析——从实现角度深入剖析volley的主要内容,如果未能解决你的问题,请参考以下文章