Android开发笔记:Volley完整源码解析

Volley是一个高效的Android网络请求库,本文详细解析了Volley的源码,包括Cache接口、Network抽象类、ResponseDelivery接口、RequestQueue的构造过程,以及网络调度线程和缓存调度线程的工作原理,特别关注了缓存策略DiskBasedCache的实现和网络请求的处理流程。

Volley源码解析

一些基本的概念

  • Cache类–一种接口,用于实现缓存策略

  • Network–抽象类,用于定义Volley框架中的网络请求接口,所有网络请求都由Network处理

  • ResponseDelivery–一种接口,用于分发请求结果

  • Volley中的两种线程对象(Volley使用缓存+网络请求的方式,Volley会将之前请求过的数据加入缓存,只有当新请求的数据之前未被请求过时才会调用网络调度线程进行网络请求,如果缓存中已经有需要请求的数据(即缓存命中),那么直接调用缓存调度线程直接返回数据):

    • 缓存调度线程
    • 网络调度线程
  • 什么是协议栈:协议栈(Protocol Stack)是指一组网络通信协议的组合,用于在计算机网络中实现数据传输和通信。它通常由多个层次的协议组成,每个层次负责不同的任务。在发送数据时,数据将通过协议栈的各个层级进行封装和处理,然后通过网络进行传输。在接收数据时,协议栈将处理网络传输并将数据解封装后交给应用程序。
    在 Android 开发中,协议栈用于实现应用程序与远程服务器之间的网络通信。不同的协议栈可以使用不同的底层实现,例如 HttpUrlConnection、OkHttp 或 Apache HTTP 等。开发人员可以根据应用程序的需求选择适合的协议栈。

    在这里插入图片描述

newRequestQueue – 创建请求队列

Volley下的newRequestQueue方法

一个参数的构造(最常用的构造)
public static RequestQueue newRequestQueue(Context context) {
    return newRequestQueue(context, (BaseHttpStack) null);
}
两个参数的构造
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);
}


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);
                    }
                    return cacheDir;
                }
            };
    RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheSupplier), network);//1
    queue.start();
    return queue;
}

可以看出,一个参数的构造方法调用的实际上是第一个两个参数的方法,而第一个传入两个参数的方法调用的又是第二个传入两个参数的方法,在第二个方法的标注1处,最终调用了new RequestQueue(new DiskBasedCache(cacheSupplier))方法。在深入探讨new RequestQueue(new DiskBasedCache(cacheSupplier), network)方法之前,让我们先来捋清楚newRequestQueue的流程。

newRequestQueue究竟做了什么?

在上面三个构造方法之中我们可以发现实际的调用流程是:
newRequestQueue(Context context) ->newRequestQueue(Context context, BaseHttpStack stack)->newRequestQueue(Context context, Network network)
由于第一步方法实际上只是执行了第二步方法,所以让我们直接看第二步方法:

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

这个方法的核心目的就是确定Network,或者更加具体的来说,确定协议栈,还记得我们前面基础概念中的:

Network–抽象类,用于定义Volley框架中的网络请求接口,所有网络请求都由Network处理

接下来我们再看第三个newRequestQueue做了什么:

 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);
                    }
                    return cacheDir;
                }
            };
    RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheSupplier), network);//1
    queue.start();
    return queue;
}

核心方法在于注释1处的RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheSupplier), network);这真正调用了RequestQueue的构造方法。

RequestQueue的真正的构造方法

接下来正是RequestQueue的真正的构造方法,如你所见,真正的构造方法被包裹在了层层newRequestQueue中。

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

这是RequestQueue的真正构造方法,传入的几个参数我们一开始都提到过,Cache是缓存策略;Network是网络实现类,也就是之后要说的BasicNetwork类,threadPoolSize是网络调度线程的数量,delivery是用于分发结果的接口。接下来我们将先分析构造方法中的Cache,Network,ResponseDelivery在Volley中的默认实现

BasicNetwork

