Hello, I’m N0tExpectErr0r, an Android developer who loves technology

My personal blog: blog.n0tExpecterr0r.cn

This source code analysis is based on Volley 1.1.1

Volley, a Web request framework developed by Google, has been discontinued. While the current focus is on third-party web request frameworks such as Retrofit and OkHttp, which are used in the team’s projects, there are plenty of good design ideas to learn from Volley. So today I’m going to take a look at Volley’s source code and understand its core design ideas.

Volley

Let’s start with the entry to Volley — the Volley class.

Create RequestQueue

NewRequestQueue (Context) : volley. newRequestQueue(Context) : volley. newRequestQueue(Context) : volley. newRequestQueue(Context) : volley. newRequestQueue(Context)

/**
 * 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.
 * @return A started {@link RequestQueue} instance.
 */
public static RequestQueue newRequestQueue(Context context) {
    return newRequestQueue(context, (BaseHttpStack) null);
}
Copy the code

NewRequestQueue (Context, BaseHttpStack) with a volley. newRequestQueue(Context, HttpStack) method:

/**
 * 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 An {@link HttpStack} to use for the network, or null for default.
 * @return A started {@link RequestQueue} instance.
 * @deprecated Use {@link #newRequestQueue(Context, BaseHttpStack)} instead to avoid depending
 *     on Apache HTTP. This method may be removed in a future release of Volley.
 */
public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
    if (stack == null) {
        return newRequestQueue(context, (BaseHttpStack) null);
    }
    return newRequestQueue(context, new BasicNetwork(stack));
}
Copy the code

NewRequestQueue (Context, NetWork); volley. newRequestQueue(Context, NetWork); Otherwise it will also call volley. newRequestQueue(Context, BaseHttpStack) :

/**
 * 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.
 * @returnA started {@link RequestQueue} instance. */ public static RequestQueue newRequestQueue(Context context, BaseHttpStack stack) { BasicNetwork network; // Create a NetWork objectif(stack == null) {// If the stack is empty, create a stack, and then create a NetWorkif (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);
}
Copy the code

The main thing this method does is create a netWork object based on the stack.

When stack is empty, HurlStack is used for SDK versions higher than 9, and HttpClientStack is used for versions lower than that. The reason for this can be seen in the comments above: Because HttpUrlConnection prior to SDK 9 was not very reliable. (We can assume that Volley is implemented based on HttpUrlConnection in older versions (SDK > 9) and HttpClient in older versions.

NetWork and HttpStack are used for the same purpose. We’ll solve all of these problems later.

It also ends up calling volley. newRequestQueue(Context, Network) :

private static RequestQueue newRequestQueue(Context context, Network network) {
    final Context appContext = context.getApplicationContext();
    // Use a lazy supplier for the cache directory so that newRequestQueue() can be called on
    // main thread without causing strict mode violation.
    DiskBasedCache.FileSupplier cacheSupplier =
            new DiskBasedCache.FileSupplier() {
                private File cacheDir = null;
                @Override
                public File get() {
                    if (cacheDir == null) {
                        cacheDir = new File(appContext.getCacheDir(), DEFAULT_CACHE_DIR);
                    }
                    returncacheDir; }}; RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheSupplier), network); queue.start();return queue;
}
Copy the code

First, the application Cache folder is retrieved from the Context and the Cache file is created. One minor detail is that Volley uses FileSupplier to wrap files in order to call newRequestQueue without being affected by StrictMode rules. It takes the lazy approach of creating the corresponding cacheDir only when needed.

The RequestQueue is then constructed, its start method is called and returned. As you can see, Volley is a static factory for a RequestQueue.

RequestQueue

RequestQueue maintains three containers: two PriorityBlockingQueues: mCacheQueue and mNetQueue, and one Set: mCurrentRequests.

  • mCacheQueue: Used to hold cached requestsRequest.
  • mNetQueue: Stores the files waiting to be initiatedRequest.
  • mCurrentQueue: Used to store requests currently in progressRequest.

