Volley is probably a fairly old thing. Google released in 2013 IO, but can also learn from the AOSP product of Google engineers after all. Here is a sample code analysis of Volley’s structure and core source code.

// Create a RequestQueue

RequestQueue mQueue = Volley.newRequestQueue(context);

//url

String url = "http//www.baidu.com";

// Return result processing

Response.Listener listener = new Response.Listener<String>() {

@Override

public void onResponse(String response) {

Log.e("TAG",response);

}

};

// Error return result processing

Response.ErrorListener errorListener = new Response.ErrorListener() {

@Override

public void onErrorResponse(VolleyError error) {

Log.e("TAG",error.getMessage(),error);

}

};

/ / Request Request

StringRequest stringRequest = new StringRequest(Request.Method.GET,url,

listener, errorListener);

// Add the request to the RequestQueue

mQueue.add(stringRequest);

Copy the code

There are actually three steps:

  • Create a queue RequestQueue
  • Creating a Request
  • Add the Request Request to the RequestQueue

Create RequestQueue

RequestQueue mQueue = Volley.newRequestQueue(context);



Call the constructor for two arguments:



This method is a little bit long and takes the important part

Mainly for versions 9 and above, BasicNetwork objects are created

Then call newRequestQueue(Context, network);

What does the BasicNetwork object do?



Create a 4*1024 binary array and pass in new HurlStack().

Okay, so I’m going to put this class here, and then I’m going to continue to see what I’m doing here.

Pass in the context and BasicNetwork

newRequestQueue(context, network);



The first line of code will not say, is to create a file on the device to slow down storage;

Mainly look at the following two sentences:

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

queue.start();



Here you see the creation of an ExecutorDelivery Handler that we’re familiar with, and that Handler is the main thread Handler; See what this ExecutorDelivery does:

ExecutorDelivery is all about creating a Handler for the main thread and passing the returned information to the Handler for the main thread. Runnable is for the main thread. Let’s go back to the constructor

public RequestQueue(Cache cache, Network network, int threadPoolSize) {

this(cache, network, threadPoolSize,

new ExecutorDelivery(new Handler(Looper.getMainLooper())));

}

  • Cache is diskbase cache is basically a cache
  • Network is the BasicNetwork is mainly to send HTTP requests, but the real implementation of sending is HurlStack, will take you to see the source code.
  • MDispatchers is creating an array of NetworkDispatcher
  • MDelivery just looked at the code for the main thread Handler to handle the result of the callback

The RequestQueue is created next

queue.start()



stop(); The English note is very clear to ensure that the dispatcher is stopped (whether network or cached) when it is currently executed.

The following code is explained line by line

mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);

  • MCacheQueue is a cache queue
  • MNetworkQueue is a network request queue
  • MCache said diskbase Cache is basically a cache
  • MDelivery just looked at the code for the main thread Handler to handle the result of the callback



Here’s a WaitingRequestManager:



What’s the constructor not doing

mCacheDispatcher.start(); Let’s look at the CacheDispatcher class



If Tread is inherited, start() calls Run ()

@Override

public void run(a) {

if (DEBUG) VolleyLog.v("start new dispatcher");

// Highest priority

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.

finalRequest<? > 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.

if(! mWaitingRequestManager.maybeAddToWaitingRequests(request)) {

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

if(! mWaitingRequestManager.maybeAddToWaitingRequests(request)) {

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.

// Send the request

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(a) {

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

}

}

} catch (InterruptedException e) {

// We may have been interrupted because it was time to quit.

if (mQuit) {

return;

}

}

}

}

Copy the code

The method is a long one, but I’m going to focus on it: first the thread is set to the highest level, then the cache is initialized, and then it’s an infinite loop. A request is pulled from the cache queue, and if the request is already canceled, the loop is closed. Not continue to go down, a entry from the cache mCache, if is empty, mWaitingRequestManager. MaybeAddToWaitingRequests (request), call it. See what it does:

private synchronized boolean maybeAddToWaitingRequests(Request
         request) {

String cacheKey = request.getCacheKey();

// Insert request into stage if there's already a request with the same cache key

// in flight.

if (mWaitingRequests.containsKey(cacheKey)) {

// There is already a request in flight. Queue up.

List<Request<? >> stagedRequests = mWaitingRequests.get(cacheKey);

if (stagedRequests == null) {

stagedRequests = newArrayList<Request<? > > ();

}

request.addMarker("waiting-for-response");

stagedRequests.add(request);

mWaitingRequests.put(cacheKey, stagedRequests);

if (VolleyLog.DEBUG) {

VolleyLog.d("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);

}

return true;

} else {

// Insert 'null' queue for this cacheKey, indicating there is now a request in

// flight.

mWaitingRequests.put(cacheKey, null);

request.setNetworkRequestCompleteListener(this);

if (VolleyLog.DEBUG) {

VolleyLog.d("new request, sending to network %s", cacheKey);

}

return false;

}

}

Copy the code



Since we are making the request for the first time, there is basically no cache, so else part is used:



The cacheKey is only stored for mWaitingRequests, and then the red box in my screenshot returns false;

SetNetworkRequestCompleteListener implementing an interface, it is this:



Let’s go back to the previous one:



Because it returns false, that is, mNetworkQueue.put(request);

The network queue adds a request and breaks out of the loop;

Back to start() in RequestQueue:



Just walked to the red arrow and continued down:

Since the capacity of mDispathcers is set to 4, the cycle is 4 times:

There’s a new NetworkDispatcher

  • MNetworkQueue is a network queue
  • Network is a BasicNetwork that sends HTTP requests, but the real implementation of sending is HurlStack
  • Cache is diskbase cache is basically a cache
  • MDelivery just looked at the code for the main thread Handler to handle the result of the callback

networkDispatcher.start(); Look at the NetworkDispatcher class:



As with the CacheDispatcher class, without further ado, just look at run()

 @Override

public void run(a) {

Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);

while (true) {

// Get the time since system startup including sleep time

long startTimeMs = SystemClock.elapsedRealtime();

Request<? > request;

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

request.notifyListenerResponseNotUsable();

continue;

}

addTrafficStatsTag(request);

// Perform the network request.

// Perform HTTP requests mNetwork is BasicNetwork

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

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

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

}

}

}