这个Network就是具体处理网络请求的接口,这个方法中我们获得的Network都是BasicNetwork,不同之处在于其内部传入的BaseHttpStack(协议栈)不同,若SDK版本大于等于九,那么传入的是HurlStack(),SDK版本小于九的情况下传入HttpClientStack()。接着让我们看看BasicNetwork类,由于有几种构造方法已经被废弃,我们这里只看未被废弃的方法,首先看构造方法:

 public BasicNetwork(BaseHttpStack httpStack) {
    // If a pool isn't passed in, then build a small default pool that will give us a lot of
    // benefit and not use too much memory.
    this(httpStack, new ByteArrayPool(DEFAULT_POOL_SIZE));
}

其中的ByteArrayPool是一个管理字节数组池的类,它负责管理字节数组的回收和重用。在网络请求过程中,Volley 库通常需要读取和写入字节数组。为了避免频繁地创建和销毁字节数组,Volley 使用 ByteArrayPool 来管理字节数组的分配和回收,从而提高性能并减少内存占用,正如同注释所示的,它是用来提高GC性能的:

/**
* @param httpStack HTTP stack to be used
* @param pool a buffer pool that improves GC performance in copy operations
*/

public BasicNetwork(BaseHttpStack httpStack, ByteArrayPool pool) {
    mBaseHttpStack = httpStack;
    // Populate mHttpStack for backwards compatibility, since it is a protected field. However,
    // we won't use it directly here, so clients which don't access it directly won't need to
    // depend on Apache HTTP.
    mHttpStack = httpStack;
    mPool = pool;
}

我们传入的BaseHttpStack类是Volley中HTTP协议栈的抽象基类,用于对HTTP请求进行处理。它提供了executeRequest()方法,该方法接收一个Request对象作为参数,用于执行HTTP请求并返回一个NetworkResponse对象。

BaseHttpStack定义了两个具体的子类:HurlStack和HttpClientStack。其中,HurlStack使用了Android平台自带的HttpURLConnection类来执行HTTP请求,而HttpClientStack则使用了Apache的HttpClient来执行HTTP请求。

现在我们的Android设备一般都在SDK9以上,所以基本都是用的HurlStack协议栈,这里我们将只解析HurlStack协议栈的内部。

BasicNetwork如何实现网络请求

主要看它的performRequest方法

public NetworkResponse performRequest(Request<?> request) throws VolleyError {
        long requestStart = SystemClock.elapsedRealtime();
        while (true) {
            HttpResponse httpResponse = null;
            byte[] responseContents = null;
            List<Header> responseHeaders = Collections.emptyList();
            try {
                // Gather headers.
                Map<String, String> additionalRequestHeaders =
                        HttpHeaderParser.getCacheHeaders(request.getCacheEntry());
                httpResponse = mBaseHttpStack.executeRequest(request, additionalRequestHeaders);//1
                int statusCode = httpResponse.getStatusCode();

                responseHeaders = httpResponse.getHeaders();
                // Handle cache validation.  
                if (statusCode == HttpURLConnection.HTTP_NOT_MODIFIED) { //2
                    long requestDuration = SystemClock.elapsedRealtime() - requestStart;
                    return NetworkUtility.getNotModifiedNetworkResponse(
                            request, requestDuration, responseHeaders);
                }

                // Some responses such as 204s do not have content.  We must check.
                InputStream inputStream = httpResponse.getContent();
                if (inputStream != null) {
                    responseContents =
                            NetworkUtility.inputStreamToBytes(
                                    inputStream, httpResponse.getContentLength(), mPool);
                } 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;
                NetworkUtility.logSlowRequests(
                        requestLifetime, request, responseContents, statusCode);

                if (statusCode < 200 || statusCode > 299) {
                    throw new IOException();
                }
                return new NetworkResponse(
                        statusCode,
                        responseContents,
                        /* notModified= */ false,
                        SystemClock.elapsedRealtime() - requestStart,
                        responseHeaders);
            } catch (IOException e) {
                // This will either throw an exception, breaking us from the loop, or will loop
                // again and retry the request.
                RetryInfo retryInfo =
                        NetworkUtility.shouldRetryException(
                                request, e, requestStart, httpResponse, responseContents);
                // We should already be on a background thread, so we can invoke the retry inline.
                NetworkUtility.attemptRetryOnException(request, retryInfo);
            }
        }
    }

刨除其中关于请求头的放置和解析部分,我们来解析如何获取具体数据的。
最重要的部分就是注释1部分的

httpResponse = mBaseHttpStack.executeRequest(request, additionalRequestHeaders);//1