create

Let’s start by looking at the RequestQueue constructor:

public RequestQueue(Cache cache, Network network) {
    this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);
}
Copy the code

It calls to another constructor and passes a default thread number, DEFAULT_NETWORK_THREAD_POOL_SIZE, which represents the default number of network request dispatch threads to start, with a default value of 4.

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

This constructor creates a main thread Handler and uses it to build an ExecutorDelivery, which we’ll discuss later, which is a class for delivering Response and Error information.

The constructor that follows the call mostly does some assignment.

Start the

Now let’s look at requestQueue.start to see how it starts:

/** 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(); }}Copy the code

The primary use of start is to start the Dispatcher in this Queue. Its main steps are as follows:

  1. callstopMethod to stop all runningDispatcher
  2. createCacheDispatcherAnd start.
  3. Creates the value of the specified numberNetworkDispatcher(Default: 4) and start.

There are five Dispatchers per RequestQueue, including four Network Dispatchers and one CacheDispatcher.

The team

We can enqueue a Request with requestQueue.add, It adds the current Request to either mNetworkQueue or mCacheQueue, depending on whether it needs to be cached. (Actually GET requests go to mCacheQueue first, and the rest go directly to mNetworkQueue.)

/**
 * Adds a Request to the dispatch queue.
 *
 * @param request The request to service
 * @return The passed-in request
 */
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");
    sendRequestEvent(request, RequestEvent.REQUEST_QUEUED);
    // If the request is uncacheable, skip the cache queue and go straight to the network.
    if(! request.shouldCache()) { mNetworkQueue.add(request);return request;
    }
    mCacheQueue.add(request);
    return request;
}
Copy the code

stop

Let’s start with what stop does:

public void stop() {
    if(mCacheDispatcher ! = null) { mCacheDispatcher.quit(); }for (final NetworkDispatcher mDispatcher : mDispatchers) {
        if(mDispatcher ! = null) { mDispatcher.quit(); }}}Copy the code

Here we simply call the quit method for each Dispatcher.

The end of the

RequestQueue also has a finish method corresponding to Request.finish:

/**
 * Called from {@link Request#finish(String)}, indicating that processing of the given request
 * has finished.
 */
@SuppressWarnings("unchecked") // see above note on RequestFinishedListener
<T> void finish(Request<T> request) {
    // Remove from the set of requests currently being processed.
    synchronized (mCurrentRequests) {
        mCurrentRequests.remove(request);
    }
    synchronized (mFinishedListeners) {
        for (RequestFinishedListener<T> listener : mFinishedListeners) {
            listener.onRequestFinished(request);
        }
    }
    sendRequestEvent(request, RequestEvent.REQUEST_FINISHED);
}
Copy the code

This removes the finished Request from mCurrentRequests, invokes externally registered callbacks, and sends REQUEST_FINISHED events.

ExecutorDelivery

What does ExecutorDelivery do

/** Delivers responses and errors. */ public class ExecutorDelivery implements ResponseDelivery { private final Executor  mResponsePoster; /** * Creates a new response delivery interface. * * @param handler {@link Handler} to post responses on */ public ExecutorDelivery(final Handler handler) { // Make an Executor that just wraps the handler. mResponsePoster = newExecutor() {
                    @Override
                    public void execute(Runnable command) {
                        handler.post(command);
                    }
                };
    }
    
    //...
}
Copy the code

The main function of ExecutorDelivery is to deliver Response and Error information.

It holds an Executor called ResponsePoster, and each Runnable that calls the execute method of this Poster is sent to the main thread’s MessageQueue via handler. post.

Then we see the method inside:

