Android Volley完全解析(四),带你从源码的角度理解Volley

本文由 简悦 SimpRead 转码, 原文地址 blog.csdn.net

转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/17656437

经过前三篇文章的学习,Volley 的用法我们已经掌握的差不多了,但是对于 Volley 的工作原理,恐怕有很多朋友还不是很清楚。因此,本篇文章中我们就来一起阅读一下 Volley 的源码,将它的工作流程整体地梳理一遍。同时,这也是 Volley 系列的最后一篇文章了。

其实,Volley 的官方文档中本身就附有了一张 Volley 的工作流程图,如下图所示。

多数朋友突然看到一张这样的图,应该会和我一样,感觉一头雾水吧?没错,目前我们对 Volley 背后的工作原理还没有一个概念性的理解,直接就来看这张图自然会有些吃力。不过没关系,下面我们就去分析一下 Volley 的源码,之后再重新来看这张图就会好理解多了。

说起分析源码,那么应该从哪儿开始看起呢?这就要回顾一下 Volley 的用法了,还记得吗,使用 Volley 的第一步,首先要调用 Volley.newRequestQueue(context) 方法来获取一个 RequestQueue 对象,那么我们自然要从这个方法开始看起了,代码如下所示:

public static RequestQueue newRequestQueue(Context context) {
    return newRequestQueue(context, null);
}

这个方法仅仅只有一行代码,只是调用了

newRequestQueue() 的方法重载,并给第二个参数传入 null。那我们看下带有两个参数的 newRequestQueue() 方法中的代码,如下所示:

public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
    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) {
    }
    if (stack == null) {
        if (Build.VERSION.SDK_INT >= 9) {
            stack = new HurlStack();
        } else {
            stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
        }
    }
    Network network = new BasicNetwork(stack);
    RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
    queue.start();
    return queue;
}

可以看到,这里在第 10 行判断如果 stack 是等于 null 的,则去创建一个 HttpStack 对象,这里会判断如果手机系统版本号是大于 9 的,则创建一个 HurlStack 的实例,否则就创建一个 HttpClientStack 的实例。实际上

HurlStack 的内部就是使用 HttpURLConnection 进行网络通讯的,而 HttpClientStack 的内部则是使用 HttpClient 进行网络通讯的,这里为什么这样选择呢?可以参考我之前翻译的一篇文章 Android 访问网络,使用 HttpURLConnection 还是 HttpClient?

创建好了 HttpStack 之后,接下来又创建了一个 Network 对象,它是用于根据传入的 HttpStack 对象来处理网络请求的,紧接着 new 出一个 RequestQueue 对象,并调用它的 start() 方法进行启动,然后将 RequestQueue 返回,这样 newRequestQueue() 的方法就执行结束了。

那么 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 的实例,然后调用了它的 start() 方法,接着在一个 for 循环里去创建 NetworkDispatcher 的实例,并分别调用它们的 start() 方法。这里的 CacheDispatcher 和 NetworkDispatcher 都是继承自 Thread 的,而默认情况下 for 循环会执行四次,也就是说当调用了 Volley.newRequestQueue(context) 之后,就会有五个线程一直在后台运行,不断等待网络请求的到来,

其中

CacheDispatcher 是缓存线程,NetworkDispatcher 是网络请求线程。

得到了 RequestQueue 之后,我们只需要构建出相应的 Request,然后调用 RequestQueue 的 add() 方法将 Request 传入就可以完成网络请求操作了,那么不用说,add() 方法的内部肯定有着非常复杂的逻辑,我们来一起看一下:

public <T> Request<T> add(Request<T> request) {
    // Tag the request as belonging to this queue and add it to the set of current requests.
    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.
            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;
    }
}

可以看到,在第 11 行的时候会判断当前的请求是否可以缓存,如果不能缓存则在第 12 行直接将这条请求加入网络请求队列,可以缓存的话则在第 33 行将这条请求加入缓存队列。在默认情况下,每条请求都是可以缓存的,当然我们也可以调用 Request 的 setShouldCache(false) 方法来改变这一默认行为。

OK,那么既然默认每条请求都是可以缓存的,自然就被添加到了缓存队列中,于是一直在后台等待的缓存线程就要开始运行起来了,我们看下 CacheDispatcher 中的 run() 方法,代码如下所示:

