精华内容
下载资源
问答
  • Volley源码

    2014-09-26 09:59:18
    Volley源码
  • volley源码

    2017-03-08 18:00:39
    本文为 Android 开源项目源码解析 中 Volley 部分 项目地址:Volley,分析的版本:35ce778,Demo 地址:Volley Demo 分析者:grumoon,校对者:huxian99、Trinea,校对状态:完成 1. 功能介绍 1.1. Volley...

    本文为 Android 开源项目源码解析 中 Volley 部分
    项目地址:Volley,分析的版本:35ce778,Demo 地址:Volley Demo
    分析者:grumoon,校对者:huxian99Trinea,校对状态:完成

    1. 功能介绍

    1.1. Volley

    Volley 是 Google 推出的 Android 异步网络请求框架和图片加载框架。在 Google I/O 2013 大会上发布。

    名字由来:a burst or emission of many things or a large amount at once
    发布演讲时候的配图

    从名字由来和配图中无数急促的火箭可以看出 Volley 的特点:特别适合数据量小,通信频繁的网络操作。(个人认为 Android 应用中绝大多数的网络操作都属于这种类型)。

    1.2 Volley 的主要特点

    (1). 扩展性强。Volley 中大多是基于接口的设计,可配置性强。
    (2). 一定程度符合 Http 规范,包括返回 ResponseCode(2xx、3xx、4xx、5xx)的处理,请求头的处理,缓存机制的支持等。并支持重试及优先级定义。
    (3). 默认 Android2.3 及以上基于 HttpURLConnection,2.3 以下基于 HttpClient 实现,这两者的区别及优劣在4.2.1 Volley中具体介绍。
    (4). 提供简便的图片加载工具。

    2. 总体设计

    2.1 总体设计图


    上面是 Volley 的总体设计图,主要是通过两种Dispatch Thread不断从RequestQueue中取出请求,根据是否已缓存调用CacheNetwork这两类数据获取接口之一,从内存缓存或是服务器取得请求的数据,然后交由ResponseDelivery去做结果分发及回调处理。

    2.2 Volley 中的概念

    简单介绍一些概念,在详细设计中会仔细介绍。
    Volley 的调用比较简单,通过 newRequestQueue(…) 函数新建并启动一个请求队列RequestQueue后,只需要往这个RequestQueue不断 add Request 即可。

    Volley:Volley 对外暴露的 API,通过 newRequestQueue(…) 函数新建并启动一个请求队列RequestQueue

    Request:表示一个请求的抽象类。StringRequestJsonRequestImageRequest 都是它的子类,表示某种类型的请求。

    RequestQueue:表示请求队列,里面包含一个CacheDispatcher(用于处理走缓存请求的调度线程)、NetworkDispatcher数组(用于处理走网络请求的调度线程),一个ResponseDelivery(返回结果分发接口),通过 start() 函数启动时会启动CacheDispatcherNetworkDispatchers

    CacheDispatcher:一个线程,用于调度处理走缓存的请求。启动后会不断从缓存请求队列中取请求处理,队列为空则等待,请求处理结束则将结果传递给ResponseDelivery去执行后续处理。当结果未缓存过、缓存失效或缓存需要刷新的情况下,该请求都需要重新进入NetworkDispatcher去调度处理。

    NetworkDispatcher:一个线程,用于调度处理走网络的请求。启动后会不断从网络请求队列中取请求处理,队列为空则等待,请求处理结束则将结果传递给ResponseDelivery去执行后续处理,并判断结果是否要进行缓存。

    ResponseDelivery:返回结果分发接口,目前只有基于ExecutorDelivery的在入参 handler 对应线程内进行分发。

    HttpStack:处理 Http 请求,返回请求结果。目前 Volley 中有基于 HttpURLConnection 的HurlStack和 基于 Apache HttpClient 的HttpClientStack

    Network:调用HttpStack处理请求,并将结果转换为可被ResponseDelivery处理的NetworkResponse

    Cache:缓存请求结果,Volley 默认使用的是基于 sdcard 的DiskBasedCacheNetworkDispatcher得到请求结果后判断是否需要存储在 Cache,CacheDispatcher会从 Cache 中取缓存结果。

    3. 流程图

    Volley 请求流程图

    上图是 Volley 请求时的流程图,在 Volley 的发布演讲中给出,我在这里将其用中文重新画出。

    4. 详细设计

    4.1 类关系图


    这是 Volley 框架的主要类关系图

    图中红色圈内的部分,组成了 Volley 框架的核心,围绕 RequestQueue 类,将各个功能点以组合的方式结合在了一起。各个功能点也都是以接口或者抽象类的形式提供。
    红色圈外面的部分,在 Volley 源码中放在了 toolbox 包中,作为 Volley 为各个功能点提供的默认的具体实现。
    通过类图我们看出, Volley 有着非常好的拓展性。通过各个功能点的接口,我们可以给出自定义的,更符合我们需求的具体实现。

    多用组合,少用继承;针对接口编程,不针对具体实现编程。

    优秀框架的设计,令人叫绝,受益良多。

    4.2 核心类功能介绍

    4.2.1 Volley.java

    这个和 Volley 框架同名的类,其实是个工具类,作用是构建一个可用于添加网络请求的RequestQueue对象。
    (1). 主要函数
    Volley.java 有两个重载的静态方法。

    public static RequestQueue newRequestQueue(Context context)
    
    public static RequestQueue newRequestQueue(Context context, HttpStack stack)
    

    第一个方法的实现调用了第二个方法,传 HttpStack 参数为 null。
    第二个方法中,如果 HttpStatck 参数为 null,则如果系统在 Gingerbread 及之后(即 API Level >= 9),采用基于 HttpURLConnection 的 HurlStack,如果小于 9,采用基于 HttpClient 的 HttpClientStack。

    if (stack == null) {
        if (Build.VERSION.SDK_INT >= 9) {
            stack = new HurlStack();
        } else {
            stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
        }
    }
    

    得到了 HttpStack,然后通过它构造一个代表网络(Network)的具体实现BasicNetwork
    接着构造一个代表缓存(Cache)的基于 Disk 的具体实现DiskBasedCache
    最后将网络(Network)对象和缓存(Cache)对象传入构建一个 RequestQueue,启动这个 RequestQueue,并返回。

    Network network = new BasicNetwork(stack);
    RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
    queue.start();
    return queue;
    

    我们平时大多采用Volly.newRequestQueue(context)的默认实现,构建 RequestQueue。
    通过源码可以看出,我们可以抛开 Volley 工具类构建自定义的 RequestQueue,采用自定义的HttpStatck,采用自定义的Network实现,采用自定义的 Cache 实现等来构建RequestQueue
    优秀框架的高可拓展性的魅力来源于此啊

    (2). HttpURLConnection 和 AndroidHttpClient(HttpClient 的封装)如何选择及原因:
    在 Froyo(2.2) 之前,HttpURLConnection 有个重大 Bug,调用 close() 函数会影响连接池,导致连接复用失效,所以在 Froyo 之前使用 HttpURLConnection 需要关闭 keepAlive。
    另外在 Gingerbread(2.3) HttpURLConnection 默认开启了 gzip 压缩,提高了 HTTPS 的性能,Ice Cream Sandwich(4.0) HttpURLConnection 支持了请求结果缓存。
    再加上 HttpURLConnection 本身 API 相对简单,所以对 Android 来说,在 2.3 之后建议使用 HttpURLConnection,之前建议使用 AndroidHttpClient。

    (3). 关于 User Agent
    通过代码我们发现如果是使用 AndroidHttpClient,Volley 还会将请求头中的 User-Agent 字段设置为 App 的 ${packageName}/${versionCode},如果异常则使用 "volley/0",不过这个获取 User-Agent 的操作应该放到 if else 内部更合适。而对于 HttpURLConnection 却没有任何操作,为什么呢?
    如果用 Fiddler 或 Charles 对数据抓包我们会发现,我们会发现 HttpURLConnection 默认是有 User-Agent 的,类似:

    Dalvik/1.6.0 (Linux; U; Android 4.1.1; Google Nexus 4 - 4.1.1 - API 16 - 768x1280_1 Build/JRO03S)
    

    经常用 WebView 的同学会也许会发现似曾相识,是的,WebView 默认的 User-Agent 也是这个。实际在请求发出之前,会检测 User-Agent 是否为空,如果不为空,则加上系统默认 User-Agent。在 Android 2.1 之后,我们可以通过

    String userAgent = System.getProperty("http.agent");
    

    得到系统默认的 User-Agent,Volley 如果希望自定义 User-Agent,可在自定义 Request 中重写 getHeaders() 函数

    @Override
    public Map<String, String> getHeaders() throws AuthFailureError {
        // self-defined user agent
        Map<String, String> headerMap = new HashMap<String, String>();
        headerMap.put("User-Agent", "android-open-project-analysis/1.0");
        return headerMap;
    }
    

    4.2.2 Request.java

    代表一个网络请求的抽象类。我们通过构建一个Request类的非抽象子类(StringRequest、JsonRequest、ImageRequest 或自定义)对象,并将其加入到·RequestQueue·中来完成一次网络请求操作。
    Volley 支持 8 种 Http 请求方式 GET, POST, PUT, DELETE, HEAD, OPTIONS, TRACE, PATCH
    Request 类中包含了请求 url,请求请求方式,请求 Header,请求 Body,请求的优先级等信息。

    因为是抽象类,子类必须重写的两个方法。

    abstract protected Response<T> parseNetworkResponse(NetworkResponse response);
    

    子类重写此方法,将网络返回的原生字节内容,转换成合适的类型。此方法会在工作线程中被调用。

    abstract protected void deliverResponse(T response);
    

    子类重写此方法,将解析成合适类型的内容传递给它们的监听回调。

    以下两个方法也经常会被重写

    public byte[] getBody()
    

    重写此方法,可以构建用于 POST、PUT、PATCH 请求方式的 Body 内容。

    protected Map<String, String> getParams()
    

    在上面getBody函数没有被重写情况下,此方法的返回值会被 key、value 分别编码后拼装起来转换为字节码作为 Body 内容。

    4.2.3 RequestQueue.java

    Volley 框架的核心类,将请求 Request 加入到一个运行的RequestQueue中,来完成请求操作。

    (1). 主要成员变量

    RequestQueue 中维护了两个基于优先级的 Request 队列,缓存请求队列和网络请求队列。
    放在缓存请求队列中的 Request,将通过缓存获取数据;放在网络请求队列中的 Request,将通过网络获取数据。

    private final PriorityBlockingQueue<Request<?>> mCacheQueue = new PriorityBlockingQueue<Request<?>>();
    private final PriorityBlockingQueue<Request<?>> mNetworkQueue = new PriorityBlockingQueue<Request<?>>();
    

    维护了一个正在进行中,尚未完成的请求集合。

    private final Set<Request<?>> mCurrentRequests = new HashSet<Request<?>>();
    

    维护了一个等待请求的集合,如果一个请求正在被处理并且可以被缓存,后续的相同 url 的请求,将进入此等待队列。

    private final Map<String, Queue<Request<?>>> mWaitingRequests = new HashMap<String, Queue<Request<?>>>();
    

    (2). 启动队列

    创建出 RequestQueue 以后,调用 start 方法,启动队列。

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

    start 方法中,开启一个缓存调度线程CacheDispatcher和 n 个网络调度线程NetworkDispatcher,这里 n 默认为 4,存在优化的余地,比如可以根据 CPU 核数以及网络类型计算更合适的并发数。
    缓存调度线程不断的从缓存请求队列中取出 Request 去处理,网络调度线程不断的从网络请求队列中取出 Request 去处理。

    (3). 加入请求

    public <T> Request<T> add(Request<T> request);
    

    流程图如下:

    (4). 请求完成

    void finish(Request<?> request)
    

    Request 请求结束

    (1). 首先从正在进行中请求集合mCurrentRequests中移除该请求。
    (2). 然后查找请求等待集合mWaitingRequests中是否存在等待的请求,如果存在,则将等待队列移除,并将等待队列所有的请求添加到缓存请求队列中,让缓存请求处理线程CacheDispatcher自动处理。

    (5). 请求取消

    public void cancelAll(RequestFilter filter)
    public void cancelAll(final Object tag)
    

    取消当前请求集合中所有符合条件的请求。
    filter 参数表示可以按照自定义的过滤器过滤需要取消的请求。
    tag 表示按照Request.setTag设置好的 tag 取消请求,比如同属于某个 Activity 的。

    4.2.4 CacheDispatcher.java

    一个线程,用于调度处理走缓存的请求。启动后会不断从缓存请求队列中取请求处理,队列为空则等待,请求处理结束则将结果传递给ResponseDelivery 去执行后续处理。当结果未缓存过、缓存失效或缓存需要刷新的情况下,该请求都需要重新进入NetworkDispatcher去调度处理。

    (1). 成员变量

    BlockingQueue<Request<?>> mCacheQueue 缓存请求队列
    BlockingQueue<Request<?>> mNetworkQueue 网络请求队列
    Cache mCache 缓存类,代表了一个可以获取请求结果,存储请求结果的缓存
    ResponseDelivery mDelivery 请求结果传递类

    (2). 处理流程图

    4.2.5 NetworkDispatcher.java

    一个线程,用于调度处理走网络的请求。启动后会不断从网络请求队列中取请求处理,队列为空则等待,请求处理结束则将结果传递给 ResponseDelivery 去执行后续处理,并判断结果是否要进行缓存。

    (1). 成员变量

    BlockingQueue<Request<?>> mQueue 网络请求队列
    Network mNetwork 网络类,代表了一个可以执行请求的网络
    Cache mCache 缓存类,代表了一个可以获取请求结果,存储请求结果的缓存
    ResponseDelivery mDelivery 请求结果传递类,可以传递请求的结果或者错误到调用者

    (2). 处理流程图

    4.2.6 Cache.java

    缓存接口,代表了一个可以获取请求结果,存储请求结果的缓存。

    (1). 主要方法:

    public Entry get(String key); 通过 key 获取请求的缓存实体
    public void put(String key, Entry entry); 存入一个请求的缓存实体
    public void remove(String key); 移除指定的缓存实体
    public void clear(); 清空缓存

    (2). 代表缓存实体的内部类 Entry

    成员变量和方法
    byte[] data 请求返回的数据(Body 实体)
    String etag Http 响应首部中用于缓存新鲜度验证的 ETag
    long serverDate Http 响应首部中的响应产生时间
    long ttl 缓存的过期时间
    long softTtl 缓存的新鲜时间
    Map<String, String> responseHeaders 响应的 Headers
    boolean isExpired() 判断缓存是否过期,过期缓存不能继续使用
    boolean refreshNeeded() 判断缓存是否新鲜,不新鲜的缓存需要发到服务端做新鲜度的检测

    4.2.7 DiskBasedCache.java

    继承 Cache 类,基于 Disk 的缓存实现类。

    (1). 主要方法:

    public synchronized void initialize() 初始化,扫描缓存目录得到所有缓存数据摘要信息放入内存。
    public synchronized Entry get(String key) 从缓存中得到数据。先从摘要信息中得到摘要信息,然后读取缓存数据文件得到内容。
    public synchronized void put(String key, Entry entry) 将数据存入缓存内。先检查缓存是否会满,会则先删除缓存中部分数据,然后再新建缓存文件。
    private void pruneIfNeeded(int neededSpace) 检查是否能再分配 neededSpace 字节的空间,如果不能则删除缓存中部分数据。
    public synchronized void clear() 清空缓存。public synchronized void remove(String key) 删除缓存中某个元素。

    (2). CacheHeader 类

    CacheHeader 是缓存文件摘要信息,存储在缓存文件的头部,与上面的Cache.Entry相似。

    4.2.8 NoCache.java

    继承 Cache 类,不做任何操作的缓存实现类,可将它作为构建RequestQueue的参数以实现一个不带缓存的请求队列。

    4.2.9 Network.java

    代表网络的接口,处理网络请求。
    唯一的方法,用于执行特定请求。

    public NetworkResponse performRequest(Request<?> request) throws VolleyError;
    

    4.2.10 NetworkResponse.java

    Network中方法 performRequest 的返回值,Request的 parseNetworkResponse(…) 方法入参,是 Volley 中用于内部 Response 转换的一级。
    封装了网络请求响应的 StatusCode,Headers 和 Body 等。

    (1). 成员变量

    int statusCode Http 响应状态码
    byte[] data Body 数据
    Map<String, String> headers 响应 Headers
    boolean notModified 表示是否为 304 响应
    long networkTimeMs 请求耗时

    (2). Volley 的内部 Response 转换流程图


    从上到下表示从得到数据后一步步的处理,箭头旁的注释表示该步处理后的实体类。

    4.2.11 BasicNetwork.java

    实现 Network,Volley 中默认的网络接口实现类。调用HttpStack处理请求,并将结果转换为可被ResponseDelivery处理的NetworkResponse
    主要实现了以下功能:
    (1). 利用 HttpStack 执行网络请求。
    (2). 如果 Request 中带有实体信息,如 Etag,Last-Modify 等,则进行缓存新鲜度的验证,并处理 304(Not Modify)响应。
    (3). 如果发生超时,认证失败等错误,进行重试操作,直到成功、抛出异常(不满足重试策略等)结束。

    4.2.12 HttpStack.java

    用于处理 Http 请求,返回请求结果的接口。目前 Volley 中的实现有基于 HttpURLConnection 的 HurlStack 和 基于 Apache HttpClient 的 HttpClientStack。
    唯一方法,执行请求

    public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
            throws IOException, AuthFailureError;
    

    执行 Request 代表的请求,第二个参数表示发起请求之前,添加额外的请求 Headers。

    4.2.13 HttpClientStack.java

    实现 HttpStack 接口,利用 Apache 的 HttpClient 进行各种请求方式的请求。
    基本就是 org.apache.http 包下面相关类的常见用法,不做详解,不过与下面 HttpURLConnection 做下对比就能发现 HttpURLConnection 的 API 相对简单的多。

    4.2.14 HurlStack.java

    实现 HttpStack 接口,利用 Java 的 HttpURLConnection 进行各种请求方式的请求。

    4.2.15 Response.java

    封装了经过解析后的数据,用于传输。并且有两个内部接口 Listener 和 ErrorListener 分别可表示请求失败和成功后的回调。
    Response 的构造函数被私有化,而通过两个函数名更易懂的静态方法构建对象。

    4.2.16 ByteArrayPool.java

    byte[] 的回收池,用于 byte[] 的回收再利用,减少了内存的分配和回收。主要通过一个元素长度从小到大排序的ArrayList作为 byte[] 的缓存,另有一个按使用时间先后排序的ArrayList属性用于缓存满时清理元素。

    public synchronized void returnBuf(byte[] buf)
    

    将用过的 byte[] 回收,根据 byte[] 长度按照从小到大的排序将 byte[] 插入到缓存中合适位置。

    public synchronized byte[] getBuf(int len)
    

    获取长度不小于 len 的 byte[],遍历缓存,找出第一个长度大于传入参数len的 byte[],并返回;如果最终没有合适的 byte[],new 一个返回。

    private synchronized void trim()
    

    当缓存的 byte 超过预先设置的大小时,按照先进先出的顺序删除最早的 byte[]。

    4.2.17 PoolingByteArrayOutputStream.java

    继承 ByteArrayOutputStream,原始 ByteArrayOutputStream 中用于接受写入 bytes 的 buf,每次空间不足时便会 new 更大容量的 byte[],而 PoolingByteArrayOutputStream 使用了 ByteArrayPool 作为 Byte[] 缓存来减少这种操作,从而提高性能。

    4.2.18 HttpHeaderParser.java

    Http header 的解析工具类,在 Volley 中主要作用是用于解析 Header 从而判断返回结果是否需要缓存,如果需要返回 Header 中相关信息。
    有三个方法

    public static long parseDateAsEpoch(String dateStr)
    

    解析时间,将 RFC1123 的时间格式,解析成 epoch 时间

    public static String parseCharset(Map<String, String> headers)
    

    解析编码集,在 Content-Type 首部中获取编码集,如果没有找到,默认返回 ISO-8859-1

    public static Cache.Entry parseCacheHeaders(NetworkResponse response)
    

    比较重要的方法,通过网络响应中的缓存控制 Header 和 Body 内容,构建缓存实体。如果 Header 的 Cache-Control 字段含有no-cacheno-store表示不缓存,返回 null。
    (1). 根据 Date 首部,获取响应生成时间
    (2). 根据 ETag 首部,获取响应实体标签
    (3). 根据 Cache-Control 和 Expires 首部,计算出缓存的过期时间,和缓存的新鲜度时间

    两点需要说明下:
    1.没有处理Last-Modify首部,而是处理存储了Date首部,并在后续的新鲜度验证时,使用Date来构建If-Modified-Since。这与 Http 1.1 的语义有些违背。
    2.计算过期时间,Cache-Control 首部优先于 Expires 首部。

    4.2.19 RetryPolicy.java

    重试策略接口
    有三个方法:

    public int getCurrentTimeout();
    

    获取当前请求用时(用于 Log)

    public int getCurrentRetryCount();
    

    获取已经重试的次数(用于 Log)

    public void retry(VolleyError error) throws VolleyError;
    

    确定是否重试,参数为这次异常的具体信息。在请求异常时此接口会被调用,可在此函数实现中抛出传入的异常表示停止重试。

    4.2.20 DefaultRetryPolicy.java

    实现 RetryPolicy,Volley 默认的重试策略实现类。主要通过在 retry(…) 函数中判断重试次数是否达到上限确定是否继续重试。
    其中mCurrentRetryCount变量表示已经重试次数。
    mBackoffMultiplier表示每次重试之前的 timeout 该乘以的因子。
    mCurrentTimeoutMs变量表示当前重试的 timeout 时间,会以mBackoffMultiplier作为因子累计前几次重试的 timeout。

    4.2.21 ResponseDelivery.java

    请求结果的传输接口,用于传递请求结果或者请求错误。
    有三个方法:

    public void postResponse(Request<?> request, Response<?> response);
    

    此方法用于传递请求结果,requestresponse 参数分别表示请求信息和返回结果信息。

    public void postResponse(Request<?> request, Response<?> response, Runnable runnable);
    

    此方法用于传递请求结果,并在完成传递后执行 Runnable。

    public void postError(Request<?> request, VolleyError error);
    

    此方法用于传输请求错误。

    4.2.22 ExecutorDelivery.java

    请求结果传输接口具体实现类。
    在 Handler 对应线程中传输缓存调度线程或者网络调度线程中产生的请求结果或请求错误,会在请求成功的情况下调用 Request.deliverResponse(…) 函数,失败时调用 Request.deliverError(…) 函数。

    4.2.23 StringRequest.java

    继承 Request 类,代表了一个返回值为 String 的请求。将网络返回的结果数据解析为 String 类型。通过构造函数的 listener 传参,支持请求成功后的 onResponse(…) 回调。

    4.2.24 JsonRequest.java

    抽象类,继承自 Request,代表了 body 为 JSON 的请求。提供了构建 JSON 请求参数的方法。

    4.2.25 JsonObjectRequest.java

    继承自 JsonRequest,将网络返回的结果数据解析为 JSONObject 类型。

    4.2.26 JsonArrayRequest.java

    继承自 JsonRequest,将网络返回的结果数据解析为 JSONArray 类型。

    4.2.27 ImageRequest.java

    继承 Request 类,代表了一个返回值为 Image 的请求。将网络返回的结果数据解析为 Bitmap 类型。
    可以设置图片的最大宽度和最大高度,并计算出合适尺寸返回。每次最多解析一张图片防止 OOM。

    4.2.28 ImageLoader.java

    封装了 ImageRequst 的方便使用的图片加载工具类。

    1.可以设置自定义的ImageCache,可以是内存缓存,也可以是 Disk 缓存,将获取的图片缓存起来,重复利用,减少请求。
    2.可以定义图片请求过程中显示的图片和请求失败后显示的图片。
    3.相同请求(相同地址,相同大小)只发送一个,可以避免重复请求。
    // TODO

    4.2.29 NetworkImageView.java

    利用 ImageLoader,可以加载网络图片的 ImageView
    有三个公开的方法:

    public void setDefaultImageResId(int defaultImage)
    

    设置默认图片,加载图片过程中显示。

    public void setErrorImageResId(int errorImage)
    

    设置错误图片,加载图片失败后显示。

    public void setImageUrl(String url, ImageLoader imageLoader)
    

    设置网络图片的 Url 和 ImageLoader,将利用这个 ImageLoader 去获取网络图片。

    如果有新的图片加载请求,会把这个 ImageView 上旧的加载请求取消。

    4.2.30 ClearCacheRequest.java

    用于人为清空 Http 缓存的请求。
    添加到 RequestQueue 后能很快执行,因为优先级很高,为Priority.IMMEDIATE。并且清空缓存的方法mCache.clear()写在了isCanceled()方法体中,能最早的得到执行。

    ClearCacheRequest 的写法不敢苟同,目前看来唯一的好处就是可以将清空缓存操作也当做一个请求。而在isCanceled()中做清空操作本身就造成了歧义,不看源码没人知道在NetworkDispatcher run 方法循环的过程中,isCanceled()这个读操作竟然做了可能造成缓存被清空。只能跟源码的解释一样当做一个 Hack 操作。

    4.2.31 Authenticator.java

    身份认证接口,用于基本认证或者摘要认证。这个类是 Volley 用于和身份验证打通的接口,比如 OAuth,不过目前的使用不是特别广泛和 Volley 的内部结合也不是特别紧密。

    4.2.32 AndroidAuthenticator.java

    继承 Authenticator,基于 Android AccountManager 的认证交互实现类。

    4.2.33 VolleyLog.java

    Volley 的 Log 工具类。

    4.2.34 VolleyError.java

    Volley 中所有错误异常的父类,继承自 Exception,可通过此类设置和获取 NetworkResponse 或者请求的耗时。

    4.2.35 AuthFailureError.java

    继承自 VolleyError,代表请求认证失败错误,如 RespondeCode 的 401 和 403。

    4.2.36 NetworkError.java

    继承自 VolleyError,代表网络错误。

    4.2.37 ParseError.java

    继承自 VolleyError,代表内容解析错误。

    4.2.38 ServerError.java

    继承自 VolleyError,代表服务端错误。

    4.2.39 TimeoutError.java

    继承自 VolleyError,代表请求超时错误。

    4.2.40 NoConnectionError.java

    继承自 NetworkError,代表无法建立连接错误。

    5. 杂谈

    5.1 关于 Http 缓存

    Volley 构建了一套相对完整的符合 Http 语义的缓存机制。
    优点和特点
    (1). 根据Cache-ControlExpires首部来计算缓存的过期时间。如果两个首部都存在情况下,以Cache-Control为准。
    (2). 利用If-None-MatchIf-Modified-Since对过期缓存或者不新鲜缓存,进行请求再验证,并处理 304 响应,更新缓存。
    (3). 默认的缓存实现,将缓存以文件的形式存储在 Disk,程序退出后不会丢失。

    我个人认为的不足之处
    缓存的再验证方面,在构建If-Modified-Since请求首部时,Volley 使用了服务端响应的Date首部,没有使用Last-Modified首部。整个框架没有使用Last-Modified首部。这与 Http 语义不符。

    private void addCacheHeaders(Map<String, String> headers, Cache.Entry entry) {
        // If there's no cache entry, we're done.
        if (entry == null) {
            return;
        }
    
        if (entry.etag != null) {
            headers.put("If-None-Match", entry.etag);
        }
    
        if (entry.serverDate > 0) {
            Date refTime = new Date(entry.serverDate);
            headers.put("If-Modified-Since", DateUtils.formatDate(refTime));
        }
    }
    

    服务端根据请求时通过If-Modified-Since首部传过来的时间,判断资源文件是否在If-Modified-Since时间 以后 有改动,如果有改动,返回新的请求结果。如果没有改动,返回 304 not modified。
    Last-Modified代表了资源文件的最后修改时间。通常使用这个首部构建If-Modified-Since的时间。
    Date代表了响应产生的时间,正常情况下Date时间在Last-Modified时间之后。也就是Date>=Last-Modified
    通过以上原理,既然Date>=Last-Modified。那么我利用Date构建,也是完全正确的。

    可能的问题出在服务端的 Http 实现上,如果服务端完全遵守 Http 语义,采用时间比较的方式来验证If-Modified-Since,判断服务器资源文件修改时间是不是在If-Modified-Since之后。那么使用Date完全正确。
    可是有的服务端实现不是比较时间,而是直接的判断服务器资源文件修改时间,是否和If-Modified-Since所传时间相等。这样使用Date就不能实现正确的再验证,因为Date的时间总不会和服务器资源文件修改时间相等。

    尽管使用Date可能出现的不正确情况,归结于服务端没有正确的实现 Http 语义。
    但我还是希望 Volley 也能完全正确的实现 Http 语义,至少同时处理Last-ModifiedDate,并且优先使用Last-Modified

    5.2 Bug

    (1). BasicNetwork.performRequest(…)

    如下代码:

    @Override
    public NetworkResponse performRequest(Request<?> request) throws VolleyError {
        ……
        while (true) {
            ……
            try {
                ……
            } catch (IOException e) {
                int statusCode = 0;
                NetworkResponse networkResponse = null;
                ……
                if (responseContents != null) {
                  ……
                } else {
                    throw new NetworkError(networkResponse);
                }
            }
        }
    }
    

    BasicNetwork.performRequest(…) 最后的

    throw new NetworkError(networkResponse);
    

    应该是

    throw new NetworkError(e);
    

    更合理。

    展开全文
  • Volley源码解析

    2018-02-06 03:12:25
    文章首发于个人博客 参考资料: Android Volley完全解析(四),带你从源码的角度理解Volley...Volley源码学习笔记 Volley的原理解析 Android Volley 源码解析(一),网络请求的执行流程 Volley简介 Volley是一款goog...

    文章首发于个人博客

    参考资料:
    Android Volley完全解析(四),带你从源码的角度理解Volley
    Volley 源码解析
    知乎-如何去阅读Android Volley框架源码?
    Volley学习笔记之简单使用及部分源码详解
    Volley源码学习笔记
    Volley的原理解析
    Android Volley 源码解析(一),网络请求的执行流程

    Volley简介

    Volley是一款google开源的网络请求框架, 其实质是对Android的HttpURLConnectio或者HttpClient进行了封装(开了多个线程), 并加入了缓存功能, 因此比较适合高并发的网络请求, 但不适合大文件下载。 Volley的官方github地址在这里

    用法实例

    mRequestQueue = Volley.newRequestQueue(this);
    
    StringRequest stringRequest = new StringRequest("https://www.baidu.com", new Response.Listener<String>() {
        @Override
        public void onResponse(String response) {
            Log.d(TAG, "response = " + response);
            // main
            Log.d(TAG, "currentThread = " + Thread.currentThread().getName());
        }
    }, new Response.ErrorListener() {
        @Override
        public void onErrorResponse(VolleyError error) {
            Log.e(TAG, error.getMessage(), error);
            Log.d(TAG, "currentThread = " + Thread.currentThread().getName());
        }
    });
    mRequestQueue.add(stringRequest);
    复制代码

    简单举个例子, 先获取一个requestQueue对象, 这个对象不需要每次请求时都获取, 一般只需要获取一次, 设置成app内全局单例或者activity的成员变量就可以了。然后new一个request对象, 然后把这个request加入到requestQueue就行了, 就这么简单。

    大体概览

    先附一张Volley的工作流程图, 懒得自己画了, 盗的图

    有几个单词不认识, 还是翻译一下吧
    再把Volley里比较重要的类列举一下

    类名 类型 作用
    Volley 普通类 对外暴露的 API,主要作用是构建 RequestQueue
    Request 抽象类 所有网络请求的抽象类,子类有StringRequest、JsonRequest、ImageRequest
    RequestQueue 普通类 存放请求的队列,子类有CacheDispatcher、NetworkDispatcher 和 ResponseDelivery
    CacheDispatcher 普通类 用于执行缓存队列请求的线程
    NetworkDispatcher 普通类 用于执行网络队列请求的线程
    Cache 接口 主要的子类是DiskBasedCache, 用于磁盘缓存
    HttpStack 接口 子类有HurlStack和HttpClientStack, 分别使用HttpURLConnection和HttpClient处理http请求
    Network 接口 子类有BasicNetwork, 调用 HttpStack 处理请求,并将结果转换成可被 ResponseDelivery 处理的 NetworkResponse
    Response 普通类 封装一个解析后的结果以便分发
    ResponseDelivery 接口 子类是ExecutorDelivery, 分发请求结果, 并将其回调到主线程

    创建RequestQueue

    因为执行一个网络请求分三步, 第一步是创建RequestQueue, 第二步是创建request, 第三步是把request加入到RequestQueue。所以我们先来分析第一步

    mRequestQueue = Volley.newRequestQueue(this);
    复制代码

    点进去看看

        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, 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) {
            File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
            RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
            queue.start();
            return queue;
        }
    复制代码

    这里创建了根据版本号创建了一个HttpStack对象。然后用BasicNetwork对HttpStack进行包装,构建了一个Network对象。Network对象是用来处理网络请求的。 可以看到, 在创建HttpStack对象的时候判断了一下手机的sdk版本号。如果sdk的版本大于等于9, 则创建基于HttpUrlConnection的HurlStack, 否则创建基于HttpClient的HttpClientStack。因为在Android2.3(SDK = 9)之前,HttpURLConnection存在一些bug,所以这时候用HttpClient来进行网络请求会比较合适。在Android 2.3版本及以后,HttpURLConnection则是最佳的选择。不过现在的手机基本上都是4.0以上的, 所以这里的if else直接看成走HurlStack的分支就行了。接着往下看。 先创建了一个默认的缓存文件夹, 然后利用传入的Network对象new了一个RequestQueue对象, 并调用start()方法。
    先看一下这个RequestQueue的构造方法

        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;
        }
    复制代码

    为了看的更直观一点, 我把代码简化成这样

        public RequestQueue(Cache cache, Network network) {
            mCache = cache;
            mNetwork = network;
            // DEFAULT_NETWORK_THREAD_POOL_SIZE = 4
            mDispatchers = new NetworkDispatcher[DEFAULT_NETWORK_THREAD_POOL_SIZE];
            Handler mainThreadHandler = new Handler(Looper.getMainLooper());
            mDelivery = new ExecutorDelivery(mainThreadHandler);
        }
    复制代码

    这里创建了一个length = 4的是NetworkDispatcher数组, 然后创建了一个ExecutorDelivery对象mDelivery。
    主要说说mDelivery这个对象的作用, 看一下它的相关方法。

        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);
                }
            };
        }
        
        @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));
        }
    复制代码

    当调用mDelivery.postResponse()和postError()方法的时候, 调用的是mResponsePoster.execute()方法, 也就是调用handler.post(command)方法, 而这个handler可以从上面看到是和主线程绑定的, 所以会将response结果回调到主线程。所以说mDelivery这个对象主要是用于将子线程中的网络请求结果发送到主线程中处理。

    思路拉回来, 让我们再回到RequestQueue, 看一下queue.start()方法的源码

        public void start() {
            // Make sure any currently running dispatchers are stopped.
            // 确保所有的调度都已经停止
            stop();
            // Create the cache dispatcher and start it.
            // 创建一个缓存调度线程并启动它, CacheDispatcher继承自Thread
            mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
            mCacheDispatcher.start();
    
            // Create network dispatchers (and corresponding threads) up to the pool size.
            // 创建网络调度调度线程(NetworkDispatcher继承自Thread), 直到数量达到了缓存池的容量, 并依次开启
            for (int i = 0; i < mDispatchers.length; i++) {
                NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork, mCache, mDelivery);
                mDispatchers[i] = networkDispatcher;
                networkDispatcher.start();
            }
        }
    复制代码

    首先确保所有的调度线层都已经停止, 然后创建了一个缓存调度线程, 然后开启这个线程。接着new了4个网络调度线程, 然后分别开启这4个线程。 对于这几个线程, 我们选取一个CacheDispatcher, 稍微看一下它的run()方法

        @Override
        public void run() {
            if (DEBUG) VolleyLog.v("start new dispatcher");
            // 设置线程的优先级为background
            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) {
                        return;
                    }
                }
            }
        }
    复制代码

    里面有一个死循环, 所以这个线程是一只运行不会退出的。 另外提前"剧透"一下, processRequest()方法中的代码, 会阻塞在从缓存队列中取request那里(如果缓存队列为空的话), 直到取到request(缓存队列中添加进了新的request), 就会往下执行去处理这个request。 这些我们后面详细分析, 现在只需要有这样一个概念: 开启这几个线程之后会不断的从队列中取出request然后去处理。

    RequestQueue.add(Request)

    现在看把request加入到RequestQueue这个过程

        /**
         * 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标记为属于这个queue并且把它加入当前所有请求的集合中
            request.setRequestQueue(this);
            synchronized (mCurrentRequests) {
                // mCurrentRequests是一个hashSet, 确保其中元素的唯一性
                mCurrentRequests.add(request);
            }
    
            // Process requests in the order they are added.
            // 按这些request被加入的顺序来处理它们
            // 设置序列号
            request.setSequence(getSequenceNumber());
            // 添加标记: 已经被加入到队列当中
            request.addMarker("add-to-queue");
    
            // If the request is uncacheable, skip the cache queue and go straight to the network.
            // 如果这个request不能被缓存, 跳过缓存队列而直接加入到网络队列中
            if (!request.shouldCache()) {
                mNetworkQueue.add(request);
                return request;
            }
            mCacheQueue.add(request);
            return request;
         }
    复制代码

    分以下这么几个步骤:

    1. 将当前request和处理request的queue进行关联,这样当该request结束的时候就会通知负责处理该request的queue
    2. 将request加入到mCurrentRequests这个hashSet中, 使用haseSet是为了去重。mCurrentRequests保存着所有被requestQueue处理的request
    3. 如果request不允许被缓存, 则把这个request放到网络请求的队列中
    4. 如果request允许被缓存, 那就把这个request加入到缓存的队列中 那么问题来了, request被加入到队列中会发生什么呢?它会被运送到哪里去呢?是什么时候被取出来的呢?答案就在CacheDispatcher和NetworkDispatcher。

    CacheDispatcher

    先看CacheDispatcher的构造方法

        public CacheDispatcher(
                BlockingQueue<Request<?>> cacheQueue, BlockingQueue<Request<?>> networkQueue,
                Cache cache, ResponseDelivery delivery) {
            mCacheQueue = cacheQueue;
            mNetworkQueue = networkQueue;
            mCache = cache;
            mDelivery = delivery;
            mWaitingRequestManager = new WaitingRequestManager(this);
        }
    复制代码

    RequestQueue中的mCacheDispatcher是在调用start()方法的时候被new出来的, 从构造方法中可以看出, mCacheDispatcher得到了request中的mCacheQueue, mNetworkQueue, mCache和mDelivery(就是那个ExecutorDelivery)。 然后看run()方法

        @Override
        public void run() {
            if (DEBUG) VolleyLog.v("start new dispatcher");
            // 设置线程的优先级为background
            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) {
                        return;
                    }
                }
            }
        }
        
        private void processRequest() throws InterruptedException {
            // Get a request from the cache triage queue, blocking until
            // at least one is available.
            // 从缓存队列中取出request, 这段过程会阻塞, 直到获得了一个请求
            final Request<?> request = mCacheQueue.take();
            request.addMarker("cache-queue-take");
    
            // If the request has been canceled, don't bother dispatching it.
            // 如果一个request被取消了, 不用费心思去分发它了(直接干掉它)
            if (request.isCanceled()) {
                request.finish("cache-discard-canceled");
                return;
            }
    
            // Attempt to retrieve this item from cache.
            // 通过request的url尝试去获取缓存
            // attention 这个方法需要进去看一下
            Cache.Entry entry = mCache.get(request.getCacheKey());
            // 如果获取缓存失败(miss),
            if (entry == null) {
                request.addMarker("cache-miss");
                // Cache miss; send off to the network dispatcher.
                // 如果waittingRequests中还没有这个request, 将它加入网络队列中
                if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
                    mNetworkQueue.put(request);
                }
                return;
            }
    
            // If it is completely expired, just send it to the network.
            // 如果缓存过期了
            if (entry.isExpired()) {
                // 将这个request标记为"缓存命中, 但是过期"
                request.addMarker("cache-hit-expired");
                // 给这个request设置缓存
                request.setCacheEntry(entry);
                // 如果缓存过期了, waittingRequests中还没有这个request, 还是需要请求网络
                if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
                    mNetworkQueue.put(request);
                }
                return;
            }
    
            // We have a cache hit; parse its data for delivery back to the request.
            // 我们在这里命中了缓存(并且没有过期), 解析它的数据并回传给request
            // 添加标记"缓存命中"
            request.addMarker("cache-hit");
            // 解析response
            Response<?> response = request.parseNetworkResponse(
                    new NetworkResponse(entry.data, entry.responseHeaders));
            // 添加标记"缓存命中并且已经被解析"
            request.addMarker("cache-hit-parsed");
    
            // 如果缓存不需要刷新, 直接发送响应即可
            if (!entry.refreshNeeded()) {
                // Completely unexpired cache hit. Just deliver the response.
                mDelivery.postResponse(request, response);
            } else {
                // Soft-expired cache hit. We can deliver the cached response,
                // but we need to also send the request to the network for
                // refreshing.
    
                // 缓存没有过期, 但是需要更新.
                // 我们分发这个缓存, 但我们也需要把这个request发送到网络来刷新它
    
                // 添加标记"缓存命中但是需要刷新"
                request.addMarker("cache-hit-refresh-needed");
                // 给request设置缓存
                request.setCacheEntry(entry);
                // Mark the response as intermediate.
                // 将这个response标记为过渡的
                response.intermediate = true;
    
                // 如果waittingRequests中没有这个request
                if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
                    // Post the intermediate response back to the user and have
                    // the delivery then forward the request along to the network.
    
                    // 将这个过渡的response返回给用户, 并且放到网络请求的队列中
                    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.
    
                    // request已经被添加到waittingRequest中来从第一个request中接收网络response, 一旦它返回了
                    mDelivery.postResponse(request, response);
                }
            }
        }
    复制代码

    我们仔细看一下processRequest()这个方法中的代码。首先会从缓存队列中取出request, 如果为空则会阻塞在mCackeQueue.take()这一行不再走下去了。其实可以看到, 之前RequestQueue.add(Request)方法中添加的request是在这里被取出来了。如果被取出来的request这时候被取消了, 那就立即结束掉。然后根据request的url去获取缓存Cache.Entry, 如果缓存为空则将将该request添加到网络队列中。如果缓存不为空, 接着再判断缓存是否过期, 如果过期, 还是将这个request添加到网络队列。如果这个缓存没有过期, 就将这个缓存解析为response对象。接着判断这个缓存是否需要刷新, 如果不需要刷新, 直接用mDelivery(就是那个ExecutorDelivery)将结果回调到主线程; 如果需要刷新, 把response回调到主线程, 并且把request添加到网络队列。

    NetworkDispatcher

    接着看看这个NetworkDispatcher

        public NetworkDispatcher(BlockingQueue<Request<?>> queue,
                Network network, Cache cache, ResponseDelivery delivery) {
            mQueue = queue;
            mNetwork = network;
            mCache = cache;
            mDelivery = delivery;
        }
        
        @Override
        public void run() {
            // 设置线程优先级为background
            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) {
                        return;
                    }
                }
            }
        }
        
        private void processRequest() throws InterruptedException {
            // Take a request from the queue.
            // 从队列中取出request
            Request<?> request = mQueue.take();
    
            // 从启动开始过去的时间
            long startTimeMs = SystemClock.elapsedRealtime();
            try {
                // 添加标记"已经从网络队列中取出"
                request.addMarker("network-queue-take");
    
                // If the request was cancelled already, do not perform the
                // network request.
                // 如果这个request已经被取消了, 不要再去执行这个request
                if (request.isCanceled()) {
                    request.finish("network-discard-cancelled");
                    request.notifyListenerResponseNotUsable();
                    return;
                }
    
                addTrafficStatsTag(request);
    
                // Perform the network request.
                // 执行网络请求, 获取到response
                NetworkResponse networkResponse = mNetwork.performRequest(request);
                // 添加标记"http请求完成"
                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.
    
                // 如果返回了304, 并且request已经被分发过了
                if (networkResponse.notModified && request.hasHadResponseDelivered()) {
                    // 结束请求
                    request.finish("not-modified");
                    // 通知所有listener没有造成任何可用的响应
                    request.notifyListenerResponseNotUsable();
                    return;
                }
    
                // Parse the response here on the worker thread.
                // 在工作线程解析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.
                // 如果request是可缓存的, 并且response的缓存不为null
                if (request.shouldCache() && response.cacheEntry != null) {
                    // 将数据保存到磁盘中
                    // attention 这里进去看一下
                    mCache.put(request.getCacheKey(), response.cacheEntry);
                    // 添加标记"网络缓存已经被写入"
                    request.addMarker("network-cache-written");
                }
    
                // Post the response back.
                // 将request标记为已经有一个响应来分发它
                request.markDelivered();
                // 分发数据
                mDelivery.postResponse(request, response);
                // 通知所有的listener已经有一个合法的响应被接收了
                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();
            }
        }
    复制代码

    有点和CacheDispatcher类似, 解读一下代码: 首先从网络请求的队列中取出一个request, 如果这个队列为空的话就会阻塞, 但是一旦队列中添加了request, 就能取出来。接着, 如果这时候request被取消了, 那就立刻结束。如果没有被取消, 那就执行网络请求, 这个过程可能又会阻塞。得到网络请求的响应之后, 如果服务器返回了304, 那就结束这个请求。如果不是304, 那就根据request解析这个网络请求。如果request允许被缓存, 那就将解析后的response保存到磁盘, 最后将这个response分发到主线程。我们在看看具体的网络请求是怎样的吧。查看mNetwork.performRequest(request)方法。

        @Override
        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 =
                            getCacheHeaders(request.getCacheEntry());
                    httpResponse = mBaseHttpStack.executeRequest(request, additionalRequestHeaders);
                    int statusCode = httpResponse.getStatusCode();
    
                    responseHeaders = httpResponse.getHeaders();
                    // Handle cache validation.
                    if (statusCode == HttpURLConnection.HTTP_NOT_MODIFIED) {
                        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.
                        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;
                    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());
                    }
                }
            }
        }
    复制代码

    代码好长啊, 我就大概说明一下, 就是使用了一开始的BaseHttpStack执行了网络请求, 如果想看HttpUrlConnection的具体请求过程, 还是要从mBaseHttpStack.executeRequest(request, additionalRequestHeaders)这个方法点进去看, 这里就不再赘述了。

    请求的结束和取消

    先看Request.finish(String)方法

        void finish(final String tag) {
            if (mRequestQueue != null) {
                // 调用的requestQueue的finish方法, 进去看一眼
                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
                    // actually do 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());
            }
        }
    复制代码

    看一下RequestQueue.finish(Request)方法

        <T> void finish(Request<T> request) {
            // Remove from the set of requests currently being processed.
            // 从现有队列中移除该request
            synchronized (mCurrentRequests) {
                mCurrentRequests.remove(request);
            }
            synchronized (mFinishedListeners) {
              for (RequestFinishedListener<T> listener : mFinishedListeners) {
                listener.onRequestFinished(request);
              }
            }
    
        }
    复制代码

    这里首先从mCurrentRequests里面移除了request, 然后回调所有的RequestFinishedListener, 这个mFinishedListeners是从request的一个可选方法传进来的, 反正我一般不传

        public  <T> void addRequestFinishedListener(RequestFinishedListener<T> listener) {
          synchronized (mFinishedListeners) {
            mFinishedListeners.add(listener);
          }
        }
    复制代码

    再看一下RequestQueue.cancelAll(Object)方法

        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();
                    }
                }
            }
        }
    复制代码

    其中cancalAll(RequestFilter filter)中的filter.apply(request)方法其实就是调用的cancalAll(final Object tag)中的return request.getTag() == tag;这条语句

    结语

    Volley的源码分析终于差不多了, 一些细枝末节我也暂且不去追究了。其实读源码的目的主要是吸取框架的思想和思路, 我们提倡不重复造轮子, 但是不等于不需要知道轮子是如何制造出来的。但有时候最好的学习方法就是自己亲身去体验一下轮子的制造过程(重复造轮子)?
    下面是我的github仓库, 注释已经写得很详细了
    github.com/mundane7996…

    展开全文
  • volley源码解析

    2017-09-22 10:20:04
    Volley源码 原理

    总结一下Volley:

    功能方面:

    基于HttpURLConnection HttpClient;封装了UIL图片加载框架,支持图片加载;网络请求的排序,优先级处理;添加了网路缓存;

    支持多级别取消网络请求;Activity和生命周期的联动(Activity结束时取消所有的网络请求)

    可扩展性好支持HttpURLConnection HttpClient以及OKHttp

    使用于数据量小但网络请求频繁的场景

    缺点:不适合大数据量的网络请求


    下面结合源码分析一下,先上官网的原理图


    首先通过Volley构建出请求队列

      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, 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);
        }
    在创建请求队列时会先去创建一个Httpstack,这里从源码可以看到9之前采用的是HurlStack 继续查看HurlStack的源码发现在执行请求时会调用

    openConnection(parsedUrl, request);获取得到网络连接在接下去发现调用的createConnection(url);这个方法,在此方法中我们发现最终调用的是
     (HttpURLConnection) url.openConnection();因此Android 9 之前Volley底层采用的是HttpURLConnection的网络请求方式、而9以后默认的采用的是
    HttpClientStack而点进去查看发现其采用的是HttpClient的网络请求。 

     public HttpResponse executeRequest(Request<?> request, Map<String, String> additionalHeaders)
                throws IOException, AuthFailureError {
            String url = request.getUrl();
            HashMap<String, String> map = new HashMap<>();
            map.putAll(request.getHeaders());
            map.putAll(additionalHeaders);
            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);
            for (String headerName : map.keySet()) {
                connection.addRequestProperty(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()));
            }
    
            return new HttpResponse(responseCode, convertHeaders(connection.getHeaderFields()),
                    connection.getContentLength(), inputStreamFromConnection(connection));
        }



    private HttpURLConnection openConnection(URL url, Request<?> request) throws IOException {
            HttpURLConnection connection = createConnection(url);
    
            int timeoutMs = request.getTimeoutMs();
            connection.setConnectTimeout(timeoutMs);
            connection.setReadTimeout(timeoutMs);
            connection.setUseCaches(false);
            connection.setDoInput(true);
    
            // use caller-provided custom SslSocketFactory, if any, for HTTPS
            if ("https".equals(url.getProtocol()) && mSslSocketFactory != null) {
                ((HttpsURLConnection)connection).setSSLSocketFactory(mSslSocketFactory);
            }
    
            return connection;
        }

     protected HttpURLConnection createConnection(URL url) throws IOException {
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
    
            // Workaround for the M release HttpURLConnection not observing the
            // HttpURLConnection.setFollowRedirects() property.
            // https://code.google.com/p/android/issues/detail?id=194495
            connection.setInstanceFollowRedirects(HttpURLConnection.getFollowRedirects());
    
            return connection;
        }

    而同时Volley也是支持okhttp作为底层网络请求的。这就需要自己构建OKHttpStack  
    方式一: 可以参照HurlStack的方式使用OkHttpStack extends HurlStac在重写
    首先build.gradle中添加okhttp与okhttp-urlconnection的支持
       compile 'com.squareup.okhttp3:okhttp:3.2.0'
        compile 'com.squareup.okhttp3:okhttp-urlconnection:3.2.0'
    createConnection方法
      protected HttpURLConnection createConnection(URL url) throws IOException {
            OkUrlFactory okUrlFactory = new OkUrlFactory(okHttpClient);
            return okUrlFactory.open(url);
    
    
        }
    
    
    

    方法二

    在eclipse中添加okhttp.jar支持此外定义okhttp重写performRequest方法

    import java.io.IOException;
    import java.util.Map;
    import org.apache.http.HttpEntity;
    import org.apache.http.HttpResponse;
    import org.apache.http.ProtocolVersion;
    import org.apache.http.entity.BasicHttpEntity;
    import org.apache.http.message.BasicHeader;
    import org.apache.http.message.BasicHttpResponse;
    import org.apache.http.message.BasicStatusLine;
    
    import com.yulong.mobile.netroid.Request;
    import com.yulong.mobile.netroid.error.AuthFailureError;
    import android.annotation.SuppressLint;
    import android.content.Context;
    import android.os.Build;
    import android.webkit.WebSettings;
    import okhttp3.Call;
    import okhttp3.Headers;
    import okhttp3.MediaType;
    import okhttp3.OkHttpClient;
    import okhttp3.Protocol;
    import okhttp3.RequestBody;
    import okhttp3.Response;
    import okhttp3.ResponseBody;
    
    public class OkHttpStack implements HttpStack {
        private final OkHttpClient mOkHttpClient;
        private Context mContext;
    
        public OkHttpStack(Context context,OkHttpClient okHttpClient) {
            if (okHttpClient == null) {
                throw new IllegalArgumentException("OkHttpClient can't be null");
            }
            this.mContext = context;
            mOkHttpClient = okHttpClient;
        }
    
        @SuppressLint("NewApi")
    	private  String getUserAgent() {
            String userAgent = "";
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
                try {
                    userAgent = WebSettings.getDefaultUserAgent(mContext);
                } catch (Exception e) {
                    userAgent = System.getProperty("http.agent");
                }
            } else {
                userAgent = System.getProperty("http.agent");
            }
            StringBuffer sb = new StringBuffer();
            for (int i = 0, length = userAgent.length(); i < length; i++) {
                char c = userAgent.charAt(i);
                if (c <= '\u001f' || c >= '\u007f') {
                    sb.append(String.format("\\u%04x", (int) c));
                } else {
                    sb.append(c);
                }
            }
            return sb.toString();
        }
    
    
        
    	@Override
    	public HttpResponse performRequest(Request<?> request) throws IOException, AuthFailureError {
    		
    		
    		 OkHttpClient client = mOkHttpClient;
    	        int timeoutMs = request.getTimeoutMs();
    
    	        okhttp3.Request.Builder builder = new okhttp3.Request.Builder();
    	        builder.url(request.getUrl());
    	        
    	        Map<String, String> headers = request.getHeaders();
    	        
    	        for (String name : headers.keySet()) {
    	            builder.addHeader(name, headers.get(name));
    	        }
    	        
    	        builder.removeHeader("User-Agent").addHeader("User-Agent",getUserAgent()).build();
    	        setConnectionParametersForRequest(builder, request);
    	        okhttp3.Request okRequest = builder.build();
    	        okhttp3.Call call = client.newCall(okRequest);
    	        okhttp3.Response okresponse = call.execute();
    	        
    	        BasicStatusLine responseStatus = new BasicStatusLine(
    	                parseProtocol(okresponse.protocol()),
    	                okresponse.code(),
    	                okresponse.message()
    	        );
    
    	        
    	        BasicHttpResponse response = new BasicHttpResponse(responseStatus);
    	        response.setEntity(entityFromOkHttpResponse(okresponse));
    	        Headers responseHeaders = okresponse.headers();
    
    	        int size = responseHeaders.size();
    	        String name = null;
    	        String value = null;
    	        for (int i = 0; i < size; i++) {
    	            name = responseHeaders.name(i);
    	            if (name != null) {
    	                response.addHeader(new BasicHeader(name, value));
    	            }
    	        }
    
    	        return response;
    
    	}
    	
    	private static HttpEntity entityFromOkHttpResponse(Response r) throws IOException {
            BasicHttpEntity entity = new BasicHttpEntity();
            ResponseBody body = r.body();
    
            entity.setContent(body.byteStream());
            entity.setContentLength(body.contentLength());
            entity.setContentEncoding(r.header("Content-Encoding"));
    
            if (body.contentType() != null) {
                entity.setContentType(body.contentType().type());
            }
            return entity;
        }
    
    	  static void setConnectionParametersForRequest(okhttp3.Request.Builder builder, Request<?> request) throws IOException, AuthFailureError {
    	        switch (request.getMethod()) {
    	           
    	            case Request.Method.GET:
    	                builder.get();
    	                break;
    	            case Request.Method.DELETE:
    	                builder.delete();
    	                break;
    	            case Request.Method.POST:
    	                builder.post(createRequestBody(request));
    	                break;
    	            case Request.Method.PUT:
    	                builder.put(createRequestBody(request));
    	                break;
    	            case Request.Method.HEAD:
    	                builder.head();
    	                break;
    	            case Request.Method.OPTIONS:
    	                builder.method("OPTIONS", null);
    	                break;
    	            case Request.Method.TRACE:
    	                builder.method("TRACE", null);
    	                break;
    	            case Request.Method.PATCH:
    	                builder.patch(createRequestBody(request));
    	                break;
    	            default:
    	                throw new IllegalStateException("Unknown method type.");
    	        }
    	    }
    	  
    	  private static RequestBody createRequestBody(Request r) throws AuthFailureError {
    	        final byte[] body = r.getBody();
    	        if (body == null) return null;
    
    	        return RequestBody.create(MediaType.parse(r.getBodyContentType()), body);
    	    }
    
    	  
    	  private static ProtocolVersion parseProtocol(final Protocol p) {
    	        switch (p) {
    	            case HTTP_1_0:
    	                return new ProtocolVersion("HTTP", 1, 0);
    	            case HTTP_1_1:
    	                return new ProtocolVersion("HTTP", 1, 1);
    	            case SPDY_3:
    	                return new ProtocolVersion("SPDY", 3, 1);
    	            case HTTP_2:
    	                return new ProtocolVersion("HTTP", 2, 0);
    	        }
    
    	        throw new IllegalAccessError("Unkwown protocol");
    	    }
    
        
    }



    回到正题我们继续跟进去查看newRequestQueue(context, network)的源代码,发现构建RequestQueue之后会将其start()启动而在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();
            }
        }


    volley构建了CacheDispatcher实例(CacheDispatcher extends Thread)然后调用了start方法,随后在for循环当中构建了NetworkDispatcher实例并也调用了start方法.默认情况下for循环会执行四次,这样也就是说后天会有五条线程在执行等待网络请求

      创建RequestQueue之后我们需要构建出Request然后将其add添加到RequestQueue当中

    public <T> Request<T> add(Request<T> request) {
            // Tag the request as belonging to this queue and add it to the set of current requests.
            request.setRequestQueue(this);
            synchronized (mCurrentRequests) {
                mCurrentRequests.add(request);
            }
    
            // Process requests in the order they are added.
            request.setSequence(getSequenceNumber());
            request.addMarker("add-to-queue");
    
            // If the request is uncacheable, skip the cache queue and go straight to the network.
            if (!request.shouldCache()) {
                mNetworkQueue.add(request);
                return request;
            }
            mCacheQueue.add(request);
            return request;
         }



       前面忘记交代RequestQueue当中会有两条优先级队列


    PriorityBlockingQueue<Request<?>> mCacheQueue =new PriorityBlockingQueue<>();   
    缓存队列PriorityBlockingQueue<Request<?>> mNetworkQueue = new PriorityBlockingQueue<>(); 网络请求队列分别用来管理各自当中的request  
     从上面的add()当中的源码可知.volley会按照是否需要缓存调到多对应的队列当中,分别出队列交给CacheDispatcher实例以及NetworkDispatcher实例处理请求。
    接下来先看看CacheDispatcher的处理方式其run方法
    @Override
        public void run() {
            if (DEBUG) VolleyLog.v("start new dispatcher");
            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
    
            // Make a blocking call to initialize the cache.
            mCache.initialize();
    
            while (true) {
                try {
                    // Get a request from the cache triage queue, blocking until
                    // at least one is available.
                    final Request<?> request = mCacheQueue.take();
                    request.addMarker("cache-queue-take");
    
                    // If the request has been canceled, don't bother dispatching it.
                    if (request.isCanceled()) {
                        request.finish("cache-discard-canceled");
                        continue;
                    }
    
                    // Attempt to retrieve this item from cache.
                    Cache.Entry entry = mCache.get(request.getCacheKey());
                    if (entry == null) {
                        request.addMarker("cache-miss");
                        // Cache miss; send off to the network dispatcher.
                        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.
                        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);
                        }
                    }
    
                } catch (InterruptedException e) {
                    // We may have been interrupted because it was time to quit.
                    if (mQuit) {
                        return;
                    }
                }
            }
        }

    可以看到缓存线程当中有一个while循环一直在处理request请求,先会去cache当中获取是否存在,并查看是否数据是否已经过期,否则重新添加到networkQueue(
    这个NetworkQueue就是在RequestQueue start()执行构建CacheDispatcher时创建并传入的)如果存在并数据有效调用
    mDelivery.postResponse(request, response);方法将数据返回mDelivery即在RequestQueue创建时构建的new ExecutorDelivery(new Handler(Looper.getMainLooper()))
    查看ExecutorDelivery 的postResponse方法
    ExecutorDeliveryd

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

    查看ExecutorDelivery 的postResponse方法

     

    public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
            request.markDelivered();
            request.addMarker("post-response");
            mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
        }
    
      private class ResponseDeliveryRunnable implements Runnable {
            private final Request mRequest;
            private final Response mResponse;
            private final Runnable mRunnable;
    
            public ResponseDeliveryRunnable(Request request, Response response, Runnable runnable) {
                mRequest = request;
                mResponse = response;
                mRunnable = runnable;
            }
    
            @SuppressWarnings("unchecked")
            @Override
            public void run() {
                // If this request has canceled, finish it and don't deliver.
                if (mRequest.isCanceled()) {
                    mRequest.finish("canceled-at-delivery");
                    return;
                }
    
                // Deliver a normal response or error, depending.
                if (mResponse.isSuccess()) {
                    mRequest.deliverResponse(mResponse.result);
                } else {
                    mRequest.deliverError(mResponse.error);
                }
    
                // If this is an intermediate response, add a marker, otherwise we're done
                // and the request can be finished.
                if (mResponse.intermediate) {
                    mRequest.addMarker("intermediate-response");
                } else {
                    mRequest.finish("done");
                }
    
                // If we have been provided a post-delivery runnable, run it.
                if (mRunnable != null) {
                    mRunnable.run();
                }
           }
        }

    从以上可以看出,最后调用handle.post方法将结果返回到主线程   通过mRequest.deliverResponse(mResponse.result);回调到了request的方法  而request的抽象方法deliverResponse在StringRequest等都会有实现  

    可以看到最终还是通过Handler.post的方式回到而来主线程,而Handler则是在RequestQueue构造过程中new出Handler传递给ExecutorDelivery的。看一下RequestQueue当中

    的构造方法

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

    new Handler(Loop.getMainLooper())RequestQueue实例创建时候还在主线程。从这这种就构建好ExecutorDelviery实例

    CacheDispatcher中会用传进取得Cache实现类DiskBasedCache来实现缓存

    DiskBasedCache中有

    private final Map<String, CacheHeader> mEntries =
            new LinkedHashMap<String, CacheHeader>(16, .75f, true);
    作为缓存数据同时会将data数据写入磁盘当中
    public synchronized void put(String key, Entry entry) {
            pruneIfNeeded(entry.data.length);
            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();
                putEntry(key, e);
                return;
            } catch (IOException e) {
            }
            boolean deleted = file.delete();
            if (!deleted) {
                VolleyLog.d("Could not clean up file %s", file.getAbsolutePath());
            }
        }



     @Override
        protected void deliverResponse(String response) {
            if (mListener != null) {
                mListener.onResponse(response);
            }
        }
    最后真正会掉到了request传来的listener
    这个具体的deliverResponse()方法当然最后可以自己具体的做实现,同样错误时一样的道理
       同理在查看NetworkDispatch

    @Override
        public void run() {
            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
            while (true) {
                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");
                        continue;
                    }
    
                    addTrafficStatsTag(request);
    
                    // Perform the network request.
                    NetworkResponse networkResponse = mNetwork.performRequest(request);
                    request.addMarker("network-http-complete");
    
                    // If the server returned 304 AND we delivered a response already,
                    // we're done -- don't deliver a second identical response.
                    if (networkResponse.notModified && request.hasHadResponseDelivered()) {
                        request.finish("not-modified");
                        continue;
                    }
    
                    // Parse the response here on the worker thread.
                    Response<?> response = request.parseNetworkResponse(networkResponse);
                    request.addMarker("network-parse-complete");
    
                    // Write to cache if applicable.
                    // TODO: Only update cache metadata instead of entire record for 304s.
                    if (request.shouldCache() && response.cacheEntry != null) {
                        mCache.put(request.getCacheKey(), response.cacheEntry);
                        request.addMarker("network-cache-written");
                    }
    
                    // Post the response back.
                    request.markDelivered();
                    mDelivery.postResponse(request, response);
                } catch (VolleyError volleyError) {
                    volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
                    parseAndDeliverNetworkError(request, volleyError);
                } 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);
                }
            }
        }



    调用BasicNetwork中的performRequest执行网络请求可以看到
    responseContents = inputStreamToBytes(inputStream, httpResponse.getContentLength());Volley使用byte[]接收网络返回的数据的。所以不建议有大数据量返回时
    使用volley作为网络请求

    @Override
        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 =
                            getCacheHeaders(request.getCacheEntry());
                    httpResponse = mBaseHttpStack.executeRequest(request, additionalRequestHeaders);
                    int statusCode = httpResponse.getStatusCode();
    
                    responseHeaders = httpResponse.getHeaders();
                    // Handle cache validation.
                    if (statusCode == HttpURLConnection.HTTP_NOT_MODIFIED) {
                        Entry entry = request.getCacheEntry();
                        if (entry == null) {
                            return new NetworkResponse(HttpURLConnection.HTTP_NOT_MODIFIED, null,
                                    responseHeaders, true,
                                    SystemClock.elapsedRealtime() - requestStart);
                        }
                        // Combine cached and response headers so the response will be complete.
                        List<Header> combinedHeaders = combineHeaders(responseHeaders, entry);
                        return new NetworkResponse(HttpURLConnection.HTTP_NOT_MODIFIED, entry.data,
                                combinedHeaders, true,
                                SystemClock.elapsedRealtime() - requestStart);
                    }
    
                    // 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;
                    logSlowRequests(requestLifetime, request, responseContents, statusCode);
    
                    if (statusCode < 200 || statusCode > 299) {
                        throw new IOException();
                    }
                    return new NetworkResponse(statusCode, responseContents, responseHeaders, false,
                            SystemClock.elapsedRealtime() - requestStart);
                } 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,
                                responseHeaders, false, SystemClock.elapsedRealtime() - requestStart);
                        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());
                    }
                }
            }
        }


    mBaseHttpStack.executeRequest(request, additionalRequestHeaders);
    即调用的是刚开始RequestQueue构建时传入的HttpStack实例的executeRequest方法。所以在HttpStack的构建当中可以做出可以合适的选择,目前来说volley是支持
    HttpURLConnection,HttpClient以及OkHttp的
    从源码看Volley还对网络错误做了相应的处理
    多级别取消:

    request.cancel();以及requestQueue.cancalAll()在BasicNetwork的performRequest方法中有做request是否取消的判断

       if (request.isCanceled()) {
                        request.finish("network-discard-cancelled");
                        request.notifyListenerResponseNotUsable();
                        continue;
                    }

    同样requestQueue当中有对请求队列当中取消请求的方法

     /**
         * Cancels all requests in this queue with the given tag. Tag must be non-null
         * and equality is by identity.
         */
        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;
                }
            });
        }

    volley与activity的联动在activity的onStop方法当中添加如下代码即可

    @Override
        protected void onStop() {
            super.onStop();
            //通过Tag标签取消请求队列中对应的全部请求
            MyApplication.getHttpQueue().cancelAll("abcPost");
        }

    volley配置重新请求retry

    stringRequest.setRetryPolicy(new DefaultRetryPolicy(timeout * 1000, retrytime, 0.0f));
    具体查看volley重试
    
    
    
    
    Volley的缓存实现
    Request当中有对应的mShouldCache用来决定是否需要缓存
    而在向RequestQueue当中添加request的时候 会按照request的mShouldCache的值来判断是加入到cacheQueue还是networkQueue
      public <T> Request<T> add(Request<T> request) {
            // Tag the request as belonging to this queue and add it to the set of current requests.
            request.setRequestQueue(this);
            synchronized (mCurrentRequests) {
                mCurrentRequests.add(request);
            }
    
            // Process requests in the order they are added.
            request.setSequence(getSequenceNumber());
            request.addMarker("add-to-queue");
    
            // If the request is uncacheable, skip the cache queue and go straight to the network.
            if (!request.shouldCache()) {
                mNetworkQueue.add(request);
                return request;
            }
            mCacheQueue.add(request);
            return request;
         }

    而上面知道了 start的时候会调用对应的Dispather执行对应的操作
    
    
    
    
    Volley的多级别退出以及与Fragment、Activity联动
    在RqeustQueue当中
        public void cancelAll(RequestFilter filter) {
            synchronized (mCurrentRequests) {
                for (Request<?> request : mCurrentRequests) {
                    if (filter.apply(request)) {
                        request.cancel();
                    }
                }
            }
        }

    在request当中
    public void cancel() {
            mCanceled = true;
        }
    最后在NetworkDiapatcher中
           if (request.isCanceled()) {
                        request.finish("network-discard-cancelled");
                        request.notifyListenerResponseNotUsable();
                        continue;
                    }

    而在CacheDispatcher当中
         if (request.isCanceled()) {
                        request.finish("cache-discard-canceled");
                        continue;
                    }
    

    所以可以在Activity或者Fragment当中的onDestroy当中调用cancel方法即可
    
    
    volley重请求实现RetryPolicy  然后通过
    stringRequest.setRetryPolicy(。。。);设置
    Volley顺序请求
    request当中有sequence字段
    为什么说Volley适合频繁但是数据量小的网络请求而不适合大数据量的网络请求
    
    
    在BasicNetWork的performRequest当中会发现
    byte[] responseContents = null;
    ...
    
    responseContents =
                              inputStreamToBytes(inputStream, httpResponse.getContentLength());


    这就是说volley在做网络请求时使用的是byte[]数组进行的接收而不是使用的stream,这样当遇到大文件时容易导致内存溢出
    接下来对应的流转化成byte[]
    protected final ByteArrayPool mPool;
     private byte[] inputStreamToBytes(InputStream in, int contentLength)
                throws IOException, ServerError {
            PoolingByteArrayOutputStream bytes =
                    new PoolingByteArrayOutputStream(mPool, contentLength);
            byte[] buffer = null;
            try {
                if (in == null) {
                    throw new ServerError();
                }
                buffer = mPool.getBuf(1024);
                int count;
                while ((count = in.read(buffer)) != -1) {
                    bytes.write(buffer, 0, count);
                }
                return bytes.toByteArray();
            } finally {
                try {
                    // Close the InputStream and release the resources by "consuming the content".
                    if (in != null) {
                        in.close();
                    }
                } catch (IOException e) {
                    // This can happen if there was an exception above that left the stream in
                    // an invalid state.
                    VolleyLog.v("Error occurred when closing InputStream");
                }
                mPool.returnBuf(buffer);
                bytes.close();
            }
        }
    

    ByteArrayPool 因为ByteArrayPool的存在而适合
    网络请求得到数据,就需要在内存开辟一个控件来存储数据;当网络请求频繁的时候,我们先要在堆中开辟存储空间,显示以后再合适的情况下有GC回收,这样频繁的时候GC的
    工作量会很大,然后导致客户端有压力,于是有了ByteArrayPool字节数据缓存池
    ByteArrayPool会有mPool.getBuf(1024);以及mPool.returnBuf(buffer);来完成从缓存区取和还的过程
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    http://blog.csdn.net/ss1168805219/article/details/52039444
    
    
    
    
    
    
    
    最后理一下Volley的整个执行过程 NetworkDiapatcher 以及CacheDiapatcher两个线程进行网络请求的处理,NetworkDispatcher调用BasicNetwork(implements Network)的
    performRequest方法这之中调用httpStack的performRequst返回响应数据经BasicNetwork处理(这种中又byte[]接收ByteArrayPool加大了内存使用效率)请求状态码并返回
    对应的响应数据给NetworkDispatcher。之后NetworkDispatcher调用Request的parseNetworkResponse返回Response。最后通过ExecutorDelivery的postResponse由Handler的
    post回到主线程。调用request的deliverResponse方法调用Listener来进行回调。走完整个流程与NetworkDispatcher不同的是CacheDispatcher仅仅只是有缓存直接返回Response
    
    
    画了一个草图
    
    
    
    
    
    

    
    
    
    
    
    
    
    
    
    
    
    
    展开全文
  • Volley源码分析

    2017-10-16 12:26:09
    Volley源码分析,缓存策略

    Volley源码分析之流程和缓存

    Volley演讲配图

    前言

    Android一开始提供了HttpURLConnection和HttpClient两种方式请求网络,但是这两种控制粒度太细,需要自己做很多操作,同时也导致了需要写很多的重复代码。为此,2013年Google推出Volley网络请求框架,基于此框架,开发者只需设置请求url,传递参数和请求成功或失败的回调的方法即可,大大简化了网络请求;虽然现在Okhttp很流行,但Volley的源码非常值得学习;

    Volley官方介绍文档(需翻墙)

    使用

    可分为三个步骤,简称三部走:

    1. 创建RequestQueue实例
    2. 创建StringRequest实例
    3. 调用RequestQueue实例方法add将request作为参数传入

    StringRequest中并没有提供设置POST参数的方法,但是当发出POST请求的时候,Volley会尝试调StringRequest的父类——Request中的getParams()方法来获取POST参数,那么解决方法自然也就有了,我们只需要在StringRequest的匿名类中重写getParams()方法,在这里设置POST参数就可以了。示范代码如下:

    String requestUrl = "http://yourapi";//建立请求的api的URL;
    
    Response.Listener successListener = new Response.Listener<String>() {//成功回调
       @Override
       public void onResponse(String response) {
           Log.d("TAG", response);
       }
    };
    
    Response.ErrorListener errorListener = new Response.ErrorListener() {//失败回调
       @Override
       public void onErrorResponse(VolleyError error) {
           Log.e("TAG", error.getMessage(), error);
       }
    };
    
    StringRequest stringRequest = new StringRequest(Request.Method.POST, requestUrl,  successListener, errorListener) {
       @Override
       protected Map<String, String> getParams() throws AuthFailureError {
           //重写getParams方法传POST请求参数
           Map<String, String> map = new HashMap<String, String>();
           map.put("phone", "phone num");
           map.put("passwd", "passwd");
           return map;
       }
    };
    
    Volley.newRequestQueue(mContext).add(stringRequest);//创建newRequestQueue并传入参数

    请求成功后,Logcat中会打印出服务器返回的信息;

    源码分析

    Volley适合通信频繁及数据量少的应用,大多展示类的App都适用;不适合繁重的下载或者流的操作,因为Volley会把解析到的响应数据保持在内存中;

    首先从我们一开始的三部走的第一步开始,创建RequestQueue实例,代码语句

    Volley.newRequestQueue(mContext)

    跟进代码查看

    public static RequestQueue newRequestQueue(Context context) {
        return newRequestQueue(context, null);
    }

    然后调用了两个参数的newRequestQueue方法,默认第二个参数HttpStack为null;

    /**
     * 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.
     */
    public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
        File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);//缓存目录
    
        String userAgent = "volley/0";
        try {
            String packageName = context.getPackageName();
            PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
            userAgent = packageName + "/" + info.versionCode;
        } catch (NameNotFoundException e) {
        }
    
        if (stack == null) {
            if (Build.VERSION.SDK_INT >= 9) {//根据系统版本号判断适用哪个请求方式
                stack = new HurlStack();
            } else {
                // Prior to Gingerbread, HttpUrlConnection was unreliable.
                // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
                stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
            }
        }
    
        Network network = new BasicNetwork(stack);
    
        RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);//进入DiskBasedCache构造方法可知,此处设置缓存目录,以及设定了一个默认5M的缓存大小;
        queue.start();
    
        return queue;
    }

    HttpStack有实现两个类:HttpClientStack和HurlStack,都实现了performRequest方法,主要的作用是执行网络请求操作并获取返回结果;HttpClientStack使用了HttpClient进行网络通信,而HttpStack是使用HttpURLConnection;当系统版本小于9时使用HttpClientStack,反之使用HurlStack;这个判断主要是因为系统版本小于9时HttpURLConnection有bug,调用 close() 函数会影响连接池,导致连接复用失效;稳定性不如HttpClient稳定;而9之后系统作了修改,默认开启了 gzip 压缩,提高了 HTTPS 的性能,HttpURLConnection成了最佳选择;郭霖文章给出的原因

    最后创建了RequestQueue,调用了start方法并返回RequestQueue实例;注意,RequestQueue的创建方法中我们可以看到,自己可以自定义RequestQueue,配置缓存目录,及缓存的大小;然后调用了RequestQueue的start方法

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

    start方法主要创建了CacheDispatcher和NetworkDispatcher,并调用了对应的start方法;CacheDispatcher负责处理调度走缓存逻辑的请求,而NetworkDispatcher则是处理调度走网络的请求;一看start方法可以推测这两个类为Thread的子类,进入看果然是继承了Thread

    public class CacheDispatcher extends Thread{
    
    }

    for循环默认创建4个NetworkDispatcher线程并开启,也就是说当Volley.newRequstQueue这句代码执行时,就创建了5个线程在不断运行,不断阻塞内部的请求队列知道请求的添加才开始下面的处理逻辑;

    Volley.newRequstQueue创建完RequestQueue后,到了三步走的第三部,调用add方法把Request插入RequestQueue中

    public Request add(Request request) {
    
     /*
       ...
       ...隐藏部分代码
       ...*/
        // If the request is uncacheable, skip the cache queue and go straight to the network.
       //判断request是否需要缓存,若不需要缓存则加入mNetworkQueue中,默认Request都需要缓存,       //setShouldCache可改变设置
        if (!request.shouldCache()) {
            mNetworkQueue.add(request);
            return request;
        }
    
        /*
       ...
       ...隐藏部分代码
       ...*/
    
        mWaitingRequests.put(cacheKey, null);
        mCacheQueue.add(request);//默认的request都加入到mCacheQueue
    
            return request;
        }
    }

    默认添加Request都会添加到mCacheQueue,缓存调度会不断轮询此队列;上面说到创建了RequestQueue后会默认开启5个线程,其中包括了缓存线程CacheDispatcher,现在看下CacheDispatcher的run方法

     @Override
        public void run() {
            /*
            ...隐藏部分代码
            */
    
                    final Request request = mCacheQueue.take();//获取队列头部元素,若为空则一直阻塞,采用了BlockingQueue
                    request.addMarker("cache-queue-take");
    
                    // If the request has been canceled, don't bother dispatching it.
                    if (request.isCanceled()) {
                        request.finish("cache-discard-canceled");
                        continue;
                    }
    
                    // Attempt to retrieve this item from cache.
                    Cache.Entry entry = mCache.get(request.getCacheKey());
                    if (entry == null) {
                        request.addMarker("cache-miss");
                        // Cache miss; send off to the network dispatcher.
                        mNetworkQueue.put(request);
                        continue;
                    }
    
                    // If it is completely expired, just send it to the network.
                      //若过期,则添加到NetworkQueue中执行网络请求
                    if (entry.isExpired()) {
                        request.addMarker("cache-hit-expired");
                        request.setCacheEntry(entry);
                        mNetworkQueue.put(request);
                        continue;
                    }
    
                    // We have a cache hit; parse its data for delivery back to the request.
                    request.addMarker("cache-hit");
                    Response<?> response = request.parseNetworkResponse(
                            new NetworkResponse(entry.data, entry.responseHeaders));
                    request.addMarker("cache-hit-parsed");
    
                    if (!entry.refreshNeeded()) {
                        // Completely unexpired cache hit. Just deliver the response.
                        mDelivery.postResponse(request, response);//缓存可用则进行回调
                    } else {
    
                        // Post the intermediate response back to the user and have
                        // the delivery then forward the request along to the network.
                        mDelivery.postResponse(request, response, new Runnable() {
                            @Override
                            public void run() {
                                try {
                                    mNetworkQueue.put(request);
                                } catch (InterruptedException e) {
                                    // Not much we can do about this.
                                }
                            }
                        });
                    }
    
                } catch (InterruptedException e) {
                    // We may have been interrupted because it was time to quit.
                    if (mQuit) {
                        return;
                    }
                    continue;
                }
            }
        }

    可以看到开启线程后会一直阻塞直到获取缓存队列中插入的数据,阻塞队列使用了BlockingQueue,可另外了解;获取后查找缓存中是否有response,若response过期或需要刷新则会向mNetworkQueue中插入,走网络请求,反之则使用缓存中的response;若缓存可用便调用request的parseNetworkResponse解析数据,然后就是调用mDelivery.postResponse回调;mDelivery默认的实现是创建RequestQueue时候创建的

    public RequestQueue(Cache cache, Network network, int threadPoolSize) {
        this(cache, network, threadPoolSize,
                new ExecutorDelivery(new Handler(Looper.getMainLooper())));
    }
    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);
            }
        };
    }

    mDelivery.postResponse最终会调用ExecutorDelivery的execute方法,其传入的参数ResponseDeliveryRunnable,最终回调用handler的post方法把线程切换到主线程;ResponseDeliveryRunnable的run方法执行在主线程,如下:

    public void run() {
        /*
        隐藏部分代码
        */
        // Deliver a normal response or error, depending.
        if (mResponse.isSuccess()) {
            mRequest.deliverResponse(mResponse.result);//回调到我们一开始创建的Listenner回调中
        } 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) {//响应需要刷新的时候为true
            mRequest.addMarker("intermediate-response");
        } else {
            mRequest.finish("done");
        }
    
        // If we have been provided a post-delivery runnable, run it.
        if (mRunnable != null) {
            mRunnable.run();
        }

    可以看到若成功了则调用mRequest.deliverResponse(mResponse.result);这句代码,而deliverResponse则调用了我们一开始创建的StringRequest代码的Response.Listener的onResponse,我们便可以在这里处理返回来的响应并更新View;

    现在看下当没有缓存或缓存实效时,使用网络获取响应的NetworkDispatcher,看下run方法:

    @Override
    public void run() {
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        Request request;
        while (true) {
            try {
                // Take a request from the queue.
                request = mQueue.take();//从Volley中创建RequestQueue后调用start方法中,同用一个队列传进构造方法可知:mQueue和刚才CacheDispatcher的mNetworkQueue是同一个队列,
            } catch (InterruptedException e) {
                // We may have been interrupted because it was time to quit.
                if (mQuit) {
                    return;
                }
                continue;
            }
    
                // Perform the network request.
           NetworkResponse networkResponse = mNetwork.performRequest(request);//网络请求,实际上mNetwork的实现是BasicNetwork
           request.addMarker("network-http-complete");
    
         /*
         隐藏部分代码
         */
                // 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);
             /*
         隐藏部分代码
         */
        }
    }

    mNetwork.performRequest这句代码实现了请求代码,mNetwork的唯一实现为BasicNetwork,其performRequest方法主要根据HttpStack来执行网络请求并构造NetworkResponse返回;然后就是调用mDelivery.postResponse(request, response)方法回调,跟CacheDispatcher一样的流程了;

    Volley.newRequestQueue(mContext).add(stringRequest)

    到此,这句代码的流程完全走了一遍;总结一下:调用newRequestQueue创建了RequestQueue,同时开启了CacheDispatcher和NetworkDispatcher 5个线程不断阻塞轮询,当调用add方法时,会先在缓存队列mCacheQueue中插入request,此时缓存线程CacheDispatcher先查找缓存中是否有该request的响应缓存,若有且没有过期,且不需要刷新操作,则request自身的parseNetworkResponse方法解析成response且回调给主线程自己创建request的Response.Listener中的onResponse,若过期,没有缓存或需要刷新,则添加到mNetworkQueue中,NetworkDispatcher轮询获取request后,根据系统版本使用HttpClient或HttpURLConnection执行网络请求,得到响应后解析并回调给主线程,若request需要缓存且响应不为null,则存入响应缓存中;

    流程图总结如下:

    这里写图片描述
    Volley原理图

    缓存策略

    • request的缓存

    现在我们从缓存的角度分析下源码,当调用Volley.newRequestQueue(mContext).add(stringRequest)时,我们进入RequestQueue的add方法

    public Request add(Request request) {
        // Tag the request as belonging to this queue and add it to the set of current requests.
        request.setRequestQueue(this);//把request与当前RequestQueue建立联系
        synchronized (mCurrentRequests) {
            mCurrentRequests.add(request);//存放所有的request
        }
    
        // Process requests in the order they are added.
        request.setSequence(getSequenceNumber());
        request.addMarker("add-to-queue");
    
        // If the request is uncacheable, skip the cache queue and go straight to the network.
        if (!request.shouldCache()) {//是否应该缓存,默认所有request都缓存
            mNetworkQueue.add(request);
            return request;
        }
    
        // Insert request into stage if there's already a request with the same cache key in flight.
        synchronized (mWaitingRequests) {
            String cacheKey = request.getCacheKey();
            if (mWaitingRequests.containsKey(cacheKey)) {//当前add的request是否已经有相同的请求,若请求没有完成(成功或者失败),则会一直存在mWaitingRequest中
                // There is already a request in flight. Queue up.
                Queue<Request> stagedRequests = mWaitingRequests.get(cacheKey);
                if (stagedRequests == null) {
                    stagedRequests = new LinkedList<Request>();
                }
                stagedRequests.add(request);//创建一个队列并添加request
                mWaitingRequests.put(cacheKey, stagedRequests);//缓存含request的队列
                if (VolleyLog.DEBUG) {
                    VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);
                }
            } else {
                // Insert 'null' queue for this cacheKey, indicating there is now a request in
                // flight.
                mWaitingRequests.put(cacheKey, null);//如果request没有则存入map中
                mCacheQueue.add(request);
            }
            return request;
        }
    }

    当add方法调用时,会判断mWaitingRequests是否包含cacheKey,假设第一个request请求mWaitingRequests不包含,则缓存cacheKey,键值为null,同时添加入缓存队列。当第一个request还在请求中,第二个和第三个相同的request这时添加进来,则走进入逻辑新建一个队列并加入两个request,和cacheKey缓存进mWaitingRequests的map中,注意此时缓存的都是request,当第一个request请求完成后会回调request的finish方法

    void finish(final String tag) {
        if (mRequestQueue != null) {
            mRequestQueue.finish(this);//调用RequestQueue的finish方法
        }
       /*
       隐藏部分代码
       */
    }
    

    而request的finish方法会调用RequestQueue的finish方法

    void finish(Request request) {
        // Remove from the set of requests currently being processed.
        synchronized (mCurrentRequests) {
            mCurrentRequests.remove(request);
        }
    
        if (request.shouldCache()) {
            synchronized (mWaitingRequests) {
                String cacheKey = request.getCacheKey();
                Queue<Request> waitingRequests = mWaitingRequests.remove(cacheKey);//移除该request的cacheKey,并得到缓存时创建的request队列
                if (waitingRequests != null) {
                    if (VolleyLog.DEBUG) {
                        VolleyLog.v("Releasing %d waiting requests for cacheKey=%s.",
                                waitingRequests.size(), cacheKey);
                    }
                    // Process all queued up requests. They won't be considered as in flight, but
                    // that's not a problem as the cache has been primed by 'request'.
                    mCacheQueue.addAll(waitingRequests);//若缓存起来的队列不为空,把里面的request全部添加到缓存队列;
                }
            }
        }
    }

    当waitingRequests中的request添加到缓存队列后,又进入到CacheDispatcher的调度线程中了

    • CacheDispatcher里的缓存

    又从CacheDispatcher的run分析,这次是从缓存角度;主要有三点

    1. 没有缓存,添加到网络请求队列
    2. 缓存过期,添加到网络请求队列
    3. 缓存需要刷新,先回调数据给用户,再添加请求到网络请求队列
    public void run() {
        if (DEBUG) VolleyLog.v("start new dispatcher");
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
    
        // Make a blocking call to initialize the cache.
        mCache.initialize();
    
        while (true) {
            try {
                // Get a request from the cache triage queue, blocking until
                // at least one is available.
                final Request request = mCacheQueue.take();
                request.addMarker("cache-queue-take");
    
                // If the request has been canceled, don't bother dispatching it.
                if (request.isCanceled()) {
                    request.finish("cache-discard-canceled");
                    continue;
                }
    
                // Attempt to retrieve this item from cache.
                Cache.Entry entry = mCache.get(request.getCacheKey());
                if (entry == null) {//没有缓存,添加到网络请求队列
                    request.addMarker("cache-miss");
                    // Cache miss; send off to the network dispatcher.
                    mNetworkQueue.put(request);
                    continue;
                }
    
                // If it is completely expired, just send it to the network.
                if (entry.isExpired()) {//缓存过期,添加到网络请求队列
                    request.addMarker("cache-hit-expired");
                    request.setCacheEntry(entry);
                    mNetworkQueue.put(request);
                    continue;
                }
    
                // We have a cache hit; parse its data for delivery back to the request.
                request.addMarker("cache-hit");
                Response<?> response = request.parseNetworkResponse(
                        new NetworkResponse(entry.data, entry.responseHeaders));
                request.addMarker("cache-hit-parsed");
    
                if (!entry.refreshNeeded()) {//不需要刷新,回调给用户设置的listener
                    // 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;//在需要刷新时置为true,在mDelivery.postResponse后,会调用ResponseDeliveryRunnable的run方法,其中中有个判断决定是添加信息或者调用request的finish方法
    
                    // Post the intermediate response back to the user and have
                    // the delivery then forward the request along to the network.
                    mDelivery.postResponse(request, response, new Runnable() {
                        @Override
                        public void run() {
                            try {
                                mNetworkQueue.put(request);
                            } catch (InterruptedException e) {
                                // Not much we can do about this.
                            }
                        }
                    });
                }
    
            } catch (InterruptedException e) {
                // We may have been interrupted because it was time to quit.
                if (mQuit) {
                    return;
                }
                continue;
            }
        }
    }

    注意这里当需要刷新数据的时候,会先回调用户设置的listener,然后再去发起请求,若数据有变化会再次回调,所以这种情况会回调用户设置的listener两次

    • NetworkDispatcher的缓存

    请求前添加和cache相关的headers

    private void addCacheHeaders(Map<String, String> headers, Cache.Entry entry) {
        // If there's no cache entry, we're done.
        if (entry == null) {
            return;
        }
    
        if (entry.etag != null) {
            headers.put("If-None-Match", entry.etag);
        }
    
        if (entry.serverDate > 0) {
            Date refTime = new Date(entry.serverDate);
            headers.put("If-Modified-Since", DateUtils.formatDate(refTime));
        }
    }

    request的字段含义

    If-None-Match:存文件的Etag(Hash)值,与服务器回应的Etag比较判断是否改变

    If-Modified-Since:缓存文件的最后修改时间

    当网络请求完成后,调用了request中的parseNetworkResponse方法,用我们一开始的StringRequest来分析下。

    protected Response<String> parseNetworkResponse(NetworkResponse response) {
        String parsed;
        try {
            parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
        } catch (UnsupportedEncodingException e) {
            parsed = new String(response.data);
        }
        return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
    }

    此时又调用了HttpHeaderParser.parseCacheHeaders(response)来构建Cache.Entry对象,代码就不放了,主要是用response.headers中的Date,Cache-Control,Expires,ETag来构建对象,然后若request需要缓存及响应不为空则放进响应缓存中

    response字段含义:

    Cache-Control:告诉所有的缓存机制是否可以缓存及哪种类型

    Expires:响应过期的日期和时间

    Last-Modified:请求资源的最后修改时间

    ETag:请求变量的实体标签的当前值

    NetworkDispatcher

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

    存入缓存的mCache的实现是DiskBasedCache,是不是有点熟悉,没错,就是我们一开始Volley.newRequestQueue中创建RequestQueue中的一个参数,我们看下DiskBasedCache的put方法

    public synchronized void put(String key, Entry entry) {
       pruneIfNeeded(entry.data.length);//put之前先修正大小,避免超出限定的缓存大小
       File file = getFileForKey(key);
       try {
           FileOutputStream fos = new FileOutputStream(file);
           CacheHeader e = new CacheHeader(key, entry);
           e.writeHeader(fos);
           fos.write(entry.data);
           fos.close();
           putEntry(key, e);
           return;
       } catch (IOException e) {
       }
       boolean deleted = file.delete();//try出错时会把file给delete
       if (!deleted) {
           VolleyLog.d("Could not clean up file %s", file.getAbsolutePath());
       }
    }

    进入pruneIfNeeded方法

    private void pruneIfNeeded(int neededSpace) {
        if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes) {
            return;
        }
        if (VolleyLog.DEBUG) {
            VolleyLog.v("Pruning old cache entries.");
        }
    
        long before = mTotalSize;
        int prunedFiles = 0;
        long startTime = SystemClock.elapsedRealtime();
    
        Iterator<Map.Entry<String, CacheHeader>> iterator = mEntries.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, CacheHeader> entry = iterator.next();
            CacheHeader e = entry.getValue();
            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));
            }
            iterator.remove();
            prunedFiles++;
    
            if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes * HYSTERESIS_FACTOR) {
                break;
            }
        }
    
        if (VolleyLog.DEBUG) {
            VolleyLog.v("pruned %d files, %d bytes, %d ms",
                    prunedFiles, (mTotalSize - before), SystemClock.elapsedRealtime() - startTime);
        }
    }

    传入需要插入数据的大小参数neededSpace和当前缓存文件总大小的记录mTotalSize计算出是否超出了设定的缓存大小,若超出则获取CacheHeader列表;CacheHeader里包含缓存文件大小等信息,遍历CacheHeader列表删除缓存,直到小于设定缓存的0.9倍,为什么是这个数?猜测是个缓存过大的标记,同时可以留有一定可缓存空间;

    • 如果让你去设计Volley的缓存功能,你要如何增大它的命中率?

      可以知道,在pruneIfNeeded中删除缓存时,并没有判断缓存是否过期,只是遍历来删除,很大程度上会误删刚缓存下来的数据,而过期数据却仍然存在;所以可以在删除前进行一次过期缓存的清除,然后再使用Lru算法清除最近最久未使用的缓存;

    总结一下缓存方面的流程:当从RequestQueue中add的时候会判断request是否可以缓存,若不可以则添加到网络队列;否则添加到缓存队列;进入缓存队列后,CacheDispatcher中不断轮询取出缓存队列中的request,然后判断该request是否已经缓存了响应,响应是否过期,响应是否需要刷新;若request取消、没有缓存响应、过期或需要刷新,则添加request到网络队列;NetworkDispatcher也不断的轮询,取出添加到网络请求队列的request,用BasicNetwork请求网络,请求前添加headers相关的信息,请求返回的响应会被HttpHeaderParser的parseCacheHeaders解析成Cache.Entry对象,若此时的request能缓存且响应不为null,则添加到响应缓存中;

    注:才疏学浅,如有有写错,理解错的地方麻烦指出更正下,非常感谢!

    相关文章的引用

    Volley的缓存策略

    Android Volley完全解析(四),带你从源码的角度理解Volley

    展开全文
  • 一共两个demo: 1.使用volley.jar进行开发的demo 2.将volley源码添加到工程中,进行演示的demo。最重要的是在源码中添加了很多中文注释。
  • google官方最新volley源码,如果你想对volley有更深的认识就下来学习吧
  • Volley 源码解析

    2015-03-19 16:32:00
    Volley 源码解析 Volley 源码解析 1. 功能介绍 1.1. Volley Volley 是 Google 推出的 Android 异步网络请求框架和图片加载框架。在 Google I/O 2013 大会上发布。 名字由来:a burst or ...
  • Volley 源码探索

    2018-11-06 03:36:53
    如果本文帮助到你,本人不胜荣幸,如果浪费了你的时间,本人深感抱歉。...一直听说 Volley 源码写的非常精妙,对于高并发处理非常不错。 研究一番之后,发现确实不错。 先说一说我是怎么看 Volley源码的。 ...
  • Volley 源码分析

    2016-05-03 00:48:35
    Volley 源码分析 http 网络请求到底是怎么发送 http 网络请求的响应是怎么分发 Volley 中关键的类之间的关系
  • Volley 源码解析 本文为 Android 开源项目源码解析 中 Volley 部分 项目地址:Volley,分析的版本:35ce778,Demo 地址:Volley Demo 分析者:grumoon,校对者:huxian99、Trinea,校对状态:完成 1. 功能介绍...
  • Android Volley源码解析

    2016-09-26 16:45:46
    前面一篇文章讲解了Volley加载网络图片,在使用ImageLoader的时候加入了内存缓冲,我说还可以加入文件缓存,其实在Volley源码中已经包含了文件缓存,不需要自己再添加了,Volley源码还是挺庞大的,不可能一一分析...
  • Volley源码个人分析

    2016-03-10 16:48:16
    Android Volley 源码
  • volley源码分析

    2017-07-06 16:41:07
    volley源码分析 背景:在2013年Google I/O大会上推出了一个新的网络通信框架Volley。Volley既可以访问网络取得数据,也可以加载图片,并且在性能方面也进行了大幅度的调整,它的设计目标就是非常适合去进行数据量...
  • Volley源码阅读

    2018-11-03 22:11:32
    Volley源码阅读 如何阅读源码?关于这个,前人有很多的方法。一般我分析这类工程,首先做的第一步是打开这类工程的开发者官网,官网上都会对这个工具库进行介绍。比如,它是什么?能解决什么问题?与其它同类工具...
  • 主要介绍了Volley源码之使用方式和使用场景详解,具有一定参考价值,需要的朋友可以了解下。

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,636
精华内容 654
关键字:

volley源码