@Override public void postResponse(Request<? > request, Response<? > response) { postResponse(request, response, null); } @Override public void postResponse(Request<? > request, Response<? > response, Runnable runnable) { request.markDelivered(); request.addMarker("post-response"); mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable)); } @Override public void postError(Request<? > request, VolleyError error) { request.addMarker("post-error"); Response<? > response = Response.error(error); mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, null)); }Copy the code

As can be seen, its internal method is mainly to post Response information and Error information to MessageQueue, where request and Response will be packaged as a ResponseDeliveryRunnable.

ResponseDeliveryRunnable

ResponseDeliveryRunnable is an internal class of ExecutorDelivery, and you can see its Run method:

@Override
public void run() {
    // NOTE: If cancel() is called off the thread that we re currently running in(by // default, the main thread), we cannot guarantee that deliverResponse()/deliverError() // won t be called, since it may be canceled after we check isCanceled() but before we // deliver the response. Apps concerned about this guarantee must either call cancel() // from the same thread or implement their own guarantee about not invoking their //  listener after cancel() has been called. // 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();
    }
}
Copy the code

Different methods in Request are called according to the different states of Request and Response to deliver the results of Response and Error:

  • ifRequestCancelled, calledRequest.finishMethod ends directly.
  • If the request is successful, callRequest.deliverResponseMethods the feedbackResponse
  • If the request fails, callRequest.deliverErrorMethods the feedbackError
  • If the request is only an intermediate request, the request is not terminated, but it is marked with an “intermediate-response”, otherwise the request is terminated.
  • ifpostResponseThe method passes oneRunnbaleIn, execute theRunnable.

NetworkDispatcher

We’ll see NetworkDispatcher, which inherits Thread. We’ll see its run method first:

@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"); }}}Copy the code

It keeps calling the processRequest method in a loop:

// Extracted to its own method to ensure locals have a constrained liveness scope by the GC.
// This is needed to avoid keeping previous request references alive for an indeterminate amount
// of time. Update consumer-proguard-rules.pro when modifying this. See also
// https://github.com/google/volley/issues/114
private void processRequest() throws InterruptedException {
    // Take a request from the queue.
    Request<?> request = mQueue.take();
    processRequest(request);
}
Copy the code

The Request is first removed from the mQuque (NetQuque in RequestQueue), and although NetworkDispacher is executed concurrently, thread safety is not a concern because BlockingQueue is used.

It can be found that this is a typical producer-consumer model, in which multiple NetworkDispatchers continuously take out requests from NetQueue and make network requests. The user is the producer of network requests, while NetworkDispatcher is the consumer of network requests.

The processRequest method continues by calling the processRequest(Request) method:

void processRequest(Request<? > request) { long startTimeMs = SystemClock.elapsedRealtime(); / / send REQUEST_NETWORK_DISPATCH_STARTED event request. Adds (RequestQueue. Requestevents. REQUEST_NETWORK_DISPATCH_STARTED);  try { request.addMarker("network-queue-take"); // If the request has been cancelled, finish it and notify the Listener that the Response is uselessif (request.isCanceled()) {
            request.finish("network-discard-cancelled");
            request.notifyListenerResponseNotUsable();
            return; } addTrafficStatsTag(request); NetworkResponse NetworkResponse = mnetwork.performRequest (request); request.addMarker("network-http-complete"); // If 304 is returned and we have delivered Reponse, finish it and notify the Listener that Response is uselessif (networkResponse.notModified && request.hasHadResponseDelivered()) {
            request.finish("not-modified");
            request.notifyListenerResponseNotUsable();
            return; } // Convert response < response? > response = request.parseNetworkResponse(networkResponse); request.addMarker("network-parse-complete"); // Write to cache if applicableif(request.shouldCache() && response.cacheEntry ! = null) { mCache.put(request.getCacheKey(), response.cacheEntry); request.addMarker("network-cache-written"); } // Deliver Response via ExecutorDelivery and mark request as delivered Request.markdelivered (); mDelivery.postResponse(request, response); / / notification Listener has obtained the Response request. NotifyListenerResponseReceived (Response); } catch (VolleyError volleyError) { volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs); / / delivery Error information and notify the Listener Response not 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); // Submit Error message and notify Listener Response that mDelivery. PostError (request, volleyError) is not used; request.notifyListenerResponseNotUsable(); } finally {// Send the REQUEST_NETWORK_DISPATCH_FINISHED event request.sendEvent(RequestQueue.RequestEvent.REQUEST_NETWORK_DISPATCH_FINISHED); }}Copy the code