这行代码通过内部的协议栈获取到了网络请求中的HttpResponse部分,紧接着我们看它是如何处理HttpResponse的:

   InputStream inputStream = httpResponse.getContent();
	                if (inputStream != null) {
	                    responseContents =
	                            NetworkUtility.inputStreamToBytes(
	                                    inputStream, httpResponse.getContentLength(), mPool); //2
	                } else {
	                    // Add 0 byte response as a way of honestly representing a
	                    // no-content request.
	                    responseContents = new byte[0];
	                }

从第一行代码我们就可以看出,Httpresponse部分内部应该是有一个输入流的,所以我们要做的就是用这个输入流将数据取出。
这个工作是在注释2处的:

 NetworkUtility.inputStreamToBytes(inputStream, httpResponse.getContentLength(), mPool); //2

做的,正如同该方法的名字一样,这个方法实现的正是将一个输入流所连接的数据所读取并返回一个包含数据的byte数组,这正是我们所需要的数据,需要说明的是,在这个过程中就是使用到了我们之前提到的缓冲区池ByteArrayPool,ByteArrayPool的详细解析我放在(这里)了,最后将其封装进一个新的NetworkResponse对象中返回给调用方:

 return new NetworkResponse(
                    statusCode,
                    responseContents,
                    /* notModified= */ false,
                    SystemClock.elapsedRealtime() - requestStart,
                    responseHeaders);

HurlStack(Volley中的默认协议栈)如何实现网络请求

还记得我们在BasicNetwork中所分析的,BasicNetwork 调用了 HurlStack的executeRequest方法,获取了一个与网络请求数据相关联的输入流

httpResponse = mBaseHttpStack.executeRequest(request, additionalRequestHeaders);//1

我们再来看看其内部是用什么获取到这个输入流的,主要看它的executeRequest方法:

 @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(),
                createInputStream(request, connection));
    } finally {
        if (!keepConnectionOpen) {
            connection.disconnect();
        }
    }
}

除去本人当前还不是很清楚的url重写器部分,我们可以发现HurlStack是通过HttpURLConnection实现网络请求的,它将获取到的与网络数据相关的输入流给包装到HttpRsponse中并将其交给BasicNetwork处理。

Volley默认的网络请求数据的实现

查看了BasicNetwork 和 其内部的HurlStack 的工作流程,我们能大致知道Volley的网络请求到底是怎样实现的
在这里插入图片描述
这样运作的好处就是你也可以实现自己的协议栈,但是同时不影响整个Volley的网络请求的大体流程。

DiskBasedCache

Cache类默认实现是DiskBasedCache。DiskBasedCache使用磁盘作为缓存存储介质,将网络请求的响应数据序列化到磁盘文件中,然后在下一次请求相同URL的响应数据时,从磁盘文件中反序列化响应数据。

老规矩,首先让我们来看它的构造方法:

public DiskBasedCache(FileSupplier rootDirectorySupplier, int maxCacheSizeInBytes) {
    mRootDirectorySupplier = rootDirectorySupplier;
    mMaxCacheSizeInBytes = maxCacheSizeInBytes;
}
public DiskBasedCache(FileSupplier rootDirectorySupplier) {
    this(rootDirectorySupplier, DEFAULT_DISK_USAGE_BYTES);
}

构造方法确定了缓存的最大大小为DEFAULT_DISK_USAGE_BYTES,这个常量的大小为

private static final int DEFAULT_DISK_USAGE_BYTES = 5 * 1024 * 1024;

即5MB。

实际上DiskBasedCache类中并没有什么很特殊的地方,它的功能已经在简介中提到了,在这里我们就说两个最常用的方法。

get方法

