Android Volley框架源码解析

来源:互联网 发布:网络信息管理办公室 编辑:程序博客网 时间:2024/05/18 00:40

经常接触Android网络编程的我们,对于Volley肯定不陌生,但我们不禁要问,对于Volley我们真的很了解吗?Volley的内部是怎样实现的?为什么几行代码就能快速搭建好一个网络请求?我们不但要知其然,也要知其所以然,抱着这样的目的,本文主要详细讲述Volley的源码,对内部流程进行详细解析。

Part 1.从RequestQueue说起

  (1)还记得搭建请求的第一步是什么吗?是新建一个请求队列,比如说这样:

RequestQueue queue = Volley.newRequestQueue(context)
  虽然表面上只是一句代码的事情,但是背后做了很多准备工作,我们追踪源码,找到Volley#newRequestQueue()方法:

/**
* Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it.
* You may set a maximum size of the disk cache in bytes.
*
* @param context A {@link Context} to use for creating the cache dir.
* @param stack An {@link HttpStack} to use for the network, or null for default.
* @param maxDiskCacheBytes the maximum size of the disk cache, in bytes. Use -1 for default size.
* @return A started {@link RequestQueue} instance.
*/
public static RequestQueue newRequestQueue(Context context, HttpStack stack, int maxDiskCacheBytes) {
File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);

