( 一 ) Volley 源码深入了解之 RequestQueue

一、为什么选 volley ?

说起 volley 还真是有点年头了,在 okHttp 未出之前 volley 其实也是个很火的框架,不过随着移动端日新月异的发展速度,使用的人越来越少却是不争的事实,当然本篇博客也不是要说这些,优秀的源码总是学习的最佳案例尤其是 Google 出品的 volley ,短小精悍的体积相比一些动辄十几个包成千上百个类的框架源码,无疑是非常适合用来阅读学习的。技术的发展以及需求的多变使得各种各样的框架层出不穷,但变得其实只是外面,里子的思想是支持外在变化的根本,对于编程人员来说这更是通往高层次的必经之路。

二、Volley 介绍

Volley 是 Google 在 2013 年在 Google I/O 大会上推出的一款轻量级网络请求框架,Volley 的设计目标是针对那些数据量不大却通信频繁的请求比如新闻类 App 、社交类App、Volley 在这方面的性能非常高效而且简单易用,抛却了 HttpUrlConnection 那繁琐的用法,但Volley不适合大型下载或流式传输操作,因为Volley在解析期间会将所有响应都保存在内存中,所以如果用 Volley 来进行下载文件的话那么表现可能就很糟糕了。

三、Volley 的工作流程

image.png

上图是 Google 官方给出的 Volley 工作流程图,从图中可以很清楚的看到 Volley 从 request 到 response 之间的一系列工作流程,可以看到当创建了一个 request 时 Volley 会把这个 request 加入到队列中,然后缓存调度器会从缓存队列中将当前 request 出队并判断这个 request 是否被缓存过,如果有则直接读取缓存中的数据并解析传递给主线程,否则会交由 NetworkDispatcher 也就是网络调度器处理,网络调度器同样也会将当前 request 从请求队列中出队并发起 Http 请求获取数据并解析传递给主线程,至此 Volley 从 request 到 response 的过程就结束了

四、从源码层面深入了解 Volley

4.1、如何阅读 Volley 源码?

从上一节中我们其实只是粗略的了解了 Volley 的总体设计思想与工作流程,内部的具体工作还是不知所云,毕竟光凭一张设计图是无法建成高楼大厦的,所以如果想真的想彻底搞懂这张流程图的话,只有深入到代码层面了,而要想在繁复纷杂的代码中找到头绪并进行阅读,从调用的流程及使用方式入手无疑是最好的方式之一,下面我们来看看如何使用 Volley 发起一个简单的请求

    String url = "https://www.baidu.com";
    RequestQueue mRequestQueue = Volley.newRequestQueue(getApplicationContext());//创建一个请求队列
    StringRequest request = new StringRequest(Request.Method.GET, url, new Response.Listener() {
        @Override
        public void onResponse(String s) {
            Toast.makeText(getApplicationContext(),s,Toast.LENGTH_SHORT).show();
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError volleyError) {
                Toast.makeText(getApplicationContext(),volleyError.toString(),Toast.LENGTH_SHORT).show();
            }
        });
    request.setTag("volley");
    mRequestQueue.add(request);
    mRequestQueue.start();

可以看到 volley 发起一个请求的过程并不复杂,首先通过 Volley 的静态方法 newRequestQueue 方法创建一个请求队列(由于 volley 的特性一般情况下全局只需要创建一个请求队里即可),接下来创建一个 request 对象并将所需的参数传入,当然这个 request 可以是 StringRequest 或 JsonRequest 也可以是你自定义的 request 这个无所谓因为内部的处理流程都是一样的。接下来就是设置 tag 这个 tag 后面会讲,然后就是将 request 添加到请求队列中并 start。好,接下来我们就来按照这个流程逐行分析从请求开始到拿到数据 volley 在内部到底为我们做了什么处理。

4.2、 深入了解 Volley.newRequestQueue 在请求之前做了什么?
4.2.1、 BaseNetwork 和 HurlStack

Volley 在发起请求时肯定要先通过 newReuqestQueue 这个静态方法来初始化获取一个 RequestQueue 对象,那么这个方法内部到底做了什么呢? 进去看看

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