The above code is quite long, and we can break it down into the following steps:

  1. sendREQUEST_NETWORK_DISPATCH_STARTEDEvent indicating that the request has started
  2. ifrequestHas been cancelled, thenfinishIt also notifies the ListenerResponseNo use. No further action
  3. callmNetwork.performRequestInitiate a synchronization request to retrieveResponse
  4. If 304 comes back and we’ve deliveredReponse,finishIt also notifies the ListenerResponseNo use. No further action
  5. rightResponseconvert
  6. If theRequestCan be cached toRequestAs the key,ResponseCaching for value
  7. throughExecutorDeliveryResponseDeliver and willRequestMark as delivered
  8. The notification Listener has been obtainedResponse
  9. If an exception occurs, deliver an Error message and notify the ListenerResponseNo use, all errors need to be converted toVollyErrorClass.
  10. sendREQUEST_NETWORK_DISPATCH_FINISHEDEvent indicating that the request has ended

We can get some information from the above steps:

  1. A set ofEventMechanisms are used to transmit various events
  2. NetworkClass is the real implementation class for network requests.
  3. ResponseThere is a conversion problem
  4. There is a set ofResponseCaching mechanism.

CacheDispatcher

CacheDispatcher also inherits from Thread, and we’ll start with its run method:

@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"); }}}Copy the code

It is also constantly calling the processRequest method:

private void processRequest() throws InterruptedException { // Get a request from the cache triage queue, blocking until // at least one is available. final Request<? > request = mCacheQueue.take(); processRequest(request); }Copy the code

Here, as in NetworkDispatcher, we fetch the Request to be requested from the mCacheQueue and see processRequest(Request) :

void processRequest(final Request<? > request) throws InterruptedException { request.addMarker("cache-queue-take"); / / send the Event request. Adds (RequestQueue. Requestevents. REQUEST_CACHE_LOOKUP_STARTED); Try {// If the request is cancelled, finish itif (request.isCanceled()) {
            request.finish("cache-discard-canceled");
            return; } // Request attempts to obtain Entry cache. Entry Entry = mCache. Get (request.getcacheKey ());if(Entry == NULL) {// If the cache is not matched, it is put into mNetworkQueue to wait for network request request.addMarker("cache-miss");
            // Cache miss; send off to the network dispatcher.
            if(! mWaitingRequestManager.maybeAddToWaitingRequests(request)) { mNetworkQueue.put(request); }return; } // If the cache expires, mNetworkQueue is placed to wait for network requestsif (entry.isExpired()) {
            request.addMarker("cache-hit-expired");
            request.setCacheEntry(entry);
            if(! mWaitingRequestManager.maybeAddToWaitingRequests(request)) { mNetworkQueue.put(request); }return; } // Cache hit, transform and get Response request.addmarker ("cache-hit"); Response<? > response = request.parseNetworkResponse( new NetworkResponse(entry.data, entry.responseHeaders)); request.addMarker("cache-hit-parsed");
        if(! Entry. RefreshNeeded ()) {// If the response does not need to be refreshed, simply deliver response mdelivery.postResponse (request, response); }else{// If the cache expires, the current response will be set as the middle response and the response will be delivered // When the response is delivered successfully, it will be put into mNetworkQueue and wait for the network request 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 deliverythen forward the request along to the network.
                mDelivery.postResponse(
                        request,
                        response,
                        new Runnable() {
                            @Override
                            public void run() { try { mNetworkQueue.put(request); } catch (InterruptedException e) { 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); } } } finally { request.sendEvent(RequestQueue.RequestEvent.REQUEST_CACHE_LOOKUP_FINISHED); }}Copy the code

