Detailed explanation of Volley source code

I. overview

Volley is a lightweight Android asynchronous network request framework and image loading framework launched by Google. Released at Google I/O 2013. Its applicable scenario is the network operation with small amount of data and frequent communication.

Main features:

(1) strong expansibility. Most of Volley's designs are based on the interface, which has strong configurability.
(2) to some extent, it conforms to the Http specification, including the processing of return response code (2XX, 3xx, 4xx, 5xx), the processing of request header, the support of cache mechanism, etc. It also supports retry and priority definition.
(3) Android 2.3 and above are based on HttpURLConnection by default, and HttpClient is used for 2.3 and below.

How to choose HttpURLConnection and Android httpclient (encapsulation of httpclient) and why:

Before Froyo(2.2), HttpURLConnection had a major Bug. Calling the close() function would affect the connection pool and cause the connection reuse to fail. Therefore, using HttpURLConnection before Froyo requires keepAlive to be closed. In addition, gzip compression is enabled by default in Gingerbread(2.3) HttpURLConnection, which improves the performance of HTTPS. Ice Cream Sandwich(4.0) HttpURLConnection supports request result caching. In addition, HttpURLConnection itself API is relatively simple, so for Android, HttpURLConnection is recommended after 2.3, and Android httpclient is recommended before.
(4) provide a simple image loading tool.

II. Flow chart

Analysis: the blue part represents the main thread, the green part represents the cache thread, and the orange part represents the network request thread.

Three, detailed explanation

1. Network request queue

When using, we first construct a request queue.

RequestQueue queue = Volley.newRequestQueue(context)

We can see from the analysis of source code that we have done those things here.

1. Instantiate different request classes according to different system version numbers. If the version number is less than 9, use HttpClient. If the version number is greater than 9, use HttpUrlConnection.

   /**
     * Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it.
     *
     * @param context A {@link Context} to use for creating the cache dir.
     * @param stack A {@link BaseHttpStack} to use for the network, or null for default.
     * @return A started {@link RequestQueue} instance.
     */
    public static RequestQueue newRequestQueue(Context context, BaseHttpStack stack) {
        BasicNetwork network;
        if (stack == null) {
            if (Build.VERSION.SDK_INT >= 9) {
                network = new BasicNetwork(new HurlStack());
            } else {
                // Prior to Gingerbread, HttpUrlConnection was unreliable.
                // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
                // At some point in the future we'll move our minSdkVersion past Froyo and can
                // delete this fallback (along with all Apache HTTP code).
                String userAgent = "volley/0";
                try {
                    String packageName = context.getPackageName();
                    PackageInfo info =
                            context.getPackageManager().getPackageInfo(packageName, /* flags= */ 0);
                    userAgent = packageName + "/" + info.versionCode;
                } catch (NameNotFoundException e) {
                }

                network =
                        new BasicNetwork(
                                new HttpClientStack(AndroidHttpClient.newInstance(userAgent)));
            }
        } else {
            network = new BasicNetwork(stack);
        }

        return newRequestQueue(context, network);
    }

2. Set the cache path and call the RequestQueue construction method.

private static RequestQueue newRequestQueue(Context context, Network network) {
        File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
        RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
        queue.start();
        return queue;
    }

3. Construction method, 4 parameters, mcache (file cache), mNetwork(BasicNetwork instance), mdispatchers (network request thread array), and mdelivery (interface of network request result callback, communication with main thread, Handler)

    public RequestQueue(
            Cache cache, Network network, int threadPoolSize, ResponseDelivery delivery) {
        mCache = cache;
        mNetwork = network;
        mDispatchers = new NetworkDispatcher[threadPoolSize];
        mDelivery = delivery;
    }