终于见到了真面目,首先第一个参数不用多说是 Context ,第二个参数 BaseHttpStack stack 是干什么用的呢? 让我们重点看看这个方法内部的逻辑,先不管具体的细节大致可以看出主要是在创建 RequestQueue 之前初始化一个基础的网络请求对象也就是 BasicNetwork ,而这里出现了一个判断,当 stack 为 null 时会对系统版本进行判断,当 sdk >= 9 也就是系统大于 2.3 的时候创建的 BasicNetwork 传入的是一个 HurlStack 实例对象,而 sdk < 9 时可以发现过程同样如此,不过不同的是 BasicNetwork 中传入的是 AndroidHttpClient 的实例化对象,由此我们可以看出在系统 < 2.3 时将会使用 httpClient 作为底层的网络请求对象,由于 httpClient 已被 Android 团队所废弃且在 Android 6.0 版本中删除故不做深入讨论,所以我们重点看看 HurlStack 这个对象,在看之前我画了一张 UML 图,如下


image.png

从图中我们可以很清晰的看到 HurlStack 的继承结构,可以看出 HurlStack 继承自 BaseHttpStack 并重写了内部的 executeRequest 方法,同时以聚合的方式和 BasicNetwork 进行关联成为 BasicNetwork 的一部分,这里插一句题外话,其实 BaseHttpStack 并不是最顶层的类,上面还有一个 HttpStack 接口,内部有一个 performRequest 方法, BaseHttpStack 实现了这个接口并重写了该方法,不过为什么我没有在 UML 中体现呢? 因为这个 HttpStack 接口已被废弃了, 2.3 以后已经完全抛弃了这个接口改用 BaseHttpStack 这个抽象类,所以我就没关注了。那么闲话少说,让我们按照这个类图结构看看它们是如何初始化一个 BasicNetwork 的

//省略无关代码
public class BasicNetwork implements Network {
  /**
     * @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;
    }

    @Override
    public NetworkResponse performRequest(Request request) throws VolleyError {}
}

BasicNetwork 实现了 Network 接口并重写了 performRequest 方法,且共有四个重载方法,但最终会调用到上述的构造方法,可以看到这里主要的作用是初始化一些本地的全局变量,包括继承了 BaseHttpStack 的子类, performRequest 这个方法不难猜出是用来执行 request 的方法

@Override
    public NetworkResponse performRequest(Request request) throws VolleyError {
        while (true) {
            HttpResponse httpResponse = null;
            byte[] responseContents = null;
            List
responseHeaders = Collections.emptyList(); try { // Gather headers. Map additionalRequestHeaders = getCacheHeaders(request.getCacheEntry()); //通过传入的 HurlStack 发起网络请求并获取 response httpResponse = mBaseHttpStack.executeRequest(request, additionalRequestHeaders); int statusCode = httpResponse.getStatusCode(); responseHeaders = httpResponse.getHeaders(); // 这里对缓存的有效性进行校验 if (statusCode == HttpURLConnection.HTTP_NOT_MODIFIED) { //获取缓存 Entry entry = request.getCacheEntry(); if (entry == null) {//为空则将未修改的标记置为 true,data 置为null return new NetworkResponse( HttpURLConnection.HTTP_NOT_MODIFIED, /* data= */ null, /* notModified= */ true, SystemClock.elapsedRealtime() - requestStart, responseHeaders); } // Combine cached and response headers so the response will be complete. List
combinedHeaders = combineHeaders(responseHeaders, entry); //将缓存的 response 直接返回 return new NetworkResponse( HttpURLConnection.HTTP_NOT_MODIFIED, entry.data, /* notModified= */ true, SystemClock.elapsedRealtime() - requestStart, combinedHeaders); } // Some responses such as 204s do not have content. We must check. InputStream inputStream = httpResponse.getContent(); //如果没有缓存获取服务端返回的输入流并转换成字节封装成 NetworkResponse 返回 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 new NetworkResponse( statusCode, responseContents, /* notModified= */ false, SystemClock.elapsedRealtime() - requestStart, responseHeaders); } catch (Exception e){ //...... 省略部分代码} } }

代码里中文注释已经解释的很清楚了,其实这个方法就干了这么一件事就是把从网络或者缓存中获取的 response 包装成 NetworkResponse return 出去,代码并不复杂,我们重点看下这行代码

//通过传入的 HurlStack 发起网络请求并获取 response
httpResponse = mBaseHttpStack.executeRequest(request, additionalRequestHeaders);