The code is relatively long, it mainly has the following steps:

  1. ifRequestCancelled, directfinishIt.
  2. throughRequestTrying to get cachedResponseThe correspondingEntry.
  3. If the cache does not hit, it is placedmNetworkQueueWaiting for a network request.
  4. If the cache expires, it is put inmNetworkQueueWaiting for a network request.
  5. If the cache hits, convert and obtainResponse.
  6. If you getResponseNo need to refresh, direct deliveryResponse.
  7. If the testResponseIf the cache is found to be expired, the currentResponseSet to the middleResponse,ResponseDelivery. One is passed inRunnablewhenResponseUpon successful delivery, theRequestIn themNetworkQueueWaiting for a network request.

In simple terms, if the cache hits and the Response is not expired, it is delivered directly, otherwise it is put into mNetworkQueue to wait for the network request.

Request

Many of the mechanisms mentioned above are related to requests. To get a better understanding, let’s leave out how a Network initiates a Network Request for now. Let’s look at the composition of a Request first.

Request is just an abstract class with several implementations as follows:

  • StringRequestReturns theStringRequest.
  • JsonRequest:JsonObjectRequestJsonArrayRequestThe base class.
  • JsonObjectRequestReturns theJsonObjectRequest.
  • JsonArrayRequestReturns theJsonArrayRequest.
  • ImageRequest: returns the imageRequest.
  • ClearCacheRequest: one used to clear the cacheRequestIs not used to make network requestsRequest.

build

To construct a Request, we can use its constructor. Different subclasses have different constructors. The main difference is that the Listener passed in to StringRequest is different.

public StringRequest(int method, String url, Listener<String> listener, @Nullable ErrorListener errorListener) {
    super(method, url, errorListener);
    mListener = listener;
}
Copy the code

They all call the Request(method, URL, errorListener) constructor:

public Request(int method, String url, @Nullable Response.ErrorListener listener) {
    mMethod = method;
    mUrl = url;
    mErrorListener = listener;
    setRetryPolicy(new DefaultRetryPolicy());
    mDefaultTrafficStatsTag = findDefaultTrafficStatsTag(url);
}
Copy the code

If you want to create a POST request and pass in parameters, you need to override its getParams method:

protected Map<String, String> getParams() throws AuthFailureError {
    return null;
}
Copy the code

cancel

Canceling a Request is as simple as setting mCanceled to true:

public void cancel() {
    synchronized (mLock) {
        mCanceled = true; mErrorListener = null; }}Copy the code

The Response conversion

Request’s parseNetworkResponse method is an abstract method that requires different subclasses to implement.

For example, StringRequest is implemented as follows:

@Override
@SuppressWarnings("DefaultCharset")
protected Response<String> parseNetworkResponse(NetworkResponse response) {
    String parsed;
    try {
        parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
    } catch (UnsupportedEncodingException e) {
        // Since minSdkVersion = 8, we can t call
        // new String(response.data, Charset.defaultCharset())
        // So suppress the warning instead.
        parsed = new String(response.data);
    }
    return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
}
Copy the code

Very simple, convert response. data to the corresponding String.

The Response of the delivery

DeliverResponse is an abstract method that needs to be implemented by different subclasses. What it does is to call the onResponse method of the Listener and pass in the transformed object.

For example, StringRequest is implemented as follows:

@Override
protected void deliverResponse(String response) {
    Response.Listener<String> listener;
    synchronized (mLock) {
        listener = mListener;
    }
    if (listener != null) {
        listener.onResponse(response);
    }
}
Copy the code

The Error of the delivery

The delivery of Error is implemented in the Request, which is also very simple, essentially calling the onErrorResponse method of the ErrorListener:

public void deliverError(VolleyError error) {
    Response.ErrorListener listener;
    synchronized (mLock) {
        listener = mErrorListener;
    }
    if (listener != null) {
        listener.onErrorResponse(error);
    }
}
Copy the code

The end of the

/**
 * Notifies the request queue that this request has finished (successfully or with error).
 *
 * <p>Also dumps all events from this request s event log; for debugging.
 */
void finish(final String tag) {
    if(mRequestQueue ! = null) { mRequestQueue.finish(this); }if (MarkerLog.ENABLED) {
        final long threadId = Thread.currentThread().getId();
        if(Looper.myLooper() ! = Looper.getMainLooper()) { // If we finish marking off of the main thread, we need to // actuallydo it on the main thread to ensure correct ordering.
            Handler mainThread = new Handler(Looper.getMainLooper());
            mainThread.post(
                    new Runnable() {
                        @Override
                        public void run() { mEventLog.add(tag, threadId); mEventLog.finish(Request.this.toString()); }});return; } mEventLog.add(tag, threadId); mEventLog.finish(this.toString()); }}Copy the code

This is done by calling the requestQueue.Finish method, which removes the current Request from the ongoing Request queue.

The cache

Request. ShouldCache is called before the Response is cached.

/** Whether or not responses to this request should be cached. */
// TODO(#190): Turn this off by default for anything other than GET requests.
private boolean mShouldCache = true;
Copy the code

As you can see, caching is not allowed except for GET requests.

Let’s look at how the key in the cache is converted from Request:

public String getCacheKey() {
    String url = getUrl();
    // If this is a GET request, just use the URL as the key.
    // For callers using DEPRECATED_GET_OR_POST, we assume the method is GET, which matches
    // legacy behavior where all methods had the same cache key. We can t determine which method
    // will be used because doing so requires calling getPostBody() which is expensive and may
    // throw AuthFailureError.
    // TODO(#190): Remove support for non-GET methods.
    int method = getMethod();
    if (method == Method.GET || method == Method.DEPRECATED_GET_OR_POST) {
        return url;
    }
    return Integer.toString(method) + The '-' + url;
}
Copy the code

Since only GET requests are cached in the new Volley, Request is cached using urls.

The Event mechanism

Instead of focusing on the Network implementation, let’s take a look at what happens after the Event is emitted.

Adds the after transfer to RequestQueue. SendRequestEvent:

void sendRequestEvent(Request<? > request, @RequestEvent int event) { synchronized (mEventListeners) {for(RequestEventListener listener : mEventListeners) { listener.onRequestEvent(request, event); }}}Copy the code

As you can see, our user can register a RequestEventListener in the RequestQueue to listen for request-related events.

Response

Compared with Request, Response is much simpler (I feel that many functions of Response are placed in Request), which mainly carries some relevant information after the end of the Request.

public class Response<T> {

    /** Callback interface for delivering parsed responses. */
    public interface Listener<T> {
        /** Called when a response is received. */
        void onResponse(T response);
    }

    /** Callback interface for delivering error responses. */
    public interface ErrorListener {
        /**
         * Callback method that an error has been occurred with the provided error code and optional
         * user-readable message.
         */
        void onErrorResponse(VolleyError error);
    }

    /** Returns a successful response containing the parsed result. */
    public static <T> Response<T> success(T result, Cache.Entry cacheEntry) {
        return new Response<>(result, cacheEntry);
    }

    /**
     * Returns a failed response containing the given error code and an optional localized message
     * displayed to the user.
     */
    public static <T> Response<T> error(VolleyError error) {
        return new Response<>(error);
    }

    /** Parsed response, or null in the case of error. */
    public final T result;

    /** Cache metadata for this response, or null in the case of error. */
    public final Cache.Entry cacheEntry;