String userAgent = "volley/0";try {    String packageName = context.getPackageName();    PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);    userAgent = packageName + "/" + info.versionCode;} catch (NameNotFoundException e) {} /**   * 根据不同的系统版本号实例化不同的请求类,如果版本号小于9,用HttpClient   * 如果版本号大于9,用HttpUrlConnection   */if (stack == null) {    if (Build.VERSION.SDK_INT >= 9) {        stack = new HurlStack();    } else {        // Prior to Gingerbread, HttpUrlConnection was unreliable.        // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html        stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));    }}  //把实例化的stack传递进BasicNetwork,实例化NetworkNetwork network = new BasicNetwork(stack);RequestQueue queue;if (maxDiskCacheBytes <= -1){   // No maximum size specified   //实例化RequestQueue类    queue = new RequestQueue(new DiskBasedCache(cacheDir), network);}else {   // Disk cache size specified   queue = new RequestQueue(new DiskBasedCache(cacheDir, maxDiskCacheBytes), network);} //调用RequestQueue的start()方法queue.start();return queue;

}
  首先我们看参数,有三个,实际上我们默认使用了只有一个参数context的方法,这个是对应的重载方法,最终调用的是三个参数的方法,context是上下文环境;stack代表需要使用的网络连接请求类,这个一般不用设置,方法内部会根据当前系统的版本号调用不同的网络连接请求类(HttpUrlConnection和HttpClient);最后一个参数是缓存的大小。接着我们看方法内部,这里先创建了缓存文件,然后根据不同的系统版本号实例化不同的请求类,用stack引用这个类。接着又实例化了一个BasicNetwork,这个类在下面会说到。然后到了实际实例化请求队列的地方:new RequestQueue(),这里接收两个参数,分别是缓存和network(BasicNetwork)。实例化RequestQueue后,调用了start()方法,最后返回这个RequestQueue。
  (2)我们跟着RequestQueue看看它的构造器做了哪些工作:

/**
* Creates the worker pool. Processing will not begin until {@link #start()} is called.
*
* @param cache A Cache to use for persisting responses to disk
* @param network A Network interface for performing HTTP requests
* @param threadPoolSize Number of network dispatcher threads to create
* @param delivery A ResponseDelivery interface for posting responses and errors
*/
public RequestQueue(Cache cache, Network network, int threadPoolSize,
ResponseDelivery delivery) {
mCache = cache;
mNetwork = network;
//实例化网络请求数组,数组大小默认是4
mDispatchers = new NetworkDispatcher[threadPoolSize];
mDelivery = delivery;
}

public RequestQueue(Cache cache, Network network, int threadPoolSize) {    this(cache, network, threadPoolSize,            //ResponseDelivery是一个接口,实现类是ExecutorDelivery            new ExecutorDelivery(new Handler(Looper.getMainLooper())));}public RequestQueue(Cache cache, Network network) {    this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);}

  可以看到,把传递过来的cache和network作为变量传递给了四个参数的构造器,在这里,初始化了RequestQueue的几个成员变量:mCache(文件缓存)、mNetwork(BasicNetwork实例)、mDispatchers(网络请求线程数组)、以及mDelivery(派发请求结果的接口),具体意义可看上面的注解。
  (3)构造完RequestQueue后,从(1)可知,最后调用了它的start()方法,我们来看看这个方法,RequestQueue#start():

public void start() {
stop(); // Make sure any currently running dispatchers are stopped.
// Create the cache dispatcher and start it.
mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
mCacheDispatcher.start();

    // Create network dispatchers (and corresponding threads) up to the pool size.    for (int i = 0; i < mDispatchers.length; i++) {        NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,                mCache, mDelivery);        mDispatchers[i] = networkDispatcher;        networkDispatcher.start();    }}

  首先实例化了CacheDispatcher,CacheDispatcher类继承自Thread,接着调用了它的start()方法,开始了一条新的缓存线程。接着是一个for循环,根据设置的mDispatchers数组大小来开启多个网络请求线程,默认是4条网络请求线程。
  到目前为止,Volley.newRequestQueue()方法完成了,即我们的网络请求第一步,建立请求队列完成。
  先小结一下:建立请求队列所做的工作是,创建文件缓存(默认),实例化BasicNetwork,实例化Delivery用于发送线程请求,创建一条缓存线程和四条网络请求线程(默认)并运行。

Part 2.网络请求的实现原理

  在创建完请求队列后,接着就是建立一个请求,请求的方式可以是StringRequest、JsonArrayRequest或者ImageRequest等,那么这些请求的背后原理是什么呢?我们拿最简单的StringRequest来说,它继承自Request,而Request则是所有请求的父类,所以说如果你要自定义一个网络请求,就应该继承自Request。接下来我们看看StringRequest的源码,因为不管Request的子类是什么,大体的实现思路都是一致的,所以我们弄懂了StringRequest,那么对于其他的请求类的理解是相通的。如下是StringRequest源码:

public class StringRequest extends Request {
private Listener mListener;

public StringRequest(int method, String url, Listener<String> listener,        ErrorListener errorListener) {    super(method, url, errorListener);    mListener = listener;}...@Overrideprotected void deliverResponse(String response) {    if (mListener != null) {        mListener.onResponse(response);    }}@Overrideprotected 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));}

}
  源码并不长,我们主要关注的是deliverResponse方法和parseNetworkResponse。可以看出,这两个方法都是重写的,我们翻看父类Request的对应方法,发现是抽象方法,说明这两个方法在每一个自定义的Request中都必须重写。这里简单说说这两个方法的作用。先看deliverResponse方法:它内部调用了mListener.onResponse(response)方法,而这个方法正是我们在写一个请求的时候,添加的listener所重写的onResponse方法,也就是说,响应成功后在这里调用了onResponse()方法。接着看pareNetworkResponse方法,可以看出这里主要是对response响应做出一些处理。可以对比一下不同请求类的这个方法,都会不同的,所以说,这个方法是针对不同的请求类型而对响应做出不同的处理。比如说,如果是StringRequest,则将响应包装成String类型;如果是JsonObjectRequest,则将响应包装成JsonObject。那么现在应该清楚了:对于想要得到某一种特殊类型的请求,我们可以自定义一个Request,重写这两个方法即可。
  这里小结一下:Request类做的工作主要是初始化一些参数,比如说请求类型、请求的url、错误的回调方法;而它的任一子类重写deliverResponse方法来实现成功的回调,重写parseNetworkResponse()方法来处理响应数据;至此,一个完整的Request请求搭建完毕。

Part 3.添加请求

  前面已经完成了请求队列的创建,Request请求的创建,那么接下来就是把请求添加进队列了。我们看RequestQueue#add()源码:

/**
* Adds a Request to the dispatch queue.
* @param request The request to service
* @return The passed-in request
*/
public Request add(Request request) {
// Tag the request as belonging to this queue and add it to the set of current requests.
//标记当前请求,表示这个请求由当前RequestQueue处理
request.setRequestQueue(this);
synchronized (mCurrentRequests) {
mCurrentRequests.add(request);
}
// Process requests in the order they are added.
//获得当前请求的序号
request.setSequence(getSequenceNumber());
request.addMarker(“add-to-queue”);
// If the request is uncacheable, skip the cache queue and go straight to the network.
//如果请求不能缓存,直接添加到网络请求队列,默认是可以缓存
if (!request.shouldCache()) {
mNetworkQueue.add(request);
return request;
}

// Insert request into stage if there's already a request with the same cache key in flight.// 锁定当前代码块,只能一条线程执行synchronized (mWaitingRequests) {    String cacheKey = request.getCacheKey();    //是否有相同请求正在处理    if (mWaitingRequests.containsKey(cacheKey)) {        // There is already a request in flight. Queue up.        //如果有相同请求正在处理,那么把这个请求放进mWaitingRequest中,等待。        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中,同时也放进mCacheQueue缓存队列中        //这代表这个请求已经开始在缓存线程中运行了        mWaitingRequests.put(cacheKey, null);        mCacheQueue.add(request);    }    return request;}

}
  结合相应的注释,我们得出如下结论:在这个add方法中,主要判断一个Request请求是否可以缓存(默认是可以缓存的),如果不可以则直接添加到网络请求队列开始网络通信;如果可以,则进一步判断当前是否有相同的请求正在进行,如果有相同的请求,则让这个请求排队等待,如果没有相同的请求,则直接放进缓存队列中。如果对此还有什么疑问,可以看下面的流程图(图片来自网络):

RequestQueue#add()方法流程图
Part 4.缓存线程

  在part1的最后实例化了缓存线程并开始运行,一直处于等待状态,而上面把请求添加进了缓存线程,此时缓存线程就开始真正的工作了。我们来看缓存线程的源码,主要看它的run()方法,CacheDispatcher#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();    } ...    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");        //先将响应的结果包装成NetworkResponse,然后调用Request子类的        //parseNetworkResponse方法解析数据        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.            //调用ExecutorDelivey#postResponse方法            mDelivery.postResponse(request, response);        } else {            ....        }    } catch (Exception e) {        VolleyLog.e(e, "Unhandled exception %s", e.toString());    }}

}
  在run()方法中,我们可以看到最开始有一个while(true)循环,表示它一直在等待缓存队列的新请求的出现。接着,先判断这个请求是否有对应的缓存结果,如果没有则直接添加到网络请求队列;接着再判断这个缓存结果是否过期了,如果过期则同样地添加到网络请求队列;接下来便是对缓存结果的处理了,我们可以看到,先是把缓存结果包装成NetworkResponse类,然后调用了Request的parseNetworkResponse,这个方法我们在part2说过,子类需要重写这个方法来处理响应数据。最后,把处理好的数据post到主线程,这里用到了ExecutorDelivery#postResponse()方法,下面会分析到。
  小结:CacheDispatcher线程主要对请求进行判断,是否已经有缓存,是否已经过期,根据需要放进网络请求队列。同时对相应结果进行包装、处理,然后交由ExecutorDelivery处理。这里以一张流程图显示它的完整工作流程:

CacheDispatcher线程工作流程
Part 5.网络请求线程

  上面提到,请求不能缓存、缓存结果不存在、缓存过期的时候会把请求添加进请求队列,此时一直等待的网络请求线程由于获取到请求,终于要开始工作了,我们来看NetworkDispatcher#run()方法:

@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
Request

原创粉丝点击