可以看到其实这一行才是真正执行请求的代码,它是由传入的 HurlStack 实例对象中的方法实现的,进入 HurlStack 对象找到该方法继续跟进

    @Override
    public HttpResponse executeRequest(Request request, Map additionalHeaders)
            throws IOException, AuthFailureError {
        String url = request.getUrl();
        HashMap 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);
        // SDK >= 9 底层使用 HttpUrlConnection 进行网络请求
        HttpURLConnection connection = openConnection(parsedUrl, request);
        boolean keepConnectionOpen = false;
        try {
             //省略若干代码....
            keepConnectionOpen = true;
            return new HttpResponse(
                    responseCode,
                    convertHeaders(connection.getHeaderFields()),
                    connection.getContentLength(),
                    new UrlConnectionInputStream(connection));
        } finally {
            if (!keepConnectionOpen) {
                connection.disconnect();
            }
        }
    }

代码进行了精简方便阅读,可以从这个方法中很明显的看到 2.3 以后底层使用的是 HttpUrlConnection进行的网络请求。致此,上面的那张 UML 图大家应该有了更深的了解,可以看到BaseNetWork 并不关心底层网络请求的细节,而是通过 BaseHttpStack 子类来完成具体的网络请求,所以到这里你应该能明白 CustomOneStack、CustomTwoStack 所代表的的意思。如果你不喜欢 HttpUrlConnection 或者 HttpClient 那么你完全可以继承 BaseHttpStack 来定制你喜欢的网络请求框架,Volley 充分尊重你的意见。从这里我们也能看到其中优秀的面向对象思想,即使用方式和具体实现进行了分离,使用者对于底层的实现是完全无感知的,这种易于扩展的思想是非常值得学习并在实际工作中加以运用的。

4.2.2、Volley 的缓存管理 DiskBasedCache

接下来离创建 RequestQueue 越来越近了,但在此之前又实例化了一个 DiskBasedCache 对象,其实这个 DiskBasedCache 是 Volley 的一个基础缓存类,是 Volley 的 Cache 接口的实现类

    /** 默认的缓存目录名称*/
    private static final String DEFAULT_CACHE_DIR = "volley";

    private static RequestQueue newRequestQueue(Context context, Network network) {
        final Context appContext = context.getApplicationContext();
        // 通过匿名内部类实现 DisBasedCache 的 FileSupplier 接口并重写 get 方法
        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);
        queue.start();
        return queue;
    }

可以看到在创建 RequestQueue 之前传入了两个参数,第一个就是 DisBasedCache 的实例化对象,第二个就是我们前面分析的 BasicNetwork ,这里就不在赘述了。至于 FileSupplier 这个接口从名字就可以看出,它是用来获取缓存文件的路径的,我们进入 DiskBasedCache 的构造函数看看

    /** 默认最大缓存字节大小 */
    private static final int DEFAULT_DISK_USAGE_BYTES = 5 * 1024 * 1024;
    
    public DiskBasedCache(FileSupplier rootDirectorySupplier) {
        this(rootDirectorySupplier, DEFAULT_DISK_USAGE_BYTES);
    }

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

DiskBasedCache 其实一共有四个构造函数,但最终都会调用有两个形参的构造函数,其中第一个就是用来获取缓存目录的 FileSupplier 接口,另一个就是在内存中默认的缓存大小了,从上述代码中可以看出 Volley 默认缓存的最大 size 是 5M。那么这个 DiskBasedCache 能做些什么呢? 让我们看下这个类的结构


image.png