    /** Detailed error information if<code>errorCode ! = OK</code>. */ public final VolleyError error; /** Trueif this response was a soft-expired one and a second one MAY be coming. */
    public boolean intermediate = false;

    /** Returns whether this response is considered successful. */
    public boolean isSuccess() {
        returnerror == null; } private Response(T result, Cache.Entry cacheEntry) { this.result = result; this.cacheEntry = cacheEntry; this.error = null; } private Response(VolleyError error) { this.result = null; this.cacheEntry = null; this.error = error; }}Copy the code

Network

From the previous analysis, we can see that the real Network request is implemented through the Network class, which is an abstract class with only one subclass, BaseNetwork.

We’ll focus on its performRequest method:

@Override public NetworkResponse performRequest(Request<? > request) throws VolleyError { long requestStart = SystemClock.elapsedRealtime();while (true) {// HttpResponse = null; byte[] responseContents = null; List<Header> responseHeaders = Collections.emptyList(); AdditionalRequestHeaders = getCacheHeaders(request.getcacheEntry ()); additionalRequestHeaders = getCacheHeaders(request.getcacheEntry ()); / / by httpStack executeRequest to synchronize perform network request httpResponse = mBaseHttpStack. ExecuteRequest (request, additionalRequestHeaders); int statusCode = httpResponse.getStatusCode(); responseHeaders = httpResponse.getHeaders(); // If there is no Modified, build a NetworkResponse and return itif (statusCode == HttpURLConnection.HTTP_NOT_MODIFIED) {
                Entry entry = request.getCacheEntry();
                if (entry == null) {
                    return new NetworkResponse(
                            HttpURLConnection.HTTP_NOT_MODIFIED,
                            /* data= */ null,
                            /* notModified= */ true, SystemClock.elapsedRealtime() - requestStart, responseHeaders); } // Combine the header of response with the cache entry, Headers List<Header> combinedHeaders = responseHeaders;return new NetworkResponse(
                        HttpURLConnection.HTTP_NOT_MODIFIED,
                        entry.data,
                        /* notModified= */ true, SystemClock.elapsedRealtime() - requestStart, combinedHeaders); } InputStream InputStream = httpresponse.getContent ();if(inputStream ! = null) { responseContents = inputStreamToBytes(inputStream, httpResponse.getContentLength()); }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, statusCode);
            if(statusCode < 200 || statusCode > 299) { throw new IOException(); } // Return the Response constructed by the request resultreturn new NetworkResponse(
                    statusCode,
                    responseContents,
                    /* notModified= */ false,
                    SystemClock.elapsedRealtime() - requestStart,
                    responseHeaders);
        } catch (SocketTimeoutException e) {
            attemptRetryOnException("socket", request, new TimeoutError());
        } catch (MalformedURLException e) {
            throw new RuntimeException("Bad URL " + request.getUrl(), e);
        } catch (IOException e) {
            int statusCode;
            if(httpResponse ! = null) { statusCode = httpResponse.getStatusCode(); }else {
                throw new NoConnectionError(e);
            }
            VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl());
            NetworkResponse networkResponse;
            if(responseContents ! = null) { networkResponse = new NetworkResponse( statusCode, responseContents, /* notModified= */false,
                                SystemClock.elapsedRealtime() - requestStart,
                                responseHeaders);
                if (statusCode == HttpURLConnection.HTTP_UNAUTHORIZED
                        || statusCode == HttpURLConnection.HTTP_FORBIDDEN) {
                    attemptRetryOnException(
                            "auth", request, new AuthFailureError(networkResponse));
                } else if (statusCode >= 400 && statusCode <= 499) {
                    // Don t retry other client errors.
                    throw new ClientError(networkResponse);
                } else if (statusCode >= 500 && statusCode <= 599) {
                    if (request.shouldRetryServerErrors()) {
                        attemptRetryOnException(
                                "server", request, new ServerError(networkResponse));
                    } else{ throw new ServerError(networkResponse); }}else{ // 3xx? No reason to retry. throw new ServerError(networkResponse); }}else {
                attemptRetryOnException("network", request, new NetworkError()); }}}}Copy the code