4. In 2, we call the queue.start() method in RequestQueue. Let's see the settings in the method.

    /** 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();
        }
    }
  • Create the thread for the cache call and turn on the cache thread.
  • According to the size of thread pool, create network request thread and start thread.
  • From the above flow chart, we can see that if the request cannot be cached, it can be added to the network request queue directly. By default, it can be cached, so the request will be put into the cache first. When the cache thread is working, the cache thread will take a request from the cache request queue, and if the hit result is taken from the cache, it will be taken and resolved. If it is lost, it will put the request into the network request thread, and then make the request.

2. Cache thread and network request thread

1. Cache 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 {
                processRequest();
            } catch (InterruptedException e) {
                // We may have been interrupted because it was time to quit.
                if (mQuit) {
                    Thread.currentThread().interrupt();
                    return;
                }
                VolleyLog.e(
                        "Ignoring spurious interrupt of CacheDispatcher thread; "
                                + "use quit() to terminate it");
            }
        }
    }




 @VisibleForTesting
    void processRequest(final Request<?> request) throws InterruptedException {
        request.addMarker("cache-queue-take");

        // If the request has been canceled, don't bother dispatching it.
        if (request.isCanceled()) {
            request.finish("cache-discard-canceled");
            return;
        }

        // 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.
            if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
                mNetworkQueue.put(request);
            }
            return;
        }

        // If it is completely expired, just send it to the network.
        if (entry.isExpired()) {
            request.addMarker("cache-hit-expired");
            request.setCacheEntry(entry);
            if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
                mNetworkQueue.put(request);
            }
            return;
        }

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

            if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
                // 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) {
                                    // Restore the interrupted status
                                    Thread.currentThread().interrupt();
                                }
                            }
                        });
            } else {
                // request has been added to list of waiting requests
                // to receive the network response from the first request once it returns.
                mDelivery.postResponse(request, response);
            }
        }
    }
  • Take a request from the cache queue to determine whether there is a request in the cache. If it does not exist and expires, add the request to the network queue.
  • If hit, the corresponding request resource will be taken out of the cache.
  • Send the result to the main thread through mDelivery.postResponse(request, response).

Summary: the CacheDispatcher thread mainly judges whether the request has been cached or expired, and puts it into the network request queue as required. At the same time, the corresponding results are packaged and processed, and then submitted to ExecutorDelivery for processing. Here is a flowchart showing its complete workflow:

2. Network request thread

 @Override
    public void run() {
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        while (true) {
            try {
                processRequest();
            } catch (InterruptedException e) {
                // We may have been interrupted because it was time to quit.
                if (mQuit) {
                    Thread.currentThread().interrupt();
                    return;
                }
                VolleyLog.e(
                        "Ignoring spurious interrupt of NetworkDispatcher thread; "
                                + "use quit() to terminate it");
            }
        }
    }




   @VisibleForTesting
    void processRequest(Request<?> request) {
        long startTimeMs = SystemClock.elapsedRealtime();
        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");
                request.notifyListenerResponseNotUsable();
                return;
            }

            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");
                request.notifyListenerResponseNotUsable();
                return;
            }

            // 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);
            request.notifyListenerResponseReceived(response);
        } catch (VolleyError volleyError) {
            volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
            parseAndDeliverNetworkError(request, volleyError);
            request.notifyListenerResponseNotUsable();
        } 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);
            request.notifyListenerResponseNotUsable();
        }
    }
  • Execute the network request, mNetwork.performRequest(request), the HttpStack object instantiated according to different system version numbers (the version number greater than 9 is HurlStack, and the version number less than 9 is HttpClientStack).
  • Judge whether the network request can be cached. By default, add the request result to the cache.
  • Send the result of the request to the main thread through mDelivery.postResponse(request, response).

3. About caching

When the CacheDispatcher of Volley works, it needs to specify the Cache policy, which is the Cache interface. This interface has two implementation classes, DiskBasedCache and NoCache. DiskedBasedCache is used by default. It stores the request results in a file for reuse. Volley is a highly flexible framework, and caching is configurable. You can even use your own caching strategy.

Unfortunately, this DiskBasedCache can't be used for many times, because even if the CacheDispatcher gets the cached data from the cache file, it needs to see whether the data is expired. If it Expires, the cached data will not be used. This requires that the server's pages can be cached. This is determined by fields such as cache control and Expires. The server needs to set this field so that the data can be cached. Otherwise, the cache is always out of date, and the network request will always go eventually.

Reference resources:

https://www.jianshu.com/p/15e6209d2e6f

https://www.cnblogs.com/wangzehuaw/p/5583919.html

 

 

 

 

 

 

 

 

Keywords: network Android Google less

Added by jonabomer on Sun, 27 Oct 2019 13:27:00 +0200