Volley源码浅析一

开始

  • Volley的基本用法

    // 创建请求队列
    RequestQueue queue = Volley.newRequestQueue(getApplicationContext());
    String url = "http://xxx";
    // 获取字符串使用StringResult,获取JsonObject使用JsonObjectRequest,获取Bitmap使用ImageRequest
    StringRequest stringRequest = new StringRequest(Request.Method.GET,url,new Response.Listener<String>() {
        @Override
        public void onResponse(String response) {
        	// 回调在主线程
        }
    },
    new Response.ErrorListener() {
        @Override
        public void onErrorResponse(VolleyError error) {
        	// 回调在主线程
        }
    });
    // 设置tag当调用queue.cancelAll(tag)会取消所有拥有这个tag的request,一般用于在onStop()中关闭网络连接
    stringRequest.setTag(this);
    queue.add(stringRequest);
    
  • 本文的流程图如下
    在这里插入图片描述

  • 由上述代码可知,使用Volley的主要步骤包括创建RequestQueue、创建Request、将Request加入到RequestQueue三步,下面一一讲述

源码

1. RequestQueue的创建

  • 1.1 常规用法
    常规做法是调用Volley.newRequestQueue这个API创建,代码如下

    // MainActivity.java
    RequestQueue queue = Volley.newRequestQueue(getApplicationContext());
    // Volley.java
    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 {
                // 小于2.3使用HttpClient忽略
            }
        } else {
            network = new BasicNetwork(stack);
        }
        return newRequestQueue(context, network);
    }
    private static RequestQueue newRequestQueue(Context context, Network network) {
        // network = BasicNetwork(new HurlStack())
        // DEFAULT_CACHE_DIR = "volley"
        File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
        RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
        queue.start();
        return queue;
    }
    public void start() {
        stop();
        //  PriorityBlockingQueue<Request<?>> mCacheQueue = new PriorityBlockingQueue<>();
    	//  PriorityBlockingQueue<Request<?>> mNetworkQueue = new PriorityBlockingQueue<>();
        //  这里的mCacheQueue、mNetWorkQueue都是优先级阻塞队列mCache是传入的DiskBaseCache
        //  mDeliver则是在构造器里面创建的ExecutorDelivery其拥有一个Looper是主线程的Handler
        mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
        mCacheDispatcher.start();
        for (int i = 0; i < mDispatchers.length; i++) {
            NetworkDispatcher networkDispatcher =
                    new NetworkDispatcher(mNetworkQueue, mNetwork, mCache, mDelivery);
            mDispatchers[i] = networkDispatcher;
            networkDispatcher.start();
        }
    }
    
  • 1.2 PriorityBlockingQueue 优先级阻塞队列
    可以把RequestQueue当做缓存队列和网络请求队列的生产者,NetworkDispatcher、CacheDispatcher当做消费者,当然有时候CacheDispatcher也会被当做网络请求队列的生产者(缓存过期、没命中等情况),Request内有一个枚举类定义了4种优先级,默认情况是NORMAL,我们可以重写Request.getPriority从而改变优先级,PriorityBlockingQueue会调用Request.compareTo来判断顺序(优先级越高越先出去,优先级相等则按照FIFO)

    public enum Priority {
           LOW,
           NORMAL,
           HIGH,
           IMMEDIATE
     }
    @Override
    public int compareTo(Request<T> other) {
        Priority left = this.getPriority();
        Priority right = other.getPriority();
        return left == right ? this.mSequence - other.mSequence : right.ordinal() - left.ordinal();
    }
    
  • 1.3 mCacheDispatcher.start
    因为CacheDispatcher派生自Thread所以直接看其run方法

    @Override
    public void run() {
         if (DEBUG) VolleyLog.v("start new dispatcher");
         Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
         // 调用实现类,初始化缓存比如从磁盘读取缓存文件
         mCache.initialize();
         while (true) {
             try {
                 processRequest();
             } catch (InterruptedException e) {
                 // 如果是主动调用quit出循环的,再调用interrupt退出循环
                 if (mQuit) {
                     Thread.currentThread().interrupt();
                     return;
                 }
             }
         }
     }
    
  • 1.3.1 下面首先看看mCache.initialize(),在这里mCache对应于DiskBasedCache

    public synchronized void initialize() {
        // 目录不存在就创建目录,由于目录都没缓存文件肯定也没直接返回
        if (!mRootDirectory.exists()) {
            if (!mRootDirectory.mkdirs()) {
                VolleyLog.e("Unable to create cache dir %s", mRootDirectory.getAbsolutePath());
            }
            return;
        }
        // 获得目录下所有的文件
        File[] files = mRootDirectory.listFiles();
        if (files == null) {
            return;
        }
        for (File file : files) {
            try {
                long entrySize = file.length();
                // 这个装饰类,只是在原有的BufferedInputStream上加上获取剩余字节的方法
                CountingInputStream cis =
                        new CountingInputStream(
                                new BufferedInputStream(createInputStream(file)), entrySize);
                try {
                    CacheHeader entry = CacheHeader.readHeader(cis);
                    // 通过初始化的entry其size等于缓存文件的大小,当网络请求回来其size等于响应体的大小
                    entry.size = entrySize;
                    // 保存到mEntries注意这里LinedHashMap是有序的按照LRU的顺序排序
                    putEntry(entry.key, entry);
                } finally {
                    cis.close();
                }
            } catch (IOException e) {
                // readHeader抛出的IOException在这里被处理掉了,删除了文件
                file.delete();
            }
        }
    }
    
  • 1.3.1.1 CountingInputStream
    CountingInputStream只是在原有的BufferedInputStream上多维护了一个已经读取多少字节的变量,代码如下

    static class CountingInputStream extends FilterInputStream {
        private final long length;
        private long bytesRead;
    
        CountingInputStream(InputStream in, long length) {
            super(in);
            this.length = length;
        }
        @Override
        public int read() throws IOException {
            int result = super.read();
            if (result != -1) {
                bytesRead++;
            }
            return result;
        }
        @Override
        public int read(byte[] buffer, int offset, int count) throws IOException {
            int result = super.read(buffer, offset, count);
            if (result != -1) {
                bytesRead += result;
            }
            return result;
        }
        @VisibleForTesting
        long bytesRead() {
            return bytesRead;
        }
    	 // 还有多少字节没有读
        long bytesRemaining() {
            return length - bytesRead;
        }
    }
    
  • 1.3.1.2 CacheHeader.readHeader

    static CacheHeader readHeader(CountingInputStream is) throws IOException {
    	 // 读取4个字节组成一个int,如果该值不等于所谓的魔法数字就抛出异常 int CACHE_MAGIC = 0x20150306
        int magic = readInt(is);
        if (magic != CACHE_MAGIC) {
            // 这里不用去删除它,最终会被外界捕获到异常将其删除
            throw new IOException();
        }
        // 读取这六个属性
        String key = readString(is);
        String etag = readString(is);
        long serverDate = readLong(is);
        long lastModified = readLong(is);
        long ttl = readLong(is);
        long softTtl = readLong(is);
        List<Header> allResponseHeaders = readHeaderList(is);
        // 通过这7个参数构建了CacheHeader对象
        return new CacheHeader(
                key, etag, serverDate, lastModified, ttl, softTtl, allResponseHeaders);
    }
    
  • 1.3.1.2.1 readInt

    // 读取4个字节组成一个int
    static int readInt(InputStream is) throws IOException {
        int n = 0;
        n |= (read(is) << 0);
        n |= (read(is) << 8);
        n |= (read(is) << 16);
        n |= (read(is) << 24);
        return n;
    }
    
  • 1.3.1.2.2 readLong

    // 读取8个字节组成一个long
    static long readLong(InputStream is) throws IOException {
        long n = 0;
        n |= ((read(is) & 0xFFL) << 0);
        n |= ((read(is) & 0xFFL) << 8);
        n |= ((read(is) & 0xFFL) << 16);
        n |= ((read(is) & 0xFFL) << 24);
        n |= ((read(is) & 0xFFL) << 32);
        n |= ((read(is) & 0xFFL) << 40);
        n |= ((read(is) & 0xFFL) << 48);
        n |= ((read(is) & 0xFFL) << 56);
        return n;
    }
    
  • 1.3.1.2.3 readString

    // 首先读取8个字节以用来确定后面那个字符串的长度,然后再读取该长度将其转化为字符串
    static String readString(CountingInputStream cis) throws IOException {
        long n = readLong(cis);
        byte[] b = streamToBytes(cis, n);
        return new String(b, "UTF-8");
    }
    
  • 1.3.1.2.3.1 streamToBytes

    // 从流中读取指定长度的字节,然后返回
    static byte[] streamToBytes(CountingInputStream cis, long length) throws IOException {
        long maxLength = cis.bytesRemaining();
        // 长度不能是负数或者大于余下的字节数,并且不能大于int的最大值,这里是根据强转判断的
        if (length < 0 || length > maxLength || (int) length != length) {
            throw new IOException("streamToBytes length=" + length + ", maxLength=" + maxLength);
        }
        byte[] bytes = new byte[(int) length];
        // 读取length长度的字节
        new DataInputStream(cis).readFully(bytes);
        return bytes;
    }
    
  • 1.3.1.2.4 readHeaderList

    static List<Header> readHeaderList(CountingInputStream cis) throws IOException {
        // 先读取下有几个响应头
        int size = readInt(cis);
        if (size < 0) {
            throw new IOException("readHeaderList size=" + size);
        }
        List<Header> result =
                (size == 0) ? Collections.<Header>emptyList() : new ArrayList<Header>();
        // 然后一个个读取出来放到result中
        for (int i = 0; i < size; i++) {
            String name = readString(cis).intern();
            String value = readString(cis).intern();
            result.add(new Header(name, value));
        }
        return result;
    }
    
  • 1.3.1.3 putEntry DiskBaseCache内部维护这一个HashMap并且是有序的,从里面读取元素是按照LRU的规格读取的,mTotalSize则维护着当然所有缓存文件的总大小,默认是5m,超出后会从mEntries取出删除

    private void putEntry(String key, CacheHeader entry) {
       if (!mEntries.containsKey(key)) {
            mTotalSize += entry.size;
        } else {
            CacheHeader oldEntry = mEntries.get(key);
            mTotalSize += (entry.size - oldEntry.size);
        }
        mEntries.put(key, entry);
    }
    
  • 1.3.2 processRequest 从缓存阻塞队列中取Request,取不到则阻塞

    private void processRequest() throws InterruptedException {
         // 当Request被添加到缓存队列前会被阻塞
         final Request<?> request = mCacheQueue.take();
         processRequest(request);
    }
    
  • 1.4 networkDispatcher.start

    public void run() {
         Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
         while (true) {
             try {
                 processRequest();
             } catch (InterruptedException e) {
                 if (mQuit) {
                     Thread.currentThread().interrupt();
                     return;
                 }
             }
         }
     }
    
  • 1.4.1 processRequest 从网络请求阻塞队列中取Request,取不到则阻塞

    private void processRequest() throws InterruptedException {
        // 同样当没有request添加到网络请求队列中也会阻塞
        Request<?> request = mQueue.take();
        processRequest(request);
    }
    