从图中我们可以看到该类下有大量的 get、put、remove、write、read等等方法,不难看出 DiskBasedCache 的主要作用是对缓存的各种操作进行了封装方便外部使用,这类方法没什么好说的,我们挑重点的看看 —— initialize()

    /**
     * Initializes the DiskBasedCache by scanning for all files currently in the specified root
     * directory. Creates the root directory if necessary.
     */
    @Override
    public synchronized void initialize() {
        //通过在构造函数中传入的 FileSupplier 实例对象获取缓存文件目录
        File rootDirectory = mRootDirectorySupplier.get();
        if (!rootDirectory.exists()) {
            if (!rootDirectory.mkdirs()) {//如果不存在且无法创建打印错误 Log
                VolleyLog.e("Unable to create cache dir %s", rootDirectory.getAbsolutePath());
            }
            return;
        }
        //获取该目录下的所有文件
        File[] files = rootDirectory.listFiles();
        if (files == null) {
            return;
        }
        //扫描该目录下的所有文件
        for (File file : files) {
            try {
                long entrySize = file.length();
                //计算文件大小
                CountingInputStream cis =
                        new CountingInputStream(
                                new BufferedInputStream(createInputStream(file)), entrySize);
                try {
                    //获取缓存信息中的 header 
                    CacheHeader entry = CacheHeader.readHeader(cis);
                    entry.size = entrySize;
                    //添加到 Map 集合中
                    putEntry(entry.key, entry);
                } finally {
                    // Any IOException thrown here is handled by the below catch block by design.
                    //noinspection ThrowFromFinallyBlock
                    cis.close();
                }
            } catch (IOException e) {
                //noinspection ResultOfMethodCallIgnored
                file.delete();
            }
        }
    }

注释已经写的很清楚了,可以看到其实就是对缓存文件的一些初始化操作,但要注意的是 initialize 方法此时并未把缓存的 response data 读入内存中,只是读取了 header ,只有调用 get 方法时才会从文件中读取response data, 那么 CacheHeaer 缓存了哪些 Header 呢?

static class CacheHeader {
        /**
         * 缓存文件的大小
         */
        long size;

        /** 缓存对应的 Key */
        final String key;

        /** 这个标记有点类似于文件的 hash 值,用来表示资源的唯一性 */
        final String etag;

        /** 服务端响应的日期*/
        final long serverDate;

        /** 最后一次修改的时间*/
        final long lastModified;

        /** 缓存过期时间如果过期则强制从网络获取数据 */
        final long ttl;

        /** 
         *不同于 ttl,这个是非强制性的缓存过期时间
          在缓存调度器中依然会先从缓存获取数据然后在从网络获取一遍数据
         */
        final long softTtl;

        /** 一个 response 所对应的所有响应头*/
        final List
allResponseHeaders; //省略若干代码....... }

可以看到 CacheHeader 其实就是缓存了每个 response 对应的 Header,其中 key 是用来获取 response data 的标识,而其余的那些 header 则会在 CacheDispatcher 和 NetworkDispatcher 中都有着相应的作用,这里后面会说。下面再来看一个重要的方法 pruneIfNeeded

    /** 缓存最大百分比 */
    static final float HYSTERESIS_FACTOR = 0.9f;      

    /** 处理超过最大缓存字节数时的方法  */
    private void pruneIfNeeded() {
        //缓存总大小 < 最大缓存 size 时方法结束,表明不需要做清除操作
        //反之清除
        if (mTotalSize < mMaxCacheSizeInBytes) {
            return;
        }
        //缓存文件总大小
        long before = mTotalSize;
        //删除的文件数量
        int prunedFiles = 0;
        long startTime = SystemClock.elapsedRealtime();
        //获取所有缓存头信息
        Iterator> iterator = mEntries.entrySet().iterator();
        //遍历所有缓存并计算缓存总大小
        while (iterator.hasNext()) {
            Map.Entry entry = iterator.next();
            CacheHeader e = entry.getValue();
            //通过 key 获取缓存文件并删除
            boolean deleted = getFileForKey(e.key).delete();
            if (deleted) {
                //删除成功,从总体大小中减去这个文件的大小
                mTotalSize -= e.size;
            } else {
                VolleyLog.d(
                        "Could not delete cache entry for key=%s, filename=%s",
                        e.key, getFilenameForKey(e.key));
            }
            //从 Map 集合中删除
            iterator.remove();
            prunedFiles++;
            //直到缓存总大小 < 缓存最大值的 90% 停止循环
            if (mTotalSize < mMaxCacheSizeInBytes * HYSTERESIS_FACTOR) {
                break;
            }
        }

         //省略若干代码......
    }

可以看到 pruneIfNeeded 的主要作用是缓存文件体积的控制,而每次 put 时 pruneIfNeeded 都会被调用。

从初始化到文件释放的实现其实逻辑并不复杂,关键在于缓存的设计思想,Volley 在设计缓存时专门抽象出了一个 Cache 接口且其中包括许多操作缓存的方法体,这就意味着对于 Volley 来说只需要依赖 Cache 这个接口标准而不依赖具体的实现,so !如果你不满意 Volley 提供的默认缓存管理你完全可以自定义一个你满意的缓存策略,在这里我们可以看到 OOP 依赖倒置的原则在这里展现的淋漓尽致,而这就是我们要学习的所谓的里子。

4.2.3、深入了解 RequestQueue

经过前面创建基础的网络请求和缓存对象的工作,终于到了创建 RequestQueue 对象的时候,还真是有点不容易,下面我们就看看这个 RequestQueue 到底是何方神圣

public class RequestQueue {
   /**
     * 这个集合用来记录当前所有请求
     */
    private final Set> mCurrentRequests = new HashSet<>();

