Volley源码解析

抽时间看了下Volley的源码,感觉这个框架还是很不错的,这里对其源码进行分析。

主要从以下几个方面分析:

  1. 简要介绍 Volley 的使用

  2. Volley 访问网络的整体流程

  3. 对源码中一些重要的类的总结

  4. 一些总结

简要介绍 Volley 的使用

这个不是文章的重点,所以就简单介绍一下。

    // 创建请求队列
    RequestQueue mQueue = Volley.newRequestQueue(context);    // 创建请求
    StringRequest stringRequest = new StringRequest("http://www.baidu.com",  
                        new Response.Listener<String>() {  
                            @Override  
                            public void onResponse(String response) {  
                                Log.d("TAG", response);  
                            }  
                        }, new Response.ErrorListener() {  
                            @Override  
                            public void onErrorResponse(VolleyError error) {  
                                Log.e("TAG", error.getMessage(), error);  
                            }  
                        });  
    // 添加请求到队列中
    mQueue.add(stringRequest);

更具体的使用可以参考博客 Volley使用

Volley 访问网络的整体流程

这里从使用方法开始进行分析。

首先通过 Volley#newRequestQueue() 创建请求队列。
在Volley类中,一共就提供了两个静态的重载方法 newRequestQueue()
主要看下 newRequestQueue(Context, HttpStack)

    // Volley#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) {
        }        // 创建 HttpStack
        if (stack == null) {            if (Build.VERSION.SDK_INT >= 9) { // 根据sdk版本选择HttpUrlConnection或者HttpClient
                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;
    }

可以看到,其中主要创建了Network,HttpStack,RequestQueue,并启动了RequestQueue
最终网络访问就封装在HttpStack中,这个后面再看
先跟进到 RequestQueue#start()中看下

    // RequestQueue#start()
    public void start() {
        stop();  // Make sure any currently running dispatchers are stopped.
        // 启动缓存派发器
        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();
        }
    }

其中可以看到,就是启动了缓存派发器和网络派发器

在构建了请求队列后,发起请求只要把请求添加到请求队列中就好了,接下来看下RequestQueue#add()代码
这里挑一些主要的代码看

    // RequestQueue#add()
    // RequestQueue中维护了两个队列,一个是缓存队列,一个是网络请求队列,之后派发器会从这两个队列中获取请求
    private final PriorityBlockingQueue<Request<?>> mCacheQueue =    new PriorityBlockingQueue<Request<?>>();    private final PriorityBlockingQueue<Request<?>> mNetworkQueue =        new PriorityBlockingQueue<Request<?>>();    public <T> Request<T> add(Request<T> request) {        // 省略 ...
        // 如果请求不需要缓存的话,直接交给网络请求队列去执行,默认是需要缓存的
        if (!request.shouldCache()) {
            mNetworkQueue.add(request);            return request;
        }        synchronized (mWaitingRequests) {
            String cacheKey = request.getCacheKey();            if (mWaitingRequests.containsKey(cacheKey)) {                // 已经有相同请求正在执行的时候,就吧这个请求保存起来
                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 {                // 没有相同的请求在执行中,就把这个请求放入缓存队列中
                mWaitingRequests.put(cacheKey, null);
                mCacheQueue.add(request);
            }            return request;
        }
    }

从上面代码中可以看到,请求需要缓存的话,会加入到缓存队列中,交给缓存派发器处理,否则交给网络请求派发器处理
接下来看下缓存派发器对请求的处理
由于 CacheDispatcher 和 NetworkDispatcher 都是继承自 Thread , 所以主要看其 run() 函数

    // CacheDispatcher#run()
    public void run() {        // 对缓存池进行初始化
        mCache.initialize();        while (true) { // 死循环不断处理请求
            try {                // 从缓存队列中获取请求,这个缓存队列就是 RequestQueue 中看到的缓存队列
                // 如果队列为空,会阻塞在这个地方
                final Request<?> request = mCacheQueue.take();
                request.addMarker("cache-queue-take");                // 请求取消什么都不做
                if (request.isCanceled()) {
                    request.finish("cache-discard-canceled");                    continue;
                }                // 从缓存池中获取请求缓存的内容
                Cache.Entry entry = mCache.get(request.getCacheKey());                if (entry == null) {
                    request.addMarker("cache-miss");                    // 缓存池中没有缓存此请求,也加入到网络请求队列中去
                    mNetworkQueue.put(request);                    continue;
                }                // 请求过期,也加入网络请求队列
                if (entry.isExpired()) {
                    request.addMarker("cache-hit-expired");
                    request.setCacheEntry(entry);
                    mNetworkQueue.put(request);                    continue;
                }
                request.addMarker("cache-hit");                // 根据缓存中获取的内容构建请求的回复
                Response<?> response = request.parseNetworkResponse(                        new NetworkResponse(entry.data, entry.responseHeaders));
                request.addMarker("cache-hit-parsed");                if (!entry.refreshNeeded()) {                    // 缓存不需要刷新,直接交给 ResponseDelivery 去处理,ResponseDelivery后面再看
                    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;
                    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;
            }
        }
    }

从上面可以看到,代码不多,也很好理解,如果有缓存,就取出来去处理,没有或者过期,就加入到网络请求队列中去请求,需要网络请求的有几个地方:1. 请求不需要缓存 2. 请求在缓存中没有找到 3. 请求的缓存过期 4. 请求的缓存需要刷新
接下来就看下网络请求派发器
NetworkDispatcher 也是继承 Thread,所以也直接看 run() 函数

    public void run() {
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);        while (true) { // 一样是死循环不断处理请求
            long startTimeMs = SystemClock.elapsedRealtime();
            Request<?> request;            try {                // 这个地方会阻塞,这个队列就是 RequestQueue 中的网络队列
                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 (request.isCanceled()) {
                    request.finish("network-discard-cancelled");                    continue;
                }
                addTrafficStatsTag(request);                // 执行网络请求 其中真正执行的是 httpStack.performRequest
                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;
                }                // 对返回的内容进行处理,parseNetworkResponse 需要我们重写,不过框架提供了一些常用的比如 StringRequest 和 JsonRequest
                Response<?> response = request.parseNetworkResponse(networkResponse);
                request.addMarker("network-parse-complete");                // 存到缓存中
                if (request.shouldCache() && response.cacheEntry != null) {
                    mCache.put(request.getCacheKey(), response.cacheEntry);
                    request.addMarker("network-cache-written");
                }
                request.markDelivered();                // 交给 ResponseDelivery 进一步处理
                mDelivery.postResponse(request, response);
            } catch (VolleyError volleyError) {
                volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
                parseAndDeliverNetworkError(request, volleyError);
            } catch (Exception e) {
                VolleyLog.e(e, "Unhandled exception %s", e.toString());
                VolleyError volleyError = new VolleyError(e);
                volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
                mDelivery.postError(request, volleyError);
            }
        }
    }