The code here is very long and consists mainly of the following steps:

  1. Loop through the request until an error occurs or the request succeeds
  2. Get Headers
  3. After throughmBaseHttpStackMaking network requests
  4. If the request result does notModified, the returned Headers is built with the cached informationNetworkResponseAnd return.
  5. Finally, if the request is successful, buildNetworkResponseAnd return.
  6. If an error occurs, a retry will be attempted if the error is retried (such as a 3XX status code or a timeout).
  7. Otherwise, the corresponding error will be built based on the specific errorErrorResponseAnd return.

HttpStack

HttpStack is the class where we actually perform network requests. It has two implementations: HurlStack, which is based on HttpUrlConnection, and HttpClientStack, which is based on HttpClient.

Since HttpClient has been completely abandoned and there are almost no machines below SDK 9, we just need to analyze the HurlStack and see its executeRequest method:

@Override public HttpResponse executeRequest(Request<? > request, Map<String, String> additionalHeaders) throws IOException, AuthFailureError { String url = request.getUrl(); HashMap<String, String> map = new HashMap<>(); map.putAll(additionalHeaders); // Request.getHeaders() takes precedence over the given additional (cache) headers). map.putAll(request.getHeaders());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);
    boolean keepConnectionOpen = false;
    try {
        for (String headerName : map.keySet()) {
            connection.setRequestProperty(headerName, map.get(headerName));
        }
        setConnectionParametersForRequest(connection, request);
        // Initialize HttpResponse with data from the HttpURLConnection.
        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.");
        }
        if(! hasResponseBody(request.getMethod(), responseCode)) {return new HttpResponse(responseCode, convertHeaders(connection.getHeaderFields()));
        }
        // Need to keep the connection open until the stream is consumed by the caller. Wrap the
        // stream such that close() will disconnect the connection.
        keepConnectionOpen = true;
        return new HttpResponse(
                responseCode,
                convertHeaders(connection.getHeaderFields()),
                connection.getContentLength(),
                new UrlConnectionInputStream(connection));
    } finally {
        if(! keepConnectionOpen) { connection.disconnect(); }}}Copy the code

The HttpUrlConnection API is called to make a network request.

Caching mechanisms

The Cache mechanism of Volley is based on the Cache interface, which exposes common Cache operation interfaces such as PUT and GET. By default, DiskBasedCache is used.

This section is mainly some of the file reading and writing, temporarily not research, interested readers can read by themselves.

conclusion

To be called in ApplicationnewRequestQueueWhile not being affected by StrictMode rules for manipulating files, Volley uses oneFileSupplierCome to the rightFileFor packaging, it takes a lazy creation approach, creating the corresponding ones only when neededcacheDirfile

RequestQuque maintains three queues: a pending cache queue, a pending network request queue, and a pending request queue.

RequestQueue, by default, maintains four NetworkDispatchers and one CacheDispatcher, all of which inherit from Thread to make network requests more efficient asynchronously.

The success or failure of the Request is delivered via ExecutorDelivery, which is then notified to various listeners via Request.

There is a caching mechanism that uses Request as the Key and Response as the Value. Only GET requests need to be cached. All GET requests are first placed in the mCacheQueue, and then in the mNetQueue for network requests when the cache fails or expires.

Network is a class that wraps the execution of Network requests and is responsible for the conversion of Response and support for retry.

HttpStack is the class that actually performs network requests. It does network requests based on HttpUrlConnection in versions higher than SDK 9, otherwise it does network requests based on HttpClient.

Cache implements Volley caching. By default, DiskBasedCache is used.

Volley does not have as flexible an interceptor mechanism as OkHttp, but it does provide a number of listeners for outsiders to listen in on the request process.