@Override
public synchronized Entry get(String key) {
    CacheHeader entry = mEntries.get(key);
    // if the entry does not exist, return.
    if (entry == null) {
        return null;
    }
    File file = getFileForKey(key);
    try {
        CountingInputStream cis =
                new CountingInputStream(
                        new BufferedInputStream(createInputStream(file)), file.length());
        try {
            CacheHeader entryOnDisk = CacheHeader.readHeader(cis);
            if (!TextUtils.equals(key, entryOnDisk.key)) {
                // File was shared by two keys and now holds data for a different entry!
                VolleyLog.d(
                        "%s: key=%s, found=%s", file.getAbsolutePath(), key, entryOnDisk.key);
                // Remove key whose contents on disk have been replaced.
                removeEntry(key);
                return null;
            }
            byte[] data = streamToBytes(cis, cis.bytesRemaining());
            return entry.toCacheEntry(data);
        } finally {
            // Any IOException thrown here is handled by the below catch block by design.
            //noinspection ThrowFromFinallyBlock
            cis.close();
        }
    } catch (IOException e) {
        VolleyLog.d("%s: %s", file.getAbsolutePath(), e.toString());
        remove(key);
        return null;
    }
}

首先得介绍一下CacheHeader:

CacheHeader 类表示缓存条目的元数据。每个条目都包含以下信息:
etag:HTTP 响应头中的 ETag 标记,用于判断缓存是否过期。
lastModified:HTTP 响应头中的 Last-Modified 标记,用于判断缓存是否过期。
serverDate:HTTP 响应头中的 Date 标记,表示服务器响应时间。
ttl:缓存的过期时间,单位为秒。
softTtl:缓存的软过期时间,单位为秒。
responseHeaders:HTTP 响应头。
CacheHeader 实例被保存在 DiskBasedCache 中的索引文件中,用于记录缓存条目的元数据。当需要读取缓存时,可以根据索引文件中的元数据来判断缓存是否过期,并获取缓存数据。当需要写入缓存时,可以将 CacheHeader 对象与缓存数据一起保存到磁盘中。

概括来说,这个方法会先判断缓存中是否存在与key对应的缓存条目,如果查询到了,就打开对应的缓存文件,然后将缓存文件中的数据与一个带缓冲区的输入流相关联。接着再读取该文件下的缓存条目。在读取的同时要确保当前传入的key值和磁盘中存储的key值是一致的,如果不一致,则说明文件被两个不同的键所共享,并且现在保存的是一个不同的缓存条目,因此需要将原先的缓存条目移除。之后调用streamToBytes方法将数据读取并提供一个byte数组,最后将byte数组写入一个键值对中并将其返回,键值对中同时还包含缓存的元数据。

简而言之,该方法就是用来读缓存上的数据的。

put方法

@Override
public synchronized void put(String key, Entry entry) {
    // If adding this entry would trigger a prune, but pruning would cause the new entry to be
    // deleted, then skip writing the entry in the first place, as this is just churn.
    // Note that we don't include the cache header overhead in this calculation for simplicity,
    // so putting entries which are just below the threshold may still cause this churn.
    if (mTotalSize + entry.data.length > mMaxCacheSizeInBytes
            && entry.data.length > mMaxCacheSizeInBytes * HYSTERESIS_FACTOR) {
        return;
    }
    File file = getFileForKey(key);
    try {
        BufferedOutputStream fos = new BufferedOutputStream(createOutputStream(file));
        CacheHeader e = new CacheHeader(key, entry);
        boolean success = e.writeHeader(fos);
        if (!success) {
            fos.close();
            VolleyLog.d("Failed to write header for %s", file.getAbsolutePath());
            throw new IOException();
        }
        fos.write(entry.data);
        fos.close();
        e.size = file.length();
        putEntry(key, e);
        pruneIfNeeded();
    } catch (IOException e) {
        boolean deleted = file.delete();
        if (!deleted) {
            VolleyLog.d("Could not clean up file %s", file.getAbsolutePath());
        }
        initializeIfRootDirectoryDeleted();
    }
}

这段代码就是用来添加缓存的,如果添加新条目会导致缓存满,需要进行清理(prune),但是如果清理后新添加的条目又会被删除,那么为了避免这种不必要的操作,就不要添加新的条目。需要注意的是,在计算空间使用量时,这段代码忽略了缓存头(cache header)的开销,因此即使新条目的大小接近阈值,也有可能会导致这种不必要的操作。

ExecutorDelivery

老规矩,先看构造方法,这里我们只看默认实现的那个构造:

public ExecutorDelivery(final Handler handler) {
    // Make an Executor that just wraps the handler.
    mResponsePoster =
            new Executor() {
                @Override
                public void execute(Runnable command) {
                    handler.post(command);
                }
            };
}

