Volley源码解析

2017-08-30  本文已影响0人  SDY_0656

volley是现在常用的网络请求库,使用也非常简单,在google的官网上就有一张图介绍volley的请求过程,


volley-request.png

本文主要从源码上分析volley的请求过程,达到知其然也知其所以然的目的。
1,step 1
在volley使用的最开始都需要调用

RequestQueue queue = Volley.newRequestQueue(this);

那么这句话的作用到底是什么呢,在源码中是这么写的:

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) {
            e.printStackTrace();
        }

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

        Network network = new BasicNetwork(stack);
        
        RequestQueue queue;
        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;
    }

这段代码非常简单,首先是见一个缓存文件,然后根据SDK的不同版本新建不同的HttpStack,在版本号大于9的时候使用的HurlStack,在后面可以看到也就是使用的HttpUrlConnection来进行网络请求,而在版本号小于9的时候使用的是HttpClientStack,也就是使用的是HttpClient进行网络请求。在然后就新建了一个RequestQueue,下面再看看这个RequestQueue的构造方法和start方法:

    /**
     * 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
     */
    public RequestQueue(Cache cache, Network network) {
        this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);
    }

/**
     * Starts the dispatchers in this queue.
     */
    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();
        }
    }

从上面的代码可以看出,volley在启动的时候新建了一个CecheDispatcher和4个NetworkDispatcher,这两个都是继承自Thread,也就是说volley有一个缓存队列和4个网络请求队列,在不停的等待加入队列的请求。在请求队列完成之后,就是要将request加入到请求队列,下面看看请求队列是如何添加请求的,
RequestQueue添加请求的方法是这样的:

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

首先将request加入到当前的请求集合中,如何判断是否使用了缓存,如果没有使用请求缓存,那就很简单,直接加入到NetworkQueue中开始请求,如果使用了请求缓存,那么就先看WaitingRequest中是否包含request的cacheKey,如果有,就加入到这个WaitingRequest中,如果没有,那么就将这个request加入到cacheQueue中。现在我们已经知道了如何将request加入到RequestQueue中,那么现在就看看RequestQueue中的一个CacheDispatcher和4个NetworkDispatcher是如何工作的:
先看NetWorkDispatcher的run方法:

                // 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");

省略了部分方法,源码也很简单,就是从请求队列中取出一个,如何调用mNetwork请求,获得response后,再对response进行转化。

再看CacheDispatcher的run方法:

 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();
            } catch (InterruptedException e) {
                // We may have been interrupted because it was time to quit.
                if (mQuit) {
                    return;
                }
                continue;
            }
            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");
                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.
                    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());
            }
        }
    }

首先还是从CacheQueue中取出一个request,然后从缓存中检查看是否存在包含request的cacheKey,如果没有,那么就直接加入到NetworkQueue中,如果有,还要看缓存中包含这个cacheKey的缓存是否已经过期,如果过期了,那么还是加入到NetworkQueue中,如果存在这个request的缓存,而且还没有过期,那么就直接从缓存中取出response返回,这个时候还要判断是否需要更新缓存中的 数据,如果需要,那么就把request加入到Networkqueue中再请求一次。
上面看了如何将request加入到NetworkQueue中,然后NetworkDispatcher就取出Networkqueue中的request进行请求,调用的是Network的performRequest,那么Network是怎么继续请求的呢,在volley中,Network的实现类是BasicNetwork,看看BasicNetwork的performRequest方法:

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

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

@Override
    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;
    }

以上就是volley请求的完整过程,看完源码再看官方给出的流程图,确实清晰多了。

上一篇下一篇

猜你喜欢

热点阅读