简介
- 关于Volley封装性跟实用性是毋庸置疑的,本篇文章是争对上一篇文章
Volley – 基本用法做出比较详细的过程分析,分析Volley请求的流程,缓存的策略,工作线程的执行分配,接口回调的机制,代码的封装等相关进行分析,涉及到Volley的相关类有Request、Response、NetworkDispatcher、CacheDispatcher、Cache、Network等。 - 本篇文章通过提问方式展示上面提及的相关知识点,你可以当成一次复习,也可以当成一次面试,ヽ(^0^)ノ一次不正式的面试,哈哈,接下来开始吧。
- 现在还写了关于Volley的图片处理源码分析,分析关于Volley对图片的压缩,请求的处理等Volley – 图片处理方式源码分析
问题
- 对于请求结果(包括错误结果),是怎么被传递到UI线程,也就是说怎么回调到了Response.Listener、Response.ErrorListener接口的。
- 请求(Request)是如何得到结果(Response)的。
- Volley缓存机制
- ……
解答问题一:请求结果怎么回调到Response.Listener、Response.ErrorListener接口
咚,看到ResponseDelivery源码
public interface ResponseDelivery {
/**
* Parses a response from the network or cache and delivers it.
*/
public void postResponse(Request<?> request, Response<?> response);
/**
* Parses a response from the network or cache and delivers it. The provided
* Runnable will be executed after delivery.
*/
public void postResponse(Request<?> request, Response<?> response, Runnable runnable);
/**
* Posts an error for the given request.
*/
public void postError(Request<?> request, VolleyError error);
}
发现它是一个接口,用于传送结果的
看一下它的子类ExecutorDelivery
/**
* Delivers responses and errors.
*/
public class ExecutorDelivery implements ResponseDelivery {
/** Used for posting responses, typically to the main thread. */
private final Executor mResponsePoster;
/**
* Creates a new response delivery interface.
* @param handler {@link Handler} to post responses on
*/
public ExecutorDelivery(final Handler handler) {
// Make an Executor that just wraps the handler.
mResponsePoster = new Executor() {
@Override
public void execute(Runnable command) {
handler.post(command);
}
};
}
/**
* Creates a new response delivery interface, mockable version
* for testing.
* @param executor For running delivery tasks
*/
public ExecutorDelivery(Executor executor) {
mResponsePoster = executor;
}
@Override
public void postResponse(Request<?> request, Response<?> response) {
postResponse(request, response, null);
}
@Override
public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
request.markDelivered();
request.addMarker("post-response");
mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
}
@Override
public void postError(Request<?> request, VolleyError error) {
request.addMarker("post-error");
Response<?> response = Response.error(error);
mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, null));
}
// 还有一个内部类ResponseDeliveryRunnable下面讲
...
}
通过构造函数,
public ExecutorDelivery(final Handler handler) {
// Make an Executor that just wraps the handler.
mResponsePoster = new Executor() {
@Override
public void execute(Runnable command) {
handler.post(command);
}
};
}
可以发现ExecutorDelivery成员变量Executor的执行操作是通过handler.post(command)将command操作传递到与Handler的相关联的线程去执行。而ExecutorDelivery的三个post操作实际上是调用mResponsePoster.execute(command)。因此也许能够猜到构造函数中的Handler极有可能是关联着UI线程,查找一下ExecutorDelivery是在哪里构造即可证实,通过RequestQueue的构造函数可以发现
public RequestQueue(Cache cache, Network network, int threadPoolSize) {
this(cache, network, threadPoolSize,new ExecutorDelivery(new Handler(Looper.getMainLooper())));
}
有了一点思路,那么通过UI线程Handler做什么呢,通过ExecutorDelivery的三个Post方法可以发现,传递的都是其内部类ResponseDeliveryRunnable的实例,看一下其源码:
/**
* A Runnable used for delivering network responses to a listener on the
* main thread.
*/
@SuppressWarnings("rawtypes")
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) {
mRequest = request;
mResponse = response;
mRunnable = runnable;
}
@SuppressWarnings("unchecked")
@Override
public void run() {
// If this request has canceled, finish it and don't deliver.
if (mRequest.isCanceled()) {
mRequest.finish("canceled-at-delivery");
return;
}
// Deliver a normal response or error, depending.
if (mResponse.isSuccess()) {
mRequest.deliverResponse(mResponse.result);
} else {
mRequest.deliverError(mResponse.error);
}
// If this is an intermediate response, add a marker, otherwise we're done
// and the request can be finished.
if (mResponse.intermediate) {
mRequest.addMarker("intermediate-response");
} else {
mRequest.finish("done");
}
// If we have been provided a post-delivery runnable, run it.
if (mRunnable != null) {
mRunnable.run();
}
}
}
可以发现ResponseDeliveryRunnable有三个成员变量,分别是Request,Response和Runnable,也许你会想这里的Runnable有什么用,在哪被使用了,下面再揭晓,重点看一下其run方法,发现它的逻辑是通过判断request是否取消,结果是否成功,来看是否执行mRequest.deliverResponse(mResponse.result);和mRequest.deliverError(mResponse.error);,问题来了上面这两个方法是干什么的??
查看Request的代码,
/**
* Subclasses must implement this to perform delivery of the parsed
* response to their listeners. The given response is guaranteed to
* be non-null; responses that fail to parse are not delivered.
* @param response The parsed response returned by
* {@link #parseNetworkResponse(NetworkResponse)}
*/
abstract protected void deliverResponse(T response);
/**
* Delivers error message to the ErrorListener that the Request was
* initialized with.
*
* @param error Error details
*/
public void deliverError(VolleyError error) {
if (mErrorListener != null) {
mErrorListener.onErrorResponse(error);
}
}
发现deliverResponse是一个抽象方法,而deliverError是调用mErrorListener.onErrorResponse(error)传递VolleyError,而这里的mErrorListener则是Response.ErrorListener接口。
到这里有没有一种恍然大悟的感觉,你应该猜到deliverResponse是通过调用Response.Listener接口传递结果的,那么继续查看Request子类中方法deliverResponse的实现
@Override
protected void deliverResponse(String response) {
if (mListener != null) {
mListener.onResponse(response);
}
}
可以发现子类中该方法写法基本一样,只是结果的参数不一样而已。
现在小结一下,一个Request是怎么在Response.Listener、Response.ErrorListener接口得到结果的
通过ExectorDelivery.post...操作,在UI线程中执行request.deliverResponse、request.deliverError方法,
而这两个方法有通过调用Request其Response.Listener、Response.ErrorListener将结果传递出来。
那么问题又来了,ExectorDelivery.post…操作在哪里被执行,通过RequestQueue的成员mDelivery发现,在构造Dispatcher(NetworkDispatcher、CacheDispatcher)时mDelivery被传递过去,Dispatcher是一个工作线程,不断获取RequesetQueue队列中的Requeset,并执行得到结果,再通过Delivery返回数据到UI线程。
问题又来了又来了,工作线程怎么得到Request的结果Response。详情请看下面问题二。
解答问题二:如何得到Request的结果Response
这里涉及到的相关类有Network、跟NetWorkResponse,Cache
看到这里应该明白Network通过HttpStack将Request转换成HttpResponse,再将HttpResponse中的数据包装成NetworkResponse
而这里的HttpStack的两个子类其实就是我们使用的HttpClient和HttpURLConnection。(下篇VolleyHTTP篇时讲解)
那么NetwoResponse是什么?
先来了解一下Cache
可以看到Entry是存储缓存数据的实体类,而NetworkResponse是存储网络数据结果的实体类
回到原来的问题,如何得到Request的结果Response,
现在有了NetworkResponse,怎么实现呢?
看一下Requset源码,可以发现
/**
* Subclasses must implement this to parse the raw network response
* and return an appropriate response type. This method will be
* called from a worker thread. The response will not be delivered
* if you return null.
* @param response Response from the network
* @return The parsed response, or null in the case of an error
*/
abstract protected Response<T> parseNetworkResponse(NetworkResponse response);
将NetworkResponse解析成Response是个抽象方法,也就是说在结果数据已经有了的前提下(NetworkResponse 对象的data数据,byte[]形式),对于具体怎么样的请求,那就有其自己去解析。比如StringRequest的parseNetworkResponse方法
parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
这样子我们也就可以自定义Request。
问题三:Volley缓存机制:CacheDispatcher及DiskBaseCache
CacheDispatcher是一个线程类,而上面讲解的都是NetworkDispatcher这个线程类执行的操作:将Request的结果Response。
这里提一下Volley在分配时默认分配有一个CacheDispatcher缓存操作线程和四个NetworkDispatcher网络操作线程。
而NetworkDispatcher线程的工作是不断的读取RequsetQueue请求队列中的Request并获去结果。
那么CacheDispatcher是干嘛的呢??
根据前面两个问题的解决思路,先看一下RequestQueue中的CacheDispatcher,发现其构造函数与NetworkDispatcher的差别在于CacheDispatcher多了一个mCacheQueue,其他对象完全一样。
那么这个mCacheQueue是什么?应该是存储正在缓存的Request的缓存队列。
主要看一下run方法,看其是怎么的缓存机制
@Override
public void run() {
if (DEBUG) VolleyLog.v("start new dispatcher");
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
// Make a blocking call to initialize the cache.
mCache.initialize();
Request<?> request;
while (true) {
// release previous request object to avoid leaking request object when mQueue is drained.
request = null;
try {
// Take a request from the queue.
request = mCacheQueue.take();
} catch (InterruptedException e) {
// We may have been interrupted because it was time to quit.
if (mQuit) {
return;
}
continue;
}
try {
request.addMarker("cache-queue-take");
// If the request has been canceled, don't bother dispatching it.
if (request.isCanceled()) {
request.finish("cache-discard-canceled");
continue;
}
// Attempt to retrieve this item from cache.
Cache.Entry entry = mCache.get(request.getCacheKey());
if (entry == null) {
request.addMarker("cache-miss");
// Cache miss; send off to the network dispatcher.
mNetworkQueue.put(request);
continue;
}
// If it is completely expired, just send it to the network.
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");
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.
mDelivery.postResponse(request, response);
} else {
// 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.
final Request<?> finalRequest = request;
mDelivery.postResponse(request, response, new Runnable() {
@Override
public void run() {
try {
mNetworkQueue.put(finalRequest);
} catch (InterruptedException e) {
// Not much we can do about this.
}
}
});
}
} catch (Exception e) {
VolleyLog.e(e, "Unhandled exception %s", e.toString());
}
}
}
根据其run方法可以看到,其缓存原理:
- 不断在缓存队列mCacheQueue中获取Request。
- 如果无缓存(为null或者过时),则添加到mNetworkQueue中等待从服务器中获取数据
- 如果有缓存,判断是否需要刷新数据
- 不需要则直接通过没Delivery.post..方法传结果递到UI线程
- 需要刷新的话,则先通过Delivery.post…方法传递结果到UI线程,并且将请求添加到没NetworkQueue中,即通过Delivery.post..( .. , .. , Runnable runnable)方法,这时候这个方法在这里调用,突然觉得这个方法的神奇
在这里也许你会发现通过 mCache.get(request.getCacheKey())获取缓存,其实mCache的真实对象是Cache的子类DiskBaseCache,其功能类似于LruCache,内部是通过LinkedHashMap来保存缓存文件数据,读取时对缓存文件进行读取操作,这里就不多讲。
前天晚上写项目写到3点,昨晚回来写这篇文章写到了快3点,而且越写越精神,哈哈,最重要的是坚持。
现在还写了关于Volley的图片处理源码分析,有兴趣的博友可以看看Volley – 图片处理方式源码分析
最后,如哪里不足或者分析错误,非常欢迎指正,谢谢。