这个构造确定了一个Executor,该Executor使用handle将runnable任务提交到与handle绑定的线程上执行。默认来说,这个handle将会与主线程绑定:

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

正如同Android官网所说:

Volley 始终在主线程上传送已解析的响应。在主线程上运行便于使用接收的数据填充界面控件,因为您可以直接从响应处理程序随意修改界面控件。对于库提供的许多重要语义(特别是与取消请求相关的语义)来说,这尤为重要。

接下来我们来看postResponse方法,用于传输结果:

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

这里涉及到一个内部类,规定了一个任务结构,用于传递最终的结果

private static 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() {
        // 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();
        }
    }
}

简而言之,就是用来传输数据的。

RequestQueue类

到前面位置,我们其他的所有的概念类已经说明完毕了,接下来就可以查看我们的RequestQueue的具体处理流程了。

Add方法

先看最直接的add方法:

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

    beginRequest(request);
    return request;
}

该方法具体分五步走:

  1. 该方法先将请求标记为属于这个请求队列,并将其添加到当前请求的集合中(mCurrentRequests)。
  2. 接着设置请求的序列号(SequenceNumberGenerator)和添加一个 “add-to-queue” 标记(用于调试)。
  3. 然后触发一个 RequestEvent.REQUEST_QUEUED 事件,表示请求已加入队列(这个在Volley中没有具体实现,不过没有影响应该是为了开发人员而留下的)。
  4. 开始请求,调用 beginRequest() 方法。
  5. 返回请求对象。

beginRequest方法(开始请求)

来看请求队列是如何开始请求的:

<T> void beginRequest(Request<T> request) {
    // If the request is uncacheable, skip the cache queue and go straight to the network.
    if (!request.shouldCache()) {
        sendRequestOverNetwork(request);
    } else {
        mCacheQueue.add(request);
    }
}
<T> void sendRequestOverNetwork(Request<T> request) {
    mNetworkQueue.add(request);
}

这个方法的意思就是,如果当前请求可以进行缓存,就将其放入缓存队列中;如果不能进行缓存,就直接将其放入网络队列中。还记得一开始我们说的Volley的大体运行机制吗,可以回到文章开头,或者这里贴出官方的流程:
在这里插入图片描述
在请求时默认是先会经过缓存线程的,不允许缓存就相当于是直接跳过了经过缓存线程的这一步。

start方法(开始工作)

还记得在newRequestQueue方法中,消息队列创建完毕后会直接调用start方法,这就相当于完成了一系列准备工作后,RequestQueue终于开始正式工作了:

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

接下来我们就来解析start方法:

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

start方法创建了一条缓存调度线程和四条网络调度线程(默认情况下),然后使这五条线程开始工作,所以接下来我们看缓存调度线程和网络调度线程具体是怎样工作的。由于这两个类又要牵扯到两个类,为了文章结构的完整,我们这里直接解析每种调度线程的核心方法。

缓存调度线程的核心

void processRequest(final Request<?> request) throws InterruptedException {
    request.addMarker("cache-queue-take");
    request.sendEvent(RequestQueue.RequestEvent.REQUEST_CACHE_LOOKUP_STARTED); 

    try {
        // 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;
        }

        // Use a single instant to evaluate cache expiration. Otherwise, a cache entry with
        // identical soft and hard TTL times may appear to be valid when checking isExpired but
        // invalid upon checking refreshNeeded(), triggering a soft TTL refresh which should be
        // impossible.
        long currentTimeMillis = System.currentTimeMillis();

        // If it is completely expired, just send it to the network.
        if (entry.isExpired(currentTimeMillis)) {
            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 (!response.isSuccess()) {
            request.addMarker("cache-parsing-failed");
            mCache.invalidate(request.getCacheKey(), true);
            request.setCacheEntry(null);
            if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
                mNetworkQueue.put(request);
            }
            return;
        }
        if (!entry.refreshNeeded(currentTimeMillis)) {
            // 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);
            }
        }
    } finally {
        request.sendEvent(RequestQueue.RequestEvent.REQUEST_CACHE_LOOKUP_FINISHED);
    }
}