    /** 一个带有优先级的缓存队列 */
    private final PriorityBlockingQueue> mCacheQueue = new PriorityBlockingQueue<>();

    /**  一个带有优先级的请求队列 */
    private final PriorityBlockingQueue> mNetworkQueue = new PriorityBlockingQueue<>();

    /** 网络请求线程池默认数量*/
    private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4;

    /** 操作缓存的对象 */
    private final Cache mCache;

    /** 网络请求的对象. */
    private final Network mNetwork;

    /** 一个传递对象,主要作用从网络或缓存的获取的 response 传递给主线程 */
    private final ResponseDelivery mDelivery;

    /** 网络调度器 */
    private final NetworkDispatcher[] mDispatchers;

    /** 缓存调度器*/
    private CacheDispatcher mCacheDispatcher;

    /**
     * 两个形参的构造函数,提供默认的网络调度器的线程池大小
     */
    public RequestQueue(Cache cache, Network network) {
        this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);
    }

    /**
     * 和上一个基本相同,只是可以自己指定网络调度器线程池数量
     */
    public RequestQueue(Cache cache, Network network, int threadPoolSize) {
        this(
                cache,
                network,
                threadPoolSize,
                new ExecutorDelivery(new Handler(Looper.getMainLooper())));
    }

    /**
     * 这是最终被调用的构造器,对传入的实例化对象赋值给本地
     */
    public RequestQueue(
            Cache cache, Network network, int threadPoolSize, ResponseDelivery delivery) {
        mCache = cache;//缓存操作对象
        mNetwork = network;//网络请求对象
        mDispatchers = new NetworkDispatcher[threadPoolSize];//网络调度线程池
        mDelivery = delivery;//response 传递对象
    }

    //省略若干代码.......
}

(注释已对各个部分做了说明,不再赘述) 代码进行了精简便于阅读,可以看到无论 new 那个 RequestQueue 的构造函数,最终都会调用最后一个,而最后一个构造函数对所需要的对象都进行了初始化,方便全局使用。为了更好的了解 RequestQueue 让我们回顾一下 Volley 的用法

    String url = "https://www.baidu.com";
    RequestQueue mRequestQueue = Volley.newRequestQueue(getApplicationContext());//创建一个请求队列
    StringRequest request = new StringRequest(Request.Method.GET, url, new Response.Listener() {
        @Override
        public void onResponse(String s) {
            Toast.makeText(getApplicationContext(),s,Toast.LENGTH_SHORT).show();
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError volleyError) {
                Toast.makeText(getApplicationContext(),volleyError.toString(),Toast.LENGTH_SHORT).show();
            }
        });
    request.setTag("volley");
    mRequestQueue.add(request);
    mRequestQueue.start();

可以看到 request 对象加入了一个 tag 后,接下来便会被 RequestQueue 添加,然后就启动请求了,说到这里我来解释下这个 tag 的作用,request.setTag 后相当于把这个请求打了个标签,如果你想取消这个请求就可以通过 RequestQueue 的方法来进行取消,代码如下

public class RequestQueue {
    /**
     * 根据 tag 取消相应的请求
     */
    public void cancelAll(final Object tag) {
        if (tag == null) {
            throw new IllegalArgumentException("Cannot cancelAll with a null tag");
        }
        cancelAll(
                new RequestFilter() {
                    @Override
                    public boolean apply(Request request) {
                        return request.getTag() == tag;
                    }
                });
     }

   /**
     * 线程安全的方法
     */
    public void cancelAll(RequestFilter filter) {
        synchronized (mCurrentRequests) {
            for (Request request : mCurrentRequests) {
                if (filter.apply(request)) {
                    request.cancel();
                }
            }
        }
     }
     //省略若干代码
}