2. Request的创建

  • 我们可以自己继承与Request也可以选择直接使用StringRequest、JsonObjectRequest等,下面说说几个重要的方法
    • getCacheKey 该方法会根据Url和Method返回一个字符串,该字符串决定了缓存文件的名称(分成两半取hash值然后相加)

      public String getCacheKey() {
             String url = getUrl();
             int method = getMethod();
             if (method == Method.GET || method == Method.DEPRECATED_GET_OR_POST) {
                 return url;
             }
             return Integer.toString(method) + '-' + url;
         }
      
    • getHeaders,Volley会在做网络请求前调用该方法设置请求参数,所以如果我们要自定义请求头那么可以重写该方法,默认是一个空Map

      public Map<String, String> getHeaders() throws AuthFailureError {
          return Collections.emptyMap();
      }
      
    • getParams, 如果该请求方法是Post,Volley会在网络请求前调用该方法将其作为输出源写给HttpUrlConnection.getOutputStream,默认返回null

      protected Map<String, String> getParams() throws AuthFailureError {
           return null;
      }
      
    • finish 通知RequestQueue该请求结束了,内部调用RequestQueue.finish,如果日志可以打印的话会打印该请求的所有日志

      void finish(final String tag) {
          if (mRequestQueue != null) {
              mRequestQueue.finish(this);
          }
          // 打印日志续文会讲
      }
      // RequestQueue.finish
      <T> void finish(Request<T> request) {
          synchronized (mCurrentRequests) {
              mCurrentRequests.remove(request);
          }
          synchronized (mFinishedListeners) {
              // 回调监听
              for (RequestFinishedListener<T> listener : mFinishedListeners) {
                  listener.onRequestFinished(request);
              }
          }
      }
      
    • parseNetworkResponse 抽象方法需要各子类实现,解析网络响应(可能来自缓存或者网络请求)包装成合适的Response对象返回,比如StringRequest就包装成Response<String>,如果该方法返回null,那么程序会直接crash

    • cancel 取消请求,如果是在主线程调用的该方法那么就可以保证不会回调Request.deliverResponse和Request.deliverError

    • hasHadResponseDelivered 用于判断当前请求是否已经分发了,主要用于SoftTtl过期了,Ttl没过期,该情况会先返回缓存的响应的响应然后再去请求如果响应码是304就调用该方法判断请求是否已经分发了,如果已经分发了那么就不进行第二次分发了

    • getBodyContentType,决定Post和Put请求body的类型

    • shouldCache,决定该请求是否需要缓存,如果不需要加入到网络请求阻塞队列中(网络请求结束后也不会缓存),否则加入缓存阻塞队列中,我们可以调用setShouldCache来控制是否需要缓存

    • getTimeoutMs、getRetryPolicy都跟重试策略,本文的续文会讲到

