版权声明:本文为LooperJing原创文章,转载请注明出处!
在上一篇,流行网络库第(一)篇---Volley用法解析中了解了Volley的基本使用,但是对于Volley可能有些朋友还不是特别清楚。我也是结合源码与其他人的分析,才真正弄清楚Volley的工作原理。
先看一个Demo,Volley十几行代码就完成了一次HTPP请求,我们看看这段代码内部究竟发生了什么?
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
RequestQueue mQueue = Volley.newRequestQueue(MainActivity.this);
mQueue.add(getStringRequest());
}
public StringRequest getStringRequest() {
return new StringRequest("https://suggest.taobao.com/sug?code=utf-8&q=beizi",
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
Log.e(getClass().getName(), response);
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
Log.e(getClass().getName(), error.getMessage());
}
}
);
}
}
首先第一句代码,创建一个消息队列
RequestQueue mQueue = Volley.newRequestQueue(MainActivity.this);
创建消息队列的逻辑,被封装在Volley中,以静态方法暴露出来,供外部调用,总共有3个重载方法,上面使用的是第一个方法(只有一个参数),第一个方法会调用第二个方法(两个参数),第二个方法会调用第三个方法(三个参数)。
public static RequestQueue newRequestQueue(Context context) {
return newRequestQueue(context, null);
}
public static RequestQueue newRequestQueue(Context context, HttpStack stack){
return newRequestQueue(context, stack, -1);
}
public static RequestQueue newRequestQueue(Context context, int maxDiskCacheBytes) {
return newRequestQueue(context, null, maxDiskCacheBytes);
}
现在看第三个构造方法中做了什么?,这是第一个重点。
public static RequestQueue newRequestQueue(Context context, HttpStack stack, int maxDiskCacheBytes) {
//应用的默认缓存目录,DEFAULT_CACHE_DIR值是volley,即文件cacheDir的文件名是volley
File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
// 通过应用程序的包名和版本信息来完善userAgent字符串
String userAgent = "volley/0";
try {
String packageName = context.getPackageName();
PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
userAgent = packageName + "/" + info.versionCode;
} catch (NameNotFoundException e) {
}
//如果stack为NULL,就建立一个默认的HttpStack
if (stack == null) {
if (Build.VERSION.SDK_INT >= 9) {
//版本在2.3之后,创建HurlStack,其内部用了HttpURLConnection
stack = new HurlStack();
} else {
//版本在2.3之前,创建HttpClientStack,其内部用了HttpClient
stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
}
}
//将stack传到Network中,Network是负责网络请求的
Network network = new BasicNetwork(stack);
RequestQueue queue;
//创建RequestQueue时要传入一个Cache对象,用来做磁盘缓存
if (maxDiskCacheBytes <= -1) {
// No maximum size specified
queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
} else{
// Disk cache size specified
queue = new RequestQueue(new DiskBasedCache(cacheDir, maxDiskCacheBytes), network);
}
// 开始请求队列
queue.start();
return queue;
}
注释都写清楚了,主要就是在创建请求队列的时候,做一些初始化的工作。
- 1、创建应用缓存目录,这个目录是volley;
- 2、 通过应用程序的包名和版本信息来完善userAgent字符串,这个userAgent只有在使用HttpClientStack才有用到;
- 3、如果用户没有建立stack,就建立一个默认的Stack,这里根据版本号判断使用 HttpURLConnection还是使用HttpClient创建Stack;
- 4、创建Stack的目的是作为参数创建Network,Network是负责网络请求的;
- 5、有了Network和DiskBasedCache,最后创建消息队列并且开启消息队列。
到这里你可能还不知道,做这些初始化操作的目的是什么,继续看。
看到最后返回的是RequestQueue对象,我们现在看RequestQueue是怎么工作的。先看看RequestQueue里面几个重要的成员变量。
public class RequestQueue {
//等待队列,当之前已经存在了与本次请求相同的请求时,会把相同的请求放在队列中,以URL为关键字(key)
private final Map<String, Queue<Request<?>>> mWaitingRequests =
new HashMap<String, Queue<Request<?>>>();
//存放所有请求,包括已经运行的和正在等待的请求(Request)
private final Set<Request<?>> mCurrentRequests = new HashSet<Request<?>>();
//表示目前缓存的请求队列,这个队列会被传入到CacheDispatcher做判断。
private final PriorityBlockingQueue<Request<?>> mCacheQueue =
new PriorityBlockingQueue<Request<?>>();
//表示请求网络连接的队列,里面的请求都会向服务器进行请求(这才是唯一和服务器打交道的队列)。
private final PriorityBlockingQueue<Request<?>> mNetworkQueue =
new PriorityBlockingQueue<Request<?>>();
// Volley类中调用的构造函数,传入了默认的网络线程数,DEFAULT_NETWORK_THREAD_POOL_SIZE = 4(默认是4个线程)
private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;
//缓存相关
private final Cache mCache;
//网络请求相关
private final Network mNetwork;
//请求结果分发器,会把响应得到的结果发送到主线程的回调方法中,最终传递给监听器。
private final ResponseDelivery mDelivery;
//阻塞式线程数组,不断循环处理Request。
private NetworkDispatcher[] mDispatchers;
//阻塞式线程,不断循环处理Request。
private CacheDispatcher mCacheDispatcher;
//请求完成时的监听数组
private List<RequestFinishedListener> mFinishedListeners =
new ArrayList<RequestFinishedListener>();
public static interface RequestFinishedListener<T> {
public void onRequestFinished(Request<T> request);
}
}
上面的成员变量代表的意思,请看注释,现在看如何创建一个RequestQueue。这是第二个重点。
public RequestQueue(Cache cache, Network network, int threadPoolSize,
ResponseDelivery delivery) {
//初始化了network,dispatchers,delivery,cache对象,这里的cache是磁盘缓存的对象
//初始化完成后,立刻调用了start()方法
mCache = cache;
mNetwork = network;
mDispatchers = new NetworkDispatcher[threadPoolSize];
mDelivery = delivery;
}
public RequestQueue(Cache cache, Network network, int threadPoolSize) {
this(cache, network, threadPoolSize,
new ExecutorDelivery(new Handler(Looper.getMainLooper())));
}
public RequestQueue(Cache cache, Network network) {
this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);
}
public void start() {
//先停止两个阻塞式线程
stop();
//创建一个缓存线程(缓存分发器),不断的处理Request
mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
mCacheDispatcher.start();
//创建4个网络线程(网络分发器),不断的处理Request
for (int i = 0; i < mDispatchers.length; i++) {
NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
mCache, mDelivery);
mDispatchers[i] = networkDispatcher;
networkDispatcher.start();
}
}
//停止5个阻塞式线程,一个缓存线程,4个网络线程
public void stop() {
if (mCacheDispatcher != null) {
mCacheDispatcher.quit();
}
for (int i = 0; i < mDispatchers.length; i++) {
if (mDispatchers[i] != null) {
mDispatchers[i].quit();
}
}
}
现在看看,其实
RequestQueue mQueue = Volley.newRequestQueue(MainActivity.this);
这一行代码,原来做了这么多的事情,用最后两行代码来概括
RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir),network);
queue.start();
就是创建了一个RequestQueue对象,调用start对象,开启了4个线程,同时我们要记清楚,RequestQueue并不是一个简单的Queue,内部有好几个Queue,其中mCurrentRequests 存放所有请求,包括已经运行的和正在等待的请求;mCacheQueue (Request)表示目前缓存的请求队列,这个队列会被传入到CacheDispatcher做判断;mNetworkQueue 表示请求网络连接的队列,里面的请求都会向服务器进行请求(这才是唯一和服务器打交道的队列)。并且RequestQueue中还创建了5个线程,其中CacheDispatcher是缓存线程,数量为1,NetworkDispatcher是网络线程,存放在mDispatchers中,数量默认为4。
RequestQueue准备好了,现在该看看,往里面add了。这是第三个重点。
mQueue.add(getStringRequest());
add方法如下
public <T> Request<T> add(Request<T> request) {
//每个request的内部维持着一个RequestQueue,先给内部维持的RequestQueue赋值
request.setRequestQueue(this);
synchronized (mCurrentRequests) {
//mCurrentRequests中保存了所有的request,所以一上来就添加
mCurrentRequests.add(request);
}
//设置request的序列号
request.setSequence(getSequenceNumber());
// 设置request当前的真正进行的事件,标记现在是被添加到队列中了
request.addMarker("add-to-queue");
// 如果不允许有磁盘缓存,跳过mCacheQueue,直接把request添加到networkQueue中,然后去请求网络
if (!request.shouldCache()) {
// 添加到mNetworkQueue中,进行网络交互
mNetworkQueue.add(request);
return request;
}
// Insert request into stage if there's already a request with the same cache key in flight.
//mWaitingRequests保存了当前正在执行的请求与等待的请求
synchronized (mWaitingRequests) {
//这个cacheKey是请求的url和请求方式Method拼接起来的,保证了Key的唯一性
String cacheKey = request.getCacheKey();
// 判断等待队列中是否已经有这个request,如果有了就需要过滤重复请求
if (mWaitingRequests.containsKey(cacheKey)) {
// There is already a request in flight. Queue up.
// mWaitingRequests的类型是HashMap<String, Queue<Request<?>>>
Queue<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
if (stagedRequests == null) {
stagedRequests = new LinkedList<Request<?>>();
}
stagedRequests.add(request);
mWaitingRequests.put(cacheKey, stagedRequests);
if (VolleyLog.DEBUG) {
VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);
}
} else {
// Insert 'null' queue for this cacheKey, indicating there is now a request in
// flight.
mWaitingRequests.put(cacheKey, null);
mCacheQueue.add(request);
}
return request;
}
}
请注意mWaitingRequests,mWaitingRequests的类型是HashMap<String, Queue<Request<?>>>,保存的对象是 Queue<Request<?>>,其中键是cacheKey,这个cacheKey是请求的url和请求方式Method拼接起来的,理论上如果重复了,就可以认为是同一个请求,Queue<Request<?>>中保存的是request,所以当我们连续添加了3个一样的请求,那么mWaitingRequests中内部维持的Queue<Request<?>>就有3个Request。
上面分析了Volley中开了5个线程,最先开启的就是缓存线程了。
public void start() {
stop();
mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
//先开启缓存线程
mCacheDispatcher.start();
for (int i = 0; i < mDispatchers.length; i++) {
NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
mCache, mDelivery);
mDispatchers[i] = networkDispatcher;
//在开启网络线程
networkDispatcher.start();
}
}
所以我们看一下,当一个request已经添加到了mWaitingRequests中了,缓存线程mCacheDispatcher是怎么处理它的。注意CacheDispatcher接收了mCacheQueue, mNetworkQueue, mCache, mDelivery这些参数。
先看看CacheDispatcher类的成员。
public class CacheDispatcher extends Thread {
private static final boolean DEBUG = VolleyLog.DEBUG;
//缓存队列
private final BlockingQueue<Request<?>> mCacheQueue;
//网路请求队列
private final BlockingQueue<Request<?>> mNetworkQueue;
/** The cache to read from. */
private final Cache mCache;
//将响应分发到主线程
private final ResponseDelivery mDelivery;
/** Used for telling us to die. */
private volatile boolean mQuit = false;
public CacheDispatcher(
BlockingQueue<Request<?>> cacheQueue, BlockingQueue<Request<?>> networkQueue,
Cache cache, ResponseDelivery delivery) {
mCacheQueue = cacheQueue;
mNetworkQueue = networkQueue;
mCache = cache;
mDelivery = delivery;
}
}
主要看CacheDispatcher线程中的run方法,这是第四个重点。
@Override
public void run() {
if (DEBUG) VolleyLog.v("start new dispatcher");
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
// 初始化缓存区,就把之前文件中所保存的对象,一个一个读出来,保存在Map<String, CacheHeader> mEntries这个变量中
mCache.initialize();
Request<?> request;
while (true) {
// release previous request object to avoid leaking request object when mQueue is drained.
request = null;
try {
// 从cache队列中取出一个request对象
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");
// 如果这个request已经被取消了,就直接finish掉,进行下一次循环
if (request.isCanceled()) {
request.finish("cache-discard-canceled");
continue;
}
//上面缓存区已经被初始化,所以可以从缓冲区中取出一个缓存,从mCache内部维持的mEntries中取
Cache.Entry entry = mCache.get(request.getCacheKey());
if (entry == null) {
// 设置当前事件名称
request.addMarker("cache-miss");
// Cache miss; send off to the network dispatcher.
//加入到mNetworkQueue中,进行网络请求,进行下一次循环,处理下一次request
mNetworkQueue.put(request);
continue;
}
// entry不为null,但是已经完全过期了,让它重新访问网络
// If it is completely expired, just send it to the network.
if (entry.isExpired()) {
//设置当前事件名称,标记缓存命中且过期
request.addMarker("cache-hit-expired");
request.setCacheEntry(entry);
//加入到mNetworkQueue中,进行下一次循环,处理下一次request
mNetworkQueue.put(request);
continue;
}
//设置当前事件名称,标记缓存命中
request.addMarker("cache-hit");
Response<?> response = request.parseNetworkResponse(
new NetworkResponse(entry.data, entry.responseHeaders));
//设置当前事件名称,标记这个request已经被解析了
request.addMarker("cache-hit-parsed");
// 判断是否需要刷新
if (!entry.refreshNeeded()) {
// 发送到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());
}
}
}
经过一系列的操作得到的request,最终会把一个request解析成一个Response,调用的是parseNetworkResponse方法。不同的request有不同的parseNetworkResponse方法,我们最上面的Demo用的是StringRequest。
@Override
protected Response<String> parseNetworkResponse(NetworkResponse response) {
String parsed;
try {
parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
} catch (UnsupportedEncodingException e) {
parsed = new String(response.data);
}
return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
}
得到了Response之后,将这个Response交给mDelivery,mDelivery称为数据分发器,最开始由构造函数传进来的。关于数据的分发,这是第五个重点。
mDelivery.postResponse(request, response);
在RequestQueue构造中,mDelivery被初始化,是个ExecutorDelivery对象。
public RequestQueue(Cache cache, Network network, int threadPoolSize,
ResponseDelivery delivery) {
mCache = cache;
mNetwork = network;
mDispatchers = new NetworkDispatcher[threadPoolSize];
mDelivery = delivery;
}
public RequestQueue(Cache cache, Network network, int threadPoolSize) {
this(cache, network, threadPoolSize,
new ExecutorDelivery(new Handler(Looper.getMainLooper())));
}
ExecutorDelivery是个接口,继承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内部有一个Runnable,/用于分发网络的响应结果到主线程的,这是第六个重点。
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 (mRequest.isCanceled()) {
//设置当前事件名称,标记request被取消
mRequest.finish("canceled-at-delivery");
return;
}
// 分发一个的响应结果
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 {
//设置当前事件名称,标记request已经完成
mRequest.finish("done");
}
// If we have been provided a post-delivery runnable, run it.
if (mRunnable != null) {
mRunnable.run();
}
}
}
mListener,mErrorListener就是我们Demo中设置的回调接口。
@Override
protected void deliverResponse(String response) {
if (mListener != null) {
mListener.onResponse(response);
}
}
public void deliverError(VolleyError error) {
if (mErrorListener != null) {
mErrorListener.onErrorResponse(error);
}
}
现在再看那张图
其中蓝色部分代表主线程,绿色部分代表缓存线程,橙色部分代表网络线程。我们在主线程中调用RequestQueue的add()方法来添加一条网络请求,这条请求会先被加入到缓存队列当中,如果发现可以找到相应的缓存结果就直接读取缓存并解析,然后回调给主线程。如果在缓存中没有找到结果,则将这条请求加入到网络请求队列中,然后处理发送HTTP请求,解析响应结果,写入缓存,并回调主线程。
Volley工作流程总结:http://www.cnblogs.com/tianzhijiexian/p/4264468.html
- 当一个RequestQueue被成功申请后会开启一个CacheDispatcher(缓存调度器)和4个(默认)NetworkDispatcher(网络请求调度器);
- CacheDispatcher缓存调度器最为第一层缓冲,开始工作后阻塞的从缓存序列mCacheQueue中取得请求:
a. 对于已经取消了的请求,直接标记为跳过并结束这个请求
b. 全新或过期的请求,直接丢入mNetworkQueue中交由N个NetworkDispatcher进行处理
c. 已获得缓存信息(网络应答)却没有过期的请求,交由Request的parseNetworkResponse进行解析,从而确定此应答是否成功。然后将请求和应答交由Delivery分发者进行处理,如果需要更新缓存那么该请求还会被放入mNetworkQueue中 - 用户将请求Request add到RequestQueue之后:
a. 对于不需要缓存的请求(需要额外设置,默认是需要缓存)直接丢入mNetworkQueue交由N个NetworkDispatcher处理;
b. 对于需要缓存的,全新的请求加入到mCacheQueue中给CacheDispatcher处理
c. 需要缓存,但是缓存列表中已经存在了相同URL的请求,放在mWaitingQueue中做暂时雪藏,待之前的请求完毕后,再重新添加到mCacheQueue中; - 网络请求调度器NetworkDispatcher作为网络请求真实发生的地方,对消息交给BasicNetwork进行处理,同样的,请求和结果都交由Delivery分发者进行处理;
- Delivery分发者实际上已经是对网络请求处理的最后一层了,在Delivery对请求处理之前,Request已经对网络应答进行过解析,此时应答成功与否已经设定。而后Delivery根据请求所获得的应答情况做不同处理:
a. 若应答成功,则触发deliverResponse方法,最终会触发开发者为Request设定的Listener
b. 若应答失败,则触发deliverError方法,最终会触发开发者为Request设定的ErrorListener
处理完后,一个Request的生命周期就结束了,Delivery会调用Request的finish操作,将其从mRequestQueue中移除,与此同时,如果等待列表中存在相同URL的请求,则会将剩余的层级请求全部丢入mCacheQueue交由CacheDispatcher进行处理。
一个Request的生命周期:
- 通过add加入mRequestQueue中,等待请求被执行;
- 请求执行后,调用自身的parseNetworkResponse对网络应答进行处理,并判断这个应答是否成功;
- 若成功,则最终会触发自身被开发者设定的Listener;若失败,最终会触发自身被开发者设定的ErrorListener。
参考链接:
http://www.cnblogs.com/tianzhijiexian/p/4264468.html
http://blog.csdn.net/guolin_blog/article/details/17656437
http://blog.csdn.net/ttdevs/article/details/17764351
Please accept mybest wishes for your happiness and success !