可以看到最终会调用 cancelAll(RequestFilter filler) 这个方法,接收一个 实现 RequestFilter 接口的对象,内部的 apply 方法会判断传入的 tag 和 当前 request 设置的 tag 是都相同,如果相同则调用 request 对象的 cancel 方法来取消当前请求。但是到这一步其实请求并未被终止,只是待取消状态,request 的 cancel 方法只是把当前 request 的请求标记置为 true 而已, 在 CacheDispatcher 和 NetworkDispatcher 中才会真正的终止,其中用到的就是 Request 中这两个方法

public abstract class Request implements Comparable> {
    @CallSuper
    public void cancel() {
        synchronized (mLock) {
            mCanceled = true;//将取消的标记字段置为 true
            mErrorListener = null;
        }
    }

    /** 对外提供方法判断当前 request 是否取消*/
    public boolean isCanceled() {
        synchronized (mLock) {
            return mCanceled;
        }
    }
    //省略若干代码......
}

代码很简单就不多说了, request 也创建了, tag 也设置了,接下来就是把 request 添加到 RequestQueue 中,我们来看看这个 add 方法做了什么

public class RequestQueue {
   /**
     * 添加 request 到调度队列中
     */
    public  Request add(Request request) {
        // 将传入的 request 与 RequestQueue 相关联
        request.setRequestQueue(this);
        //加入当前请求的 set 集合中,这个集合会在取消请求的时候用到,详见 cancelAll 方法,线程安全
        synchronized (mCurrentRequests) {
            mCurrentRequests.add(request);
        }

        // 设置request的优先级
        request.setSequence(getSequenceNumber());
        //添加标记
        request.addMarker("add-to-queue");
        sendRequestEvent(request, RequestEvent.REQUEST_QUEUED);

        //如果传入的 request 不需要缓存则添加到请求队列中
        if (!request.shouldCache()) {
            mNetworkQueue.add(request);
            return request;
        }
        //需要缓存则加入缓存队列
        mCacheQueue.add(request);
        return request;
    }
    //省略若干代码........
}

add 这个方法很简单,主要的作用就是把传入的 request 加入到相应的调度队列中,add 完成后就到了最后一步 “start”,进入看看

public class RequestQueue {
   /** 启动所有 dispatcher */
    public void start() {
        stop(); // 启动之前先停止所有 dispathcer
        // 在这里创建了缓存调度器并启动
        mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
        mCacheDispatcher.start();

        // 在这里启动网络调度器线程池(数量默认为4)并全部启动
        for (int i = 0; i < mDispatchers.length; i++) {
            NetworkDispatcher networkDispatcher =
                    new NetworkDispatcher(mNetworkQueue, mNetwork, mCache, mDelivery);
            mDispatchers[i] = networkDispatcher;
            networkDispatcher.start();
        }
    }

    /** 停止调度 */
    public void stop() {
        if (mCacheDispatcher != null) {
            mCacheDispatcher.quit();//调用 Thread 的 interrupt 方法终止线程
        }
        //终止线程池
        for (final NetworkDispatcher mDispatcher : mDispatchers) {
            if (mDispatcher != null) {
                mDispatcher.quit();//同上
            }
        }
    }
}

我们从 “start” 方法中终于看到了官方流程图中 CacheDispatcher 和 NetworkDispatcher 这两个调度器线程被实例化和启动,而至此 Volley.newReqeustQueue 在发起请求前到底为我们做了什么我们已经彻底了解,在此做个小结:当调用 newRequestQueue 方法时会先把基础的网络请求对象也就是 BasicNetwork 和基础的缓存操作对象 DiskBasedCache 实例化,然后才会创建 RequestQueue并将上述连个基础对象通过构造函数传入 ,RequestQueue 并不是一个真的队列,它只是对 queue 进行了一层包装,对外提供一些方法列如把 request 添加到真正的队列中或取消某个请求等等,当调用 start 方法时,CacheDispathcer 和 NetworkDispatcher 线程才会被创建。

PS: ReqeustQueue 分析到这里就结束了,如果你还想深入了解 CacherDispathcer 和 NetworkDispatcher 的话不妨在看看下面这篇博客

( 二 ) Volley 源码深入了解之 Dispatcher

你可能感兴趣的:(( 一 ) Volley 源码深入了解之 RequestQueue)