Volley是Google2013年I/O大会发布的一个轻量级的网络请求封装库,适用于多而小的网络请求,原因是它的缓存机制使用的是大小仅为5M的DiskBasedCache缓存。它的整个请求的分发流程,是通过RequestQueue来下发给CacheDispatcher和NetworkDispatcher,具体的流程,如有不懂可参考Volley解析(一)– 源码进行了解。本篇文章的主题是分析Volley的架构,主要是涉及一些设计模式的使用,结合Google官方的图做一下分析:
我们还是以发送一个简单的StringRequest请求为例,示例代码如下:
// Instantiate the RequestQueue.
RequestQueue queue = Volley.newRequestQueue(this);
String url ="http://www.google.com";
// Request a string response from the provided URL.
StringRequest stringRequest = new StringRequest(Request.Method.GET, url,
new Response.Listener() {
@Override
public void onResponse(String response) {
// Display the first 500 characters of the response string.
mTextView.setText("Response is: "+ response.substring(0,500));
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
mTextView.setText("That didn't work!");
}
});
// Add the request to the RequestQueue.
queue.add(stringRequest);
以Volley.newRequestQueue(this);
为开头实例化了一个RequestQueue对象,Volley类实际上是对RequestQueue的实例化和初始化作了一个简单的封装,里面是多个newRequestQueue重载函数,函数返回一个RequestQueue对象,方便用户的使用。虽然Volley里面有多个重载的newRequestQueue函数,不过最终都会调用同一个函数,如下:
private static RequestQueue newRequestQueue(Context context, Network network) {
//缓存文件
File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
//请求队列
RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
//开始执行请求
queue.start();
return queue;
}
在这里除了调用RequestQueue的构造函数,还调用了它的start函数,开始执行请求的分发,newRequestQueue将这两个操作进行了绑定,防止开发者遗忘,降低开发者的使用成本,你要认为它是一个简单的封装也可以,认为它是对于外观模式的一个简单运用也可以。
外观模式正规定义:要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行。
适用场景:
(1)为一个复杂子系统提供一个简单接口;
(2)当需要构建一个层次结构的子系统时,使用Facade模式定义子系统中每层的入口点
个人理解:当一个系统是由多个部件组合而成,为了降低使用者的成本,提高用户体验,将多个部件在一个门户类中进行整合,从而使用开发者不用记那么多的API。
下面就是Volley中的核心类了,它是其它重要组件的生命起源 – RequestQueue
,在整个Volley的体系中最主要的组件就是网络分发器和缓存分发器(两种线程),它们是负责分发所有请求的,只不过是通过不同的渠道,两者既相互独立,又息息相关,其实只要看上面一个Volley请求的生命周期图就知道:
(1)一个新的请求优先是添加到缓存分发器的(默认情况,在你不调用setShouldCache
设置禁止缓存的前提下,因为默认值是true)。
(2)当请求处于缓存分发器中时,判断缓存是否命中,命中直接解析缓存结果,然后回调相应。这里其实是运用到了模板方法模式,我们可以看一下。从CacheDispatcher的processRequest函数入手:
@VisibleForTesting
void processRequest(final Request> request) throws InterruptedException {
request.addMarker("cache-queue-take");
// 请求取消,直接finish即可
if (request.isCanceled()) {
request.finish("cache-discard-canceled");
return;
}
// 从缓存中检索缓存实体对象
Cache.Entry entry = mCache.get(request.getCacheKey());
if (entry == null) {
//缓存未命中,说明没有缓存过,将当前请求添加到网络请求队列中
request.addMarker("cache-miss");
if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
mNetworkQueue.put(request);
}
return;
}
// 缓存命中,但是过期,将请求添加到网络请求队列中
if (entry.isExpired()) {
request.addMarker("cache-hit-expired");
request.setCacheEntry(entry);
if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
mNetworkQueue.put(request);
}
return;
}
// 缓存命中; 解析数据返回给请求
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;
if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
// 交付响应,同时执行Runnable任务,将刷新缓存的请求添加到网络请求对列中
mDelivery.postResponse(
request,
response,
new Runnable() {
@Override
public void run() {
try {
mNetworkQueue.put(request);
} catch (InterruptedException e) {
// Restore the interrupted status
Thread.currentThread().interrupt();
}
}
});
} else {
//等待状态,直接交付缓存
mDelivery.postResponse(request, response);
}
}
}
在上面的代码中重点在于request.parseNetworkResponse和mDelivery.postResponse,从调用函数的函数名其实很容易就知道它们的目的,分别是解析网络响应结果和回调函数,解析网络响应结果是蛮容易理解的,但是这个mDelivery.postResponse是怎么回调到我们传入的Response.Listener< String>中的呢,我们看一下mDelivery,发现它是一个ExecutorDelivery对象,进入ExecutorDelivery的postResponse函数,发现其实调用的是mResponsePoster的execute函数,mResponsePoster是Executor接口的实现类,它的参数是一个Runnable,我们传入的Runnable为null,但是由于ExecutorDelivery中会对Request,Response, Runnable进行一次封装,即使Runnable为空,看一下封装类ResponseDeliveryRunnable的run方法:
@SuppressWarnings("unchecked")
@Override
public void run() {
if (mRequest.isCanceled()) {
mRequest.finish("canceled-at-delivery");
return;
}
if (mResponse.isSuccess()) {
//回调Response的onResponse
mRequest.deliverResponse(mResponse.result);
} else {
//回调Response的ErrorListener
mRequest.deliverError(mResponse.error);
}
//根据是否是软过期做不同的逻辑处理
if (mResponse.intermediate) {
mRequest.addMarker("intermediate-response");
} else {
mRequest.finish("done");
}
// 在软过期情况下,如果Runnable为不null,会请求网络,刷新数据
if (mRunnable != null) {
mRunnable.run();
}
}
上面回调了Request的deliverResponse,或者是deliverError,由于在抽象父类Request中deliverResponse是抽象,由具体子类来实现,我们看一下StringRequest:
@Override
protected void deliverResponse(String response) {
Response.Listener listener;
synchronized (mLock) {
listener = mListener;
}
if (listener != null) {
listener.onResponse(response);
}
}
果然如我们所预料的一样,回调了Response的Listener< T>函数,这里的T为String,到这里其实可以很明显的看出模板方法模式的使用。
模板方法模式正规定义:当遇到一个问题,我们知道解决该问题所需的关键步骤,并确定了这些步骤的执行顺序,但是,某些步骤的具体实现是未知的,这类问题的解决方案就是–模板方法模式。简单来说就是:流程封装
适用场景:
(1)多个子类有公有的方法,并且逻辑基本相同
(2)重要、复杂的算法,可以把核心算法设计为模板方法,周边的相关细节功能则由各个子类实现
(3)重构时经常使用,把相同的代码抽取到父类中,然后通过钩子函数约束其行为
个人理解:就是解决一个问题,套路是固定的,但是具体怎么操作,因人而异。比如我肚子饿了,想自己蒸馒头吃,买馒头、蒸,这是少不了的步骤,但是我可以自己买,可让别人代买,蒸的话,我可以那个蒸笼蒸,可以那个盘子放在水里蒸,方式多种多样。
根据我们的定义,结合上面分析的代码逻辑,你有没有发现模板方法的影子呢?从缓存中取出缓存的请求响应,调用Request的parseNetwordResponse来解析结果,然后后面调用Request的deliverResponse函数,这两个函数在Request是抽象的,具体由子类来实现,因为Request不知道你结果要解析成啥样,也不知道结果是要回调给你自定义的监听器,还是系统监听器,索性就交由子类来实现。如果对模板方法模式有兴趣的话,可以看一下AsyncTask–模板模式的应用。
设计模式重在思想,并不是一成不变的,不要绕到死角里
以上涉及到的一些设计模式的个人理解,因为是自己平常看书的理解,如果错误,还请不吝赐教。
本文中的请求的生命周期和示例代码,可以参考Google官网,需要FQ。