可以看到,在 NetworkDispatcher 中,最主要的就是调用了 mNetwork.performRequest(request) 执行网络请求,而在 Network 是个接口,具体实现类是 BasicNetwork,在BasicNetwork#performRequest()中调用 httpResponse = mHttpStack.performRequest(request, headers),这里的 mHttpStack 就是前面看到的 HurlStack 和 HttpClientStack。而在 HttpStack 的 performRequest() 中就是具体的网络请求,看下 HurlStack 的。

    // 这块就比较熟悉了,就是之前经常用的网络请求
    public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
            throws IOException, AuthFailureError {
        String url = request.getUrl();
        HashMap<String, String> map = new HashMap<String, String>();
        map.putAll(request.getHeaders());
        map.putAll(additionalHeaders);        if (mUrlRewriter != null) {
            String rewritten = mUrlRewriter.rewriteUrl(url);            if (rewritten == null) {                throw new IOException("URL blocked by rewriter: " + url);
            }
            url = rewritten;
        }
        URL parsedUrl = new URL(url);
        HttpURLConnection connection = openConnection(parsedUrl, request);        for (String headerName : map.keySet()) {
            connection.addRequestProperty(headerName, map.get(headerName));
        }
        setConnectionParametersForRequest(connection, request);        // Initialize HttpResponse with data from the HttpURLConnection.
        ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1);        int responseCode = connection.getResponseCode();        if (responseCode == -1) {            // -1 is returned by getResponseCode() if the response code could not be retrieved.
            // Signal to the caller that something was wrong with the connection.
            throw new IOException("Could not retrieve response code from HttpUrlConnection.");
        }
        StatusLine responseStatus = new BasicStatusLine(protocolVersion,
                connection.getResponseCode(), connection.getResponseMessage());
        BasicHttpResponse response = new BasicHttpResponse(responseStatus);        if (hasResponseBody(request.getMethod(), responseStatus.getStatusCode())) {
            response.setEntity(entityFromConnection(connection));
        }        for (Entry<String, List<String>> header : connection.getHeaderFields().entrySet()) {            if (header.getKey() != null) {
                Header h = new BasicHeader(header.getKey(), header.getValue().get(0));
                response.addHeader(h);
            }
        }        return response;
    }