缓存调度线程的核心方法就是这个processRequest方法,因为我们之前已经学习过缓存策略Cache了,所以这里会更好理解一点。这个方法具体做的事:

    1. 检查请求是否被取消,如果已经被取消,则直接调用 finish 方法结束请求的处理,否则继续进行下一步处理。

      if (request.isCanceled()) {
             request.finish("cache-discard-canceled");
             return;
         }
      
    1. 通过之前说的缓存策略查找缓存中是否缓存了这个请求对应的数据(检查缓存是否命中)。如果缓存未命中,并且如果请求没有被添加到等待请求列表中,将请求放入网络队列,并且接下来其他的步骤都会被跳过。

      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;
         }
      
    1. 如果已经到了这一步,说明缓存命中了。检查缓存条目是否过期,如果缓存条目已经过期,则将请求添加到网络请求队列中。

      if (entry.isExpired(currentTimeMillis)) {
             request.addMarker("cache-hit-expired");
             request.setCacheEntry(entry);
             if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
                 mNetworkQueue.put(request);
             }
             return;
         }
      
    1. 将命中的缓存转成Response类。

         request.addMarker("cache-hit");
         Response<?> response =
                 request.parseNetworkResponse(
                         new NetworkResponse(entry.data, entry.responseHeaders));
         request.addMarker("cache-hit-parsed");
      
    1. 检查得到的Response是否被认为是成功的,如果未成功,将缓存中对应的数据给无效化并且检查等待请求队列中是否有当前请求,若没有,则将其加入到网络队列中。

      if (!response.isSuccess()) {
             request.addMarker("cache-parsing-failed");
             mCache.invalidate(request.getCacheKey(), true);
             request.setCacheEntry(null);
             if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
                 mNetworkQueue.put(request);
             }
             return;
         }
      
    1. 检查得到的Response是否需要刷新,若不需要刷新,就直接通过delivery发送结果;否则就会将请求加入到网络队列中,并且通过delivery发送过去执行(delivery是可以传入runnable对象的)。

      if (!entry.refreshNeeded(currentTimeMillis)) {
             // 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);
             }
         }
      

总而言之,缓存调度线程做的事就是检查缓存是否命中且命中的数据是否有效,若有效就直接返回结果;否则若缓存未命中或者命中的数据无效,就通过网络队列请求数据。

网络调度线程的核心方法

void processRequest(Request<?> request) {
    long startTimeMs = SystemClock.elapsedRealtime();
    request.sendEvent(RequestQueue.RequestEvent.REQUEST_NETWORK_DISPATCH_STARTED);
    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();
    } finally {
        request.sendEvent(RequestQueue.RequestEvent.REQUEST_NETWORK_DISPATCH_FINISHED);
    }
}
  1. 检查请求是否被取消,若被取消就直接通过finish方法处理。

    if (request.isCanceled()) {
             request.finish("network-discard-cancelled");
             request.notifyListenerResponseNotUsable();
             return;
         }
    
  2. 通过之前所说的网络实现Network获取网络请求数据。

    NetworkResponse networkResponse = mNetwork.performRequest(request);
    
  3. 判断网络请求的应答是不是304(即未修改)且网络请求的数据是不是已经被发送给了用户,若两个条件同时满足,则直接通过finish和notifyListenerResponseNotUsable结束操作。

    if (networkResponse.notModified && request.hasHadResponseDelivered()) {
             request.finish("not-modified");
             request.notifyListenerResponseNotUsable();
             return;
         }
    

    这样做的意义就是减少不必要的网络请求,优化性能。

  4. 将响应的数据包装成Response,并且判断这次网络请求是不是能缓存,若可以,就将其通过缓存策略缓存到本地。

    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");
         }
    
  5. 首先标记请求为已传送(即已交付),然后将响应到的数据分派给处理程序,最后通知请求的监听器已收到响应(通知正在等待的相同的请求)。

    request.markDelivered();
         mDelivery.postResponse(request, response);
         request.notifyListenerResponseReceived(response);
    

    这些步骤用于确保请求和响应之间的同步,并向上层应用程序传递请求的响应。

  6. 剩下是一些异常的处理。

     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();
     } finally {
         request.sendEvent(RequestQueue.RequestEvent.REQUEST_NETWORK_DISPATCH_FINISHED);
     }
    

大体结构

这里放一张大体结构图:
在这里插入图片描述

至此,Volley的大部分源码和原理流程就解析完毕了。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值