Copy the code

NetworkDispatcher is similar to CacheDispatcher except that CacheDispatcher returns results by caching, whereas NetworkDispatcher returns results by sending network requests.

Process.setThreadPriority(Process.THREADPRIORITYBACKGROUND);

The first step is also to set the thread to the highest level; And then it’s an infinite loop;

request = mQueue.take();

Get the Request object from the network collection;



This code is different from CacheDispatcher, see the annotations;

What is mNetwork in this case? In fact, is



BasicNetwork, look at its performRequest() method:

/ * *

* Execute the Request Request

* * /


@Override

public NetworkResponse performRequest(Request request) throws VolleyError
{

// Return the elapsed time including sleep since startup

long requestStart = SystemClock.elapsedRealtime();

while (true) {

HttpResponse httpResponse = null;

byte[] responseContents = null;

List<Header> responseHeaders = Collections.emptyList();

try {

// Gather headers.

Map<String, String> additionalRequestHeaders =

getCacheHeaders(request.getCacheEntry());

// Returns the HTTP result

httpResponse = mBaseHttpStack.executeRequest(request, additionalRequestHeaders);

/ / status code

int statusCode = httpResponse.getStatusCode();

/ / the response header

responseHeaders = httpResponse.getHeaders();

// Handle cache validation.

/ * *

* 304 (server cached and valid) builds a Response on its own

* * /


if (statusCode == HttpURLConnection.HTTP_NOT_MODIFIED) {

/ * *

* 304:

* The Not Modified client has a buffered document and makes a conditional request (typically providing an if-Modified-since header to indicate that the client only wants the document to be updated past the specified date)

* When 304 is returned, one database query has already been done, but more database queries can be avoided.

* and instead of returning the page content, just an HTTP Header,

* Thus greatly reduce the consumption of bandwidth, for the user's feeling is also improved.

* The server automatically completes Last Modified(cached file) and If Modified Since(included in the request)

* * /


Entry entry = request.getCacheEntry();

if (entry == null) {

return new NetworkResponse(HttpURLConnection.HTTP_NOT_MODIFIED, null.true.

SystemClock.elapsedRealtime() - requestStart, responseHeaders);

}

// Combine cached and response headers so the response will be complete.

// Entry is the body of Response

List<Header> combinedHeaders = combineHeaders(responseHeaders, entry);

return new NetworkResponse(HttpURLConnection.HTTP_NOT_MODIFIED, entry.data,

true, SystemClock.elapsedRealtime() - requestStart, combinedHeaders);

}

// Some responses such as 204s do not have content. We must check.

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;

// Record the request time

logSlowRequests(requestLifetime, request, responseContents, statusCode);

if (statusCode < 200 || statusCode > 299) {

throw new IOException();

}

return new NetworkResponse(statusCode, responseContents, 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, 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

For a long paragraph, we look at the core:



You want to get the cache entry through request and get the HTTP header through entry;

then

httpResponse = mBaseHttpStack.executeRequest(request, additionalRequestHeaders);

What is mBaseHttpStack? It’s actually the same as before



So I’ve already revealed that the HurlStack actually performs HTTP requests

Here I do not have more screenshots, if you are interested in Volley source Code HurlStack executeRequest method. In this case, it completes the HTTP request via HttpURLConnection. Believe everyone is familiar with the HttpURLConnection, without using any network request frame Android is not using Apache httpclient is to use Java HttpURLConnection inside.

We continue with BasicNetwork’s performRequest:

Request return to obtain the status code, obtain the returned header information according to the status code to determine if it is 304: indicates that the client has a buffered document and the document information is still valid.

If it’s not a 304 status code,

We get what’s returned and we return NetworkResponse

Go back to NetworkDispatcher: Get the returned NetworkResponse

request.parseNetworkResponse(networkResponse); This code simply converts the returned content through the header encoding information;

In fact, there is a little bit of code here that will cache the returned information, and the next time you have the same request if the status code returns 304 you can cache the content of the network request.

The core code is

mDelivery.postResponse(request, response); This code is essentially the postResponse for ExcutorDelivery

Here is the ResponseDeliveryRunnable run method



It is the deliverResponse or deliverError that returns the result to the Request



Here we just intercept the deliverResponse, and look inside that is actually the listener of our sample code.

The entire Volley request flow is such a flow. Now to sum up:

  • Volley actually completes network requests through HttpURLConnection.
  • The biggest feature of Volley is that it has a cache queue. The cache queue goes first, and the network queue goes after the cache queue has no information.
  • Instead of creating a Thread pool, Volley creates four threads to execute network requests by default.
  • Finally, Volley uses the PriorityBlockingQueue, which is thread-safe to get elements based on their priority. The PriorityBlockingQueue requires a Comparable interface for all elements stored in it.

Finally, PUT a VolleyUML diagram drawn by myself and have a macro understanding of Volley’s structure:

Because only from the point of view of example code source code, some classes are not involved in the look, if there is any problem welcome to correct and learn, common communication.