在上面我们看到,最终会调用 ResponseDelivery#postResponse()
ExecutorDelivery 是 ResponseDelivery 的实现类,可以看下 ExecutorDelivery#postResponse()

    // ExecutorDelivery#postResponse()
    private final Executor mResponsePoster;    public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
        request.markDelivered();
        request.addMarker("post-response");        // 执行任务
        mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
    }
    // ResponseDeliveryRunnable#run()
        public void run() {            // If this request has canceled, finish it and don't deliver.
            if (mRequest.isCanceled()) {
                mRequest.finish("canceled-at-delivery");                return;
            }            if (mResponse.isSuccess()) {                // 这个就是 request 中自己实现的,在 StringRequest 和 JsonRequest 中都是直接调用了 listener.onResponse()
                mRequest.deliverResponse(mResponse.result);
            } else {
                mRequest.deliverError(mResponse.error);
            }            if (mResponse.intermediate) {
                mRequest.addMarker("intermediate-response");
            } else {
                mRequest.finish("done");
            }            // 如果有需要执行的任务,会执行,前面缓存需要刷新的话,就会在这个地方进行网络调用
            if (mRunnable != null) {
                mRunnable.run();
            }
       }

以上就是请求的主要流程了,下面附上一张流程图

上面分析中没有提到 Volley 中的重试机制,其实在 BasicNetwork#performRequest() 中实现了失败重试机制。

    public NetworkResponse performRequest(Request<?> request) throws VolleyError {        long requestStart = SystemClock.elapsedRealtime();        // 这个循环保证了失败后可以重新请求
        while (true) {            // 省略代码 ...
            try {                // 执行网络请求
                httpResponse = mHttpStack.performRequest(request, headers);                // 省略代码 ...
            } catch (SocketTimeoutException e) {                // 这里通过捕获异常来重试,这里没有捕捉 VolleyError 的异常,所以抛出 VolleyError 异常就会结束循环,不再重试了
                attemptRetryOnException("socket", request, new TimeoutError());
            }            // 省略代码 ...
        }
    }    private static void attemptRetryOnException(String logPrefix, Request<?> request,
            VolleyError exception) throws VolleyError {
        RetryPolicy retryPolicy = request.getRetryPolicy();        int oldTimeout = request.getTimeoutMs();        try {
            retryPolicy.retry(exception); // 重试策略,如果不需要重试,就会抛出 VolleyError 的异常
        } catch (VolleyError e) {
            request.addMarker(
                    String.format("%s-timeout-giveup \[timeout=%s\]", logPrefix, oldTimeout));            throw e;
        }
        request.addMarker(String.format("%s-retry \[timeout=%s\]", logPrefix, oldTimeout));
    }

对源码中一些重要的类的总结

其实在上面讲流程的时候已经分析了主要的类了,这里主要做个总结。
Volley中主要的类有这些:

  • Volley: 提供了构建 RequestQueue 的统一方法,我们也可以不通过这个而是自己构建 RequestQueue

  • RequestQueue: 负责分发请求到不同的请求队列中

  • CacheDispatcher: 处理缓存请求

  • NetworkDispatcher: 处理网络请求

  • ResponseDelivery: 获取请求后进行处理

  • Cache: 缓存接口,具体实现类有 DiskBaseCache

  • Network: 网络接口,具体实现类有 BasicNetwork

  • HttpStack: 真正执行请求,具体实现类有 HurlStack HttpClientStack

  • Request: 封装请求信息并处理回复,具体实现类有 StringRequest JsonRequest

  • Response: 封装返回的信息,具体实现类有 NetworkResponse

一些总结

最后再分析一下为什么在 Volley 的 listener 的中可以直接更新UI。
在前面 Volley#newRequestQueue() 中有调用到如下代码

RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);

跟进会发现最后调用的是

public RequestQueue(Cache cache, Network network, int threadPoolSize) {        this(cache, network, threadPoolSize,                new ExecutorDelivery(new Handler(Looper.getMainLooper())));
    }

我们上面也提到,最后的 Response 是通过 ResponseDelivery#postResponse() 来分发,而 ExecutorDelivery 是 ResponseDelivery 的实现类,而这个构造函数的实现如下:

    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); // 请求最终都会调用 handler.post(),而这个 handler 是上面通过 new Handler(Lopper.getMainLopper()) 得到的
            }
        };
    }

请求最终都会调用 handler.post(),而这个 handler 是上面通过 new Handler(Lopper.getMainLopper()) 得到的,所以请求的处理就通过这里分发到主线程中了,也就自然可以更新 UI 了。

最后:
通过对 Volley 源码的分析,可以发现, Volley 框架的拆装性很强,框架默认使用的是 HttpUrlConnection 和 HttpClient 来实现网络请求,如果我们想换成其他,比如okhttp,只要继承 HttpStack 并重写 performRequest() 来处理请求就可以了。对于请求,框架默认提供了 StringRequest 和 JsonRequest,一般情况下这两个是足够用了,但是特别情况下,我们只要继承 Request 并重写 deliverResponse() 和 parseNetworkResponse() 就可以了。
以前自己也有实现过网络框架,相比之下,还是有很多不足,通过阅读源码,还是学到了很多。