3. 将Request填加到RequestQueue中

  • 3.1 RequestQueue.add 视请求是否需要缓存决定把其放到缓存队列还是网络请求队列中
    public <T> Request<T> add(Request<T> request) {
        // 标志着当前request属于哪个RequestQueue
         request.setRequestQueue(this);
         synchronized (mCurrentRequests) {
             mCurrentRequests.add(request);
         }
         // 设置序列号从零开始0、1、2
         request.setSequence(getSequenceNumber());
         // 设置日志,最后讲
         request.addMarker("add-to-queue");
         // 如果这个请求不需要缓存,那么跳过加入缓存队列,直接添加到网络请求队列
         if (!request.shouldCache()) {
             mNetworkQueue.add(request);
             return request;
         }
         mCacheQueue.add(request);
         return request;
     }
    

4. 假设Request需要缓存CacheDispatcher就能从缓存队列中取到Request,就能脱离阻塞调用processRequest(Request)

  • 4.1 processRequest(Request)

    void processRequest(final Request<?> request) throws InterruptedException {
        request.addMarker("cache-queue-take");
        // 如果在被添加到缓存队列前就已经取消了直接结束该请求
        if (request.isCanceled()) {
        	  // 内部会将自己从RequestQueue中移除,回调RequestFinishedListener.onRequestFinished()
        	  // 并且打印日志(如果开启了等级为VERBOSE)
            request.finish("cache-discard-canceled");
            return;
        }
        // 尝试从缓存中恢复
        Cache.Entry entry = mCache.get(request.getCacheKey());
        if (entry == null) {
            request.addMarker("cache-miss");
            // 缓存没命中,如果已经有了相同缓存key的请求并且响应还没下来,那么不需要加入到网络请求队列
            // 否则加入到缓存队列中去,这会使4个NetworkDispatcher中的一个脱离阻塞
            if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
                mNetworkQueue.put(request);
            }
            return;
        }
        // 如果缓存的内容已经完全过期了那么只能再请求网络,内部判断ttl与System.currentTimeMillis
        // 满足ttl >= softTtl
        if (entry.isExpired()) {
        	 // 缓存命中了但是过期了
            request.addMarker("cache-hit-expired");
            // 设置一下过期的缓存内容,内部的etag,last_modify将会在下次请求时带给服务端
            request.setCacheEntry(entry);
            // 与上面一样
            if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
                mNetworkQueue.put(request);
            }
            return;
        }
        // 下面就是缓存命中了,并且没有完全过期
        request.addMarker("cache-hit");
        // 组装NetWorkResponse对象,给Request解析
        Response<?> response =
                request.parseNetworkResponse(
                        new NetworkResponse(entry.data, entry.responseHeaders));
        request.addMarker("cache-hit-parsed");
        // 判断softTtl < System.currentTimeMillis()
        if (!entry.refreshNeeded()) {
            // 如果softTtl也小于当前时间,那么缓存有效,直接分发响应
            mDelivery.postResponse(request, response);
        } else {
            // softTtl已经大于当前时间了,也可以直接分发这个响应,但是分发后还要再去请求一下网络
            request.addMarker("cache-hit-refresh-needed");
            request.setCacheEntry(entry);
            // 标记当前的响应是中间态,可能该请求会触发两次响应
            response.intermediate = true;
            if (!mWaitingRequestManager.maybeAddToWaitingRequests(request)) {
                // 分发响应然后再把请求放入网络请求队列中		
                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 {
                // 已经有同样的请求了那么就直接分发响应
                mDelivery.postResponse(request, response);
            }
        }
    }
    
  • 4.1.1 mCache.get 其中mCache对应DiskBaseCache

    public synchronized Entry get(String key) {
        CacheHeader entry = mEntries.get(key);
        // 如果缓存内容不存在返回null
        if (entry == null) {
            return null;
        }
        // 内部根据key获取了文件名然后创建了一个File对象,创建文件名的规格是把key平均分成两部分,分别计算
        // hash然后拼接在一起,感觉是为了提高不重复的概率
        File file = getFileForKey(key);
        try {
            CountingInputStream cis =
                    new CountingInputStream(
                            new BufferedInputStream(createInputStream(file)), file.length());
            try {
                // 再读一遍防止文件名与缓存内容对不上
                CacheHeader entryOnDisk = CacheHeader.readHeader(cis);
                // 不相等的情况有两个缓存文件互相名字交换等情况
                if (!TextUtils.equals(key, entryOnDisk.key)) {
                    // 从Map中移除,返回null
                    removeEntry(key);
                    return null;
                }
                // 读取响应体
                byte[] data = streamToBytes(cis, cis.bytesRemaining());
                // 将响应头与响应体进行组合返回一个Entry
                return entry.toCacheEntry(data);
            } finally {
                cis.close();
            }
        } catch (IOException e) {
        	  // 读取失败也从Map中移除,并且将缓存文件删除,返回null
            remove(key);
            return null;
        }
    }
    
  • 4.1.1.1 getFileForKey 根据缓存key创建File对象 getFilenameForKey根据缓存key获取缓存文件名

    public File getFileForKey(String key) {
      return new File(mRootDirectory, getFilenameForKey(key));
    }
    private String getFilenameForKey(String key) {
        int firstHalfLength = key.length() / 2;
        String localFilename = String.valueOf(key.substring(0, firstHalfLength).hashCode());
        localFilename += String.valueOf(key.substring(firstHalfLength).hashCode());
        return localFilename;
    }
    
  • 4.1.1.2 entry.toCacheEntry 将缓存的响应头与缓存的响应体进行组合

    Entry toCacheEntry(byte[] data) {
        Entry e = new Entry();
        e.data = data;
        e.etag = etag;
        e.serverDate = serverDate;
        e.lastModified = lastModified;
        e.ttl = ttl;
        e.softTtl = softTtl;
        e.responseHeaders = HttpHeaderParser.toHeaderMap(allResponseHeaders);
        e.allResponseHeaders = Collections.unmodifiableList(allResponseHeaders);
        return e;
    }
    
  • 4.1.1.2.1 HttpHeaderParser.toHeaderMap

    static Map<String, String> toHeaderMap(List<Header> allHeaders) {
         // 创建一个大小写不敏感的TreeMap也就是忽略大小写,比如Expired与expired会被认为相同
         Map<String, String> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
         // 后面的响应头具有高优先级
         for (Header header : allHeaders) {
             headers.put(header.getName(), header.getValue());
         }
         return headers;
     }
    
  • 4.1.2 request.parseNetworkResponse 来看看StringRequest是怎么执行的

    // 从请求头中读出charset然后把响应体转化为一个字符串
    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));
    }
    
  • 4.1.3 HttpHeaderParser.parseCacheHeaders 该方法会返回一个Cache.Entry,如果调用parseNetworkResponse的是NetworkDispatcher的话会把这个Cache.Entry写入缓存文件,如果是CacheDispatcher调用的话那么基本没什么用

    public static Cache.Entry parseCacheHeaders(NetworkResponse response) {
        long now = System.currentTimeMillis();
        Map<String, String> headers = response.headers;
        long serverDate = 0;
        long lastModified = 0;
        long serverExpires = 0;
        long softExpire = 0;
        long finalExpire = 0;
        long maxAge = 0;
        long staleWhileRevalidate = 0;
        boolean hasCacheControl = false;
        boolean mustRevalidate = false;
        String serverEtag = null;
        String headerValue;
        headerValue = headers.get("Date");
        if (headerValue != null) {
        	  // 将服务器返回的处理请求的时间转化为long
            serverDate = parseDateAsEpoch(headerValue);
        }
        headerValue = headers.get("Cache-Control");
        if (headerValue != null) {
            hasCacheControl = true;
            String[] tokens = headerValue.split(",", 0);
            for (int i = 0; i < tokens.length; i++) {
                String token = tokens[i].trim();
                // 不使用缓存直接返回null
                if (token.equals("no-cache") || token.equals("no-store")) {
                    return null;
                } else if (token.startsWith("max-age=")) {
                    try {
                    	   // 缓存的内容将在maxAge秒后失效
                        maxAge = Long.parseLong(token.substring(8));
                    } catch (Exception e) {
                    }
                    // 在staleWhileRevalidate内可以直接把缓存内容返回
                } else if (token.startsWith("stale-while-revalidate=")) {
                    try {
                        staleWhileRevalidate = Long.parseLong(token.substring(23));
                    } catch (Exception e) {
                    }
                // 本地副本过期前,可以使用本地副本;本地副本一旦过期,必须服务器进行有效性校验。
                } else if (token.equals("must-revalidate") || token.equals("proxy-revalidate")) {
                    mustRevalidate = true;
                }
            }
        }
        headerValue = headers.get("Expires");
        if (headerValue != null) {
            // 获取服务器设置的缓存过期时间
            serverExpires = parseDateAsEpoch(headerValue);
        }
        headerValue = headers.get("Last-Modified");
        if (headerValue != null) {
            // 记录一下服务端该资源的最后修改时间
            lastModified = parseDateAsEpoch(headerValue);
        }
    	// 获取ETag
        serverEtag = headers.get("ETag");
        // 缓存控制头优先于Expires,因为前者限制性更强
        if (hasCacheControl) {
            // 软过期,不怎么理解怎么把缓存当做当前响应返回时间了
            softExpire = now + maxAge * 1000;
            // 该响应的最终过期时间
            finalExpire = mustRevalidate ? softExpire : softExpire + staleWhileRevalidate * 1000;
        } else if (serverDate > 0 && serverExpires >= serverDate) {
            // 如果没有缓存控制头那么根据expires决定
            softExpire = now + (serverExpires - serverDate);
            finalExpire = softExpire;
        }
    
        Cache.Entry entry = new Cache.Entry();
        entry.data = response.data;
        entry.etag = serverEtag;
        entry.softTtl = softExpire;
        entry.ttl = finalExpire;
        entry.serverDate = serverDate;
        entry.lastModified = lastModified;
        entry.responseHeaders = headers;
        entry.allResponseHeaders = response.allHeaders;
        return entry;
    }
    
  • 4.1.4 mWaitingRequestManager.maybeAddToWaitingRequests mWaitingRequestManager为WaitingRequestManager的实例

    private synchronized boolean maybeAddToWaitingRequests(Request<?> request) {
    	 String cacheKey = request.getCacheKey();
        // 如果已经有相同缓存key的请求了并且该请求还没收到响应那么加入到List后面,防止相同缓存key的请求一个还没得到响应下一个就又发出请求了
        if (mWaitingRequests.containsKey(cacheKey)) {
            List<Request<?>> stagedRequests = mWaitingRequests.get(cacheKey);
            if (stagedRequests == null) {
                stagedRequests = new ArrayList<>();
            }
            request.addMarker("waiting-for-response");
            stagedRequests.add(request);
            mWaitingRequests.put(cacheKey, stagedRequests);
            if (VolleyLog.DEBUG) {
                VolleyLog.d("Request for cacheKey=%s is in flight, putting on hold.", cacheKey);
            }
            return true;
        } else {
            // 插入null,标志着当前的request正在等待响应
            mWaitingRequests.put(cacheKey, null);
            // 设置监听以便在网络请求完成后把Request从mWaitingRequest中移除
            request.setNetworkRequestCompleteListener(this);
            if (VolleyLog.DEBUG) {
                VolleyLog.d("new request, sending to network %s", cacheKey);
            }
            return false;
        }
    }
    
  • 4.1.3 mDelivery.postResponse mDelivery是一个ExecutorDelivery实例,如果是SoftTtl过期Ttl没过期则可能会分发两次响应,一次属于缓存,一个属于网络请求,但是如果网络请求是304则只会分发属于缓存的那次响应,续文会讲到

    @Override
    public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
        request.markDelivered();
        request.addMarker("post-response");
        mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
    }
    mResponsePoster = new Executor() {
    	 @Override
    	 public void execute(Runnable command) {
    	     handler.post(command);
    	 }
    };
    // 也就是在主线程调用ResponseDeliveryRunnable.run
    public void run() {
             // 取消了就不分发了直接结束
            if (mRequest.isCanceled()) {
                mRequest.finish("canceled-at-delivery");
                return;
            }
            // 成功分发成功,失败分发失败
            if (mResponse.isSuccess()) {
                mRequest.deliverResponse(mResponse.result);
            } else {
                mRequest.deliverError(mResponse.error);
            }
    	     //  softTtl过期Ttl不过期那么还需要执行mRunnable其实就是把Request加入到网络请求队列中去
            if (mResponse.intermediate) {
                mRequest.addMarker("intermediate-response");
            } else {
                mRequest.finish("done");
            }
            if (mRunnable != null) {
                mRunnable.run();
            }
        }
    
  • 4.1.3.1 mRequest.deliverResponse 该方法是一个抽象方法 用于分发响应 看看StringRequest怎么实现的,内部就是直接回调Listener.onResponse

    protected void deliverResponse(String response) {
        Response.Listener<String> listener;
        synchronized (mLock) {
            listener = mListener;
        }
        if (listener != null) {
            listener.onResponse(response);
        }
    }
    
  • 4.1.3.2 mRequest.deliverError StringRequest没有重写该方法,内部就是调用ErrorListener.onErrorResponse()

    public void deliverError(VolleyError error) {
        Response.ErrorListener listener;
        synchronized (mLock) {
            listener = mErrorListener;
        }
        if (listener != null) {
            listener.onErrorResponse(error);
        }
    }
    
至此CacheDispatcher的流程结束,下文主要讲NetworkDispatcher
第一部分总结
  • 常规用法会创建一个缓存线程四个网络请求线程,共5个线程
  • 文件缓存内容前后顺序是magic、key、eTag、serverDate、lastModified、ttl、softTtl、所有响应头、响应正文
  • 对于缓存文件是否过期,通过ttl和softTtl,并且ttl >= softTtl, 不相等的情况只有缓存控制头有stale-while-revalidate=和must-revalidate这两个字段
  • 一个请求可能会有两个响应,当Ttl没过期,SoftTtl过期了就会先把缓存的内容返回然后再去请求网络,如果不是304则再次回调Request.deliverResponse
  • 对于已经过期的缓存,就放到网络请求队列中去

没有更多推荐了,返回首页