Complete parsing of Android Volley (4), which helps you understand Volley from the source code perspective

Source: Internet
Author: User

Reprinted please indicate the source: http://blog.csdn.net/guolin_blog/article/details/17656437

After learning from the first three articles, we have mastered the usage of Volley, but I am afraid many friends are not very clear about Volley's working principles. Therefore, in this article, let's take a look at Volley's source code and sort out its workflow as a whole. At the same time, this is the last article in the Volley series.

In fact, Volley's official documents contain a Volley workflow, as shown in.


Most of my friends suddenly see a picture like this. Is it a fog? That's right. We don't have a conceptual understanding of the working principles behind Volley, so it's a bit difficult to look at this picture. But it does not matter. Next we will analyze the Volley source code, and then review this picture to understand it much better.

When talking about source code analysis, where should we start? Let's review Volley's usage. Do you still remember that Volley's first step is to call Volley. the newRequestQueue (context) method is used to obtain a RequestQueue object. The Code is as follows:

public static RequestQueue newRequestQueue(Context context) {    return newRequestQueue(context, null);}
This method only has one line of code. It only calls the newRequestQueue () method overload and passes null to the second parameter. Let's take a look at the code in the newRequestQueue () method with two parameters, as shown below:
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;}
We can see that in row 10th, if the stack value is null, an HttpStack object is created. Here, we can determine that if the mobile phone system version number is greater than 9, create a HurlStack instance. Otherwise, an HttpClientStack instance is created. In fact, HurlStack uses HttpURLConnection for network communication, while HttpClientStack uses HttpClient for network communication. Why? Refer to an article I translated earlier. Does Android use HttpURLConnection or HttpClient to access the network?

After HttpStack is created, a Network object is created to process Network requests based on the incoming HttpStack object. Then a RequestQueue object is created, and call its start () method to start, and then return RequestQueue, so that the newRequestQueue () method is executed.

So what exactly does the start () method of RequestQueue execute internally? Let's take a look:

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();    }}
Here, a CacheDispatcher instance is created, its start () method is called, and then NetworkDispatcher instances are created in a for loop, and their start () method. Here, both CacheDispatcher and NetworkDispatcher inherit from the Thread, And the for Loop will be executed four times by default, that is, when Volley is called. after newRequestQueue (context), there will be five threads running in the background constantly waiting for the arrival of network requests. Among them, CacheDispatcher is the cache thread and NetworkDispatcher is the network request thread.

After obtaining RequestQueue, we only need to construct the corresponding Request, and then call the add () method of RequestQueue to pass in the Request to complete the network Request operation. Needless to say, add () the internal logic of the method must be very complex. Let's take a look:

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;    }}
We can see that the current request can be cached in row 11th. If the request cannot be cached, the request is directly added to the network Request queue in row 12th, if the request can be cached, the request is added to the cache queue on line 1. By default, each Request can be cached. Of course, we can also call the setShouldCache (false) method of the Request to change this default behavior.

Okay, now that each request can be cached by default, it is naturally added to the cache queue, so the cache thread that has been waiting in the background is about to start running, let's take a look at the run () method in CacheDispatcher. The Code is as follows:

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;            }        }    }}
The Code is a bit long. Let's focus on it. First, we can see a while (true) loop in row 11, which indicates that the cache thread is always running, and then we try to retrieve the response result from the cache in line 23rd, if it is null, add the request to the network Request queue. If it is not empty, determine whether the cache has expired, if the request expires, the request is added to the network Request queue. Otherwise, you do not need to resend the network request and directly use the cached data. The parseNetworkResponse () method of the Request will be called in Row 3 to parse the data, and the parsed data will be called back later. We will skip this part of the code first, because its logic is basically the same as that of the second half of NetworkDispatcher, let's wait and see it together. Let's take a look at how NetworkDispatcher processes the network Request queue, the Code is as follows:
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));            }        }    }}
Similarly, in row 7th, we see a similar while (true) loop, indicating that the network request thread is also running continuously. In line 3, we will call the Network's receivmrequest () method to send Network requests, while Network is an interface. The specific implementation here is BasicNetwork. Let's take a look at its receivmrequest () method:
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) {                ……            }        }    }}

Most of the methods in this section are network request details, and we don't need to worry much about it. We need to note that the same mrequest () method of HttpStack is called in line 14th, httpStack is the HurlStack object created when newRequestQueue () method is called at the beginning. By default, if the system version number is greater than 9, The HttpClientStack object is created. As mentioned above, the two objects actually use HttpURLConnection and HttpClient to send network requests, so we will not read them again, then, the data returned by the server is assembled into a NetworkResponse object and returned.

After receiving the NetworkResponse returned value in NetworkDispatcher, the Request parseNetworkResponse () method is called to parse the data in NetworkResponse and write the data to the cache, the implementation of this method is completed by the Request subclass, because different types of Request Parsing methods are certainly different. Do you still remember the custom Request method we learned in the previous article? The parseNetworkResponse () method must be rewritten.

After the data in NetworkResponse is parsed, ExecutorDelivery's postResponse () method is called to mediate the data. The Code is as follows:

public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {    request.markDelivered();    request.addMarker("post-response");    mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));}
Specifically, a ResponseDeliveryRunnable object is passed in the execute () method of mResponsePoster, so that the run () method in this object runs in the main thread, let's take a look at the code in the run () method:
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();        }   }}

Although there are not many codes, we do not need to read the code in the line. Just focus on it. I have called the Request's deliverResponse () method in Row 3. Are you familiar with this? Yes, this is another method we need to rewrite when customizing a Request. The response of each network Request is called back to this method, finally, we will call back the Response data to Response in this method. in the onResponse () method of Listener.

Now, let's sort out the complete execution process of Volley. Do you already feel clear? By the way, do you still remember the flowchart at the beginning of the article? I just can't understand it. Now let's take a look at it again:


The blue part indicates the main thread, the green part indicates the cache thread, and the orange part indicates the network thread. We call the add () method of RequestQueue in the main thread to add a network request. This request is first added to the cache queue, if the corresponding cache results are found, the cache is directly read and parsed, and then the callback is sent to the main thread. If no result is found in the cache, add the request to the network Request queue, process and send the HTTP request, parse the response, write it to the cache, and call back the main thread.

How do you think it is easy to understand this image now? So far, we have learned all the usage and source code of Volley. I believe you are familiar with Volley and can apply it to actual projects, so Volley's series of articles are completely parsed. Thank you for your patience.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.