public class CacheDispatcher extends Thread {
 
    ……
 
    @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();
        while (true) {
            try {
                // Get a request from the cache triage queue, blocking until
                // at least one is available.
                final Request<?> request = mCacheQueue.take();
                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.
                    mDelivery.postResponse(request, response, new Runnable() {
                        @Override
                        public void run() {
                            try {
                                mNetworkQueue.put(request);
                            } catch (InterruptedException e) {
                                // Not much we can do about this.
                            }
                        }
                    });
                }
            } catch (InterruptedException e) {
                // We may have been interrupted because it was time to quit.
                if (mQuit) {
                    return;
                }
                continue;
            }
        }
    }
}

代码有点长,我们只挑重点看。首先在 11 行可以看到一个 while(true) 循环,说明缓存线程始终是在运行的,接着在第 23 行会尝试从缓存当中取出响应结果,如何为空的话则把这条请求加入到网络请求队列中,如果不为空的话再判断该缓存是否已过期,如果已经过期了则同样把这条请求加入到网络请求队列中,否则就认为不需要重发网络请求,直接使用缓存中的数据即可。之后会在第 39 行调用 Request 的

parseNetworkResponse() 方法来对数据进行解析,再往后就是将解析出来的数据进行回调了,这部分代码我们先跳过,因为它的逻辑和 NetworkDispatcher 后半部分的逻辑是基本相同的,那么我们等下合并在一起看就好了,先来看一下 NetworkDispatcher 中是怎么处理网络请求队列的,代码如下所示:

public class NetworkDispatcher extends Thread {
	……
    @Override
    public void run() {
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        Request<?> request;
        while (true) {
            try {
                // Take a request from the queue.
                request = mQueue.take();
            } catch (InterruptedException e) {
                // We may have been interrupted because it was time to quit.
                if (mQuit) {
                    return;
                }
                continue;
            }
            try {
                request.addMarker("network-queue-take");
                // If the request was cancelled already, do not perform the
                // network request.
                if (request.isCanceled()) {
                    request.finish("network-discard-cancelled");
                    continue;
                }
                addTrafficStatsTag(request);
                // Perform the network request.
                NetworkResponse networkResponse = mNetwork.performRequest(request);
                request.addMarker("network-http-complete");
                // If the server returned 304 AND we delivered a response already,
                // we're done -- don't deliver a second identical response.
                if (networkResponse.notModified && request.hasHadResponseDelivered()) {
                    request.finish("not-modified");
                    continue;
                }
                // Parse the response here on the worker thread.
                Response<?> response = request.parseNetworkResponse(networkResponse);
                request.addMarker("network-parse-complete");
                // Write to cache if applicable.
                // TODO: Only update cache metadata instead of entire record for 304s.
                if (request.shouldCache() && response.cacheEntry != null) {
                    mCache.put(request.getCacheKey(), response.cacheEntry);
                    request.addMarker("network-cache-written");
                }
                // Post the response back.
                request.markDelivered();
                mDelivery.postResponse(request, response);
            } catch (VolleyError volleyError) {
                parseAndDeliverNetworkError(request, volleyError);
            } catch (Exception e) {
                VolleyLog.e(e, "Unhandled exception %s", e.toString());
                mDelivery.postError(request, new VolleyError(e));
            }
        }
    }
}

同样地,在第 7 行我们看到了类似的 while(true) 循环,说明网络请求线程也是在不断运行的。在第 28 行的时候会调用 Network 的 performRequest() 方法来去发送网络请求,而 Network 是一个接口,这里具体的实现是 BasicNetwork,我们来看下它的

performRequest() 方法,如下所示:

public class BasicNetwork implements Network {
	……
    @Override
    public NetworkResponse performRequest(Request<?> request) throws VolleyError {
        long requestStart = SystemClock.elapsedRealtime();
        while (true) {
            HttpResponse httpResponse = null;
            byte[] responseContents = null;
            Map<String, String> responseHeaders = new HashMap<String, String>();
            try {
                // Gather headers.
                Map<String, String> headers = new HashMap<String, String>();
                addCacheHeaders(headers, request.getCacheEntry());
                httpResponse = mHttpStack.performRequest(request, headers);
                StatusLine statusLine = httpResponse.getStatusLine();
                int statusCode = statusLine.getStatusCode();
                responseHeaders = convertHeaders(httpResponse.getAllHeaders());
                // Handle cache validation.
                if (statusCode == HttpStatus.SC_NOT_MODIFIED) {
                    return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED,
                            request.getCacheEntry() == null ? null : request.getCacheEntry().data,
                            responseHeaders, true);
                }
                // Some responses such as 204s do not have content.  We must check.
                if (httpResponse.getEntity() != null) {
                  responseContents = entityToBytes(httpResponse.getEntity());
                } else {
                  // Add 0 byte response as a way of honestly representing a
                  // no-content request.
                  responseContents = new byte[0];
                }
                // if the request is slow, log it.
                long requestLifetime = SystemClock.elapsedRealtime() - requestStart;
                logSlowRequests(requestLifetime, request, responseContents, statusLine);
                if (statusCode < 200 || statusCode > 299) {
                    throw new IOException();
                }
                return new NetworkResponse(statusCode, responseContents, responseHeaders, false);
            } catch (Exception e) {
                ……
            }
        }
    }
}

这段方法中大多都是一些网络请求细节方面的东西,我们并不需要太多关心,需要注意的是在第 14 行调用了 HttpStack 的 performRequest() 方法,这里的 HttpStack 就是在一开始调用 newRequestQueue() 方法是创建的实例,默认情况下如果系统版本号大于 9 就创建的 HurlStack 对象,否则创建 HttpClientStack 对象。前面已经说过,这两个对象的内部实际就是分别使用 HttpURLConnection 和 HttpClient 来发送网络请求的,我们就不再跟进去阅读了,之后会将服务器返回的数据组装成一个 NetworkResponse 对象进行返回。

在 NetworkDispatcher 中收到了 NetworkResponse 这个返回值后又会调用 Request 的 parseNetworkResponse() 方法来解析 NetworkResponse 中的数据,以及将数据写入到缓存,这个方法的实现是交给 Request 的子类来完成的,因为不同种类的 Request 解析的方式也肯定不同。还记得我们在上一篇文章中学习的自定义 Request 的方式吗?其中 parseNetworkResponse() 这个方法就是必须要重写的。

在解析完了 NetworkResponse 中的数据之后,又会调用 ExecutorDelivery 的 postResponse() 方法来回调解析出的数据,代码如下所示:

public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
    request.markDelivered();
    request.addMarker("post-response");
    mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
}

其中,在 mResponsePoster 的 execute() 方法中传入了一个 ResponseDeliveryRunnable 对象,就可以保证该对象中的 run() 方法就是在主线程当中运行的了,我们看下 run() 方法中的代码是什么样的:

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();
        }
   }
}

代码虽然不多,但我们并不需要行行阅读,抓住重点看即可。其中在第 22 行调用了 Request 的 deliverResponse() 方法,有没有感觉很熟悉?没错,这个就是我们在自定义 Request 时需要重写的另外一个方法,每一条网络请求的响应都是回调到这个方法中,最后我们再在这个方法中将响应的数据回调到 Response.Listener 的 onResponse() 方法中就可以了。

好了,到这里我们就把 Volley 的完整执行流程全部梳理了一遍,你是不是已经感觉已经很清晰了呢?对了,还记得在文章一开始的那张流程图吗,刚才还不能理解,现在我们再来重新看下这张图:

其中蓝色部分代表主线程,绿色部分代表缓存线程,橙色部分代表网络线程。我们在主线程中调用 RequestQueue 的 add() 方法来添加一条网络请求,这条请求会先被加入到缓存队列当中,如果发现可以找到相应的缓存结果就直接读取缓存并解析,然后回调给主线程。如果在缓存中没有找到结果,则将这条请求加入到网络请求队列中,然后处理发送 HTTP 请求,解析响应结果,写入缓存,并回调主线程。

怎么样,是不是感觉现在理解这张图已经变得轻松简单了?好了,到此为止我们就把 Volley 的用法和源码全部学习完了,相信你已经对 Volley 非常熟悉并可以将它应用到实际项目当中了,那么 Volley 完全解析系列的文章到此结束,感谢大家有耐心看到最后。

关注我的技术公众号,每天都有优质技术文章推送。关注我的娱乐公众号,工作、学习累了的时候放松一下自己。

微信扫一扫下方二维码即可关注:

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值