Volley源码解析一

Volley源码解析一_第1张图片

基本使用

//创建请求队列 google推荐写一个单例类 获取唯一一个队列 
public class VolleyApplication extends Application {
    private static RequestQueue requestQueue;
    @Override
    public void onCreate() {
        super.onCreate();
         requestQueue = Volley.newRequestQueue(getApplicationContext());

    }

    public static RequestQueue getHttpQueues(){
        return requestQueue;
    }
}
//这里以StringRequest为例,Volley还提供了JsonObjectRequest、ImageRequeest
  String url = "https://www.baidu.com";
        StringRequest request = new StringRequest(Request.Method.GET, url, new Response.Listener<String>() {
            @Override
            public void onResponse(String s) {
                Toast.makeText(MainActivity.this, s, Toast.LENGTH_SHORT).show();

            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError volleyError) {
                Toast.makeText(MainActivity.this, volleyError.toString(), Toast.LENGTH_SHORT).show();
            }
        });
// 设置tag当调用queue.cancelAll(tag)会取消所有拥有这个tag的request,一般用于在onStop()中关闭网络连接

        request.setTag(this);

        VolleyApplication.getHttpQueues().add(request);

使用Volley 的主要步骤主要包括 创建RequestQueue、 创建Request、 将Request加入到RequestQueue 三步,我们来一一说明。

源码解析

一、RequestQueue的创建

public class Volley {
    private static final String DEFAULT_CACHE_DIR = "volley";

    public Volley() {
    }

    public static RequestQueue newRequestQueue(Context context, HttpStack stack) {
        File cacheDir = new File(context.getCacheDir(), "volley");
        String userAgent = "volley/0";

        try {
            String packageName = context.getPackageName();
            PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
            userAgent = packageName + "/" + info.versionCode;
        } catch (NameNotFoundException var6) {
            ;
        }

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

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

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

可以看到,Volley的newRequestQueue()方法里面,根据版本号不同创建了stack(HurStack 和 HttpClientStack)。
然后创建了网络类 BssicNetwoek,缓存类DiskBasedCache,然后使用这两个来创建RequestQueue对象。

接下来看下 queue.start()做了什么

public void start() {
    stop();
    //  PriorityBlockingQueue> mCacheQueue = new PriorityBlockingQueue<>(); //1
	//  PriorityBlockingQueue> mNetworkQueue = new PriorityBlockingQueue<>();
    //  这里的mCacheQueue、mNetWorkQueue都是优先级阻塞队列mCache是传入的DiskBaseCache
    //  mDeliver则是在构造器里面创建的ExecutorDelivery其拥有一个Looper是主线程的Handler
    mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
    
    mCacheDispatcher.start(); //2
    for (int i = 0; i < mDispatchers.length; i++) {
        NetworkDispatcher networkDispatcher =
                new NetworkDispatcher(mNetworkQueue, mNetwork, mCache, mDelivery);
        mDispatchers[i] = networkDispatcher;
        networkDispatcher.start(); //3
    }

RequestQueue对象创建的时候,会初始化一个缓存调度线程(CacheDispatcher),和4个网络调度线程(NetworkDispatcher),所以Volley默认会在后台开启5个线程。线程都初始化之后,分别调用其start方法开启线程。

1、PrioityBlockingQueue 优先级阻塞队列

可以把RequestQueue当做缓存队列和网络请求队列的生产者,NetworkDispatcher、CacheDispatcher当做消费者,当然有时候CacheDispatcher也会被当做网络请求队列的生产者(缓存过期、没命中等情况),Request内有一个枚举类定义了4种优先级,默认情况是NORMAL,我们可以重写Request.getPriority从而改变优先级,PriorityBlockingQueue会调用Request.compaeTo来判断顺序(优先级越高越先出去,优先级相等则按照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();
}

2、 mCacheDispatcher.start

因为CacheDispatcher继承自Thread所以直接看其run方法

     public void run() {
        if (DEBUG) {
            VolleyLog.v("start new dispatcher", new Object[0]);
        }

        Process.setThreadPriority(10);
        //初始化缓存
        this.mCache.initialize(); //2.1

        while(true) {
            while(true) {
                while(true) {
                    while(true) {
                        try {
                            while(true) {
                                 // Request没有添加到缓存队列前会被阻塞
                                final Request request = (Request)this.mCacheQueue.take();
                                request.addMarker("cache-queue-take");
   							 // 如果在被添加到缓存队列前就已经取消了直接结束该请求
                                if (request.isCanceled()) {
                                    request.finish("cache-discard-canceled");
                                } else {
                                //尝试从缓存中获取key
                                    Entry entry = this.mCache.get(request.getCacheKey()); //2.2
                                    if (entry == null) {
                                        request.addMarker("cache-miss");
                          
                                        this.mNetworkQueue.put(request); //添加到网络请求队列
                                        
                                    } 
                                     // 如果缓存的内容已经完全过期了那么只能再请求网络,内部判断ttl与System.currentTimeMillis
   									 // 满足ttl < System.currentTimeMillis()
                                    else if (entry.isExpired()) {  
                                        request.addMarker("cache-hit-expired");
                                        // 设置一下过期的缓存内容,内部的etag,last_modify将会在下次请求时带给服务端
                                        request.setCacheEntry(entry);
                                        this.mNetworkQueue.put(request);
                                    } else {
                                        request.addMarker("cache-hit");
                                        // 组装NetWorkResponse对象,给Request解析
                                        Response<?> response = request.parseNetworkResponse(new NetworkResponse(entry.data, entry.responseHeaders)); //2.3
                                        request.addMarker("cache-hit-parsed");
                                            // 判断softTtl < System.currentTimeMillis()
                                        if (entry.refreshNeeded()) {
      										  // softTtl已经大于当前时间,也可以直接分发这个响应,但是分发后还要再去请求一下网络
                                            request.addMarker("cache-hit-refresh-needed");
                                            request.setCacheEntry(entry);
                                             // 标记当前的响应是中间态,可能该请求会触发两次响应
                                            response.intermediate = true;
                                            // 分发响应然后再把请求放入网络请求队列中	
                                            //2.4	
                                            this.mDelivery.postResponse(request, response, new Runnable() {
                                                public void run() {
                                                    try {
                                                        CacheDispatcher.this.mNetworkQueue.put(request);
                                                    } catch (InterruptedException var2) {
                                                        ;
                                                    }

                                                }
                                            });
                                        } else {
                                                // softTtl小于当前时间,那么缓存有效,直接分发响应
                                            this.mDelivery.postResponse(request, response);
                                        }
                                    }
                                }
                            }
                        } catch (InterruptedException var4) {
                        //退出循环
                            if (this.mQuit) {
                                return;
                            }
                        }
                    }
                }
            }
        }
    }
  • 2.1、我们先来看下this.mCache.initialize(),在这里mCache对应于DiskBasedCache
public synchronized void initialize() {
		// 目录不存在就创建目录,由于目录都没有,所以缓存文件肯定也没有,直接返回
        if (!this.mRootDirectory.exists()) {
            if (!this.mRootDirectory.mkdirs()) {
                VolleyLog.e("Unable to create cache dir %s", new Object[]{this.mRootDirectory.getAbsolutePath()});
            }

        } else {
        //获取目录下所有文件
            File[] files = this.mRootDirectory.listFiles();
            if (files != null) {
                File[] var5 = files;
                int var4 = files.length;

                for(int var3 = 0; var3 < var4; ++var3) {
                    File file = var5[var3];
                    FileInputStream fis = null;

                    try {
                        fis = new FileInputStream(file);
                        DiskBasedCache.CacheHeader entry = DiskBasedCache.CacheHeader.readHeader(fis); //2.1.1
                      
                        entry.size = file.length();
                        // 保存到mEntries注意这里LinedHashMap是有序的按照LRU的顺序排序
                        this.putEntry(entry.key, entry); //2.1.2
                    } catch (IOException var16) {
                        if (file != null) {
                           // readHeader抛出的IOException在这里被处理掉了,删除了文件
                            file.delete();
                        }
                    } finally {
                        try {
                            if (fis != null) {
                                fis.close();
                            }
                        } catch (IOException var15) {
                            ;
                        }

                    }
                }

            }
        }
    }
  • 2.1.1、 DiskBasedCache.CacheHeader.readHeader
 public static DiskBasedCache.CacheHeader readHeader(InputStream is) throws IOException {
            DiskBasedCache.CacheHeader entry = new DiskBasedCache.CacheHeader();
            //读取4个字节组成一个int,如果该值不等于所谓的魔法数字就抛出异常
            int magic = DiskBasedCache.readInt(is); //2.1.1.1
            if (magic != 538051844) {
                // 这里不用去删除它,最终会被外界捕获到异常将其删除
                throw new IOException();
            } else {
   			 // 读取六个属性
   			 //缓存键
                entry.key = DiskBasedCache.readString(is); //2.1.1.3
            
                entry.etag = DiskBasedCache.readString(is);
                if (entry.etag.equals("")) {
                    entry.etag = null;
                }
                //保存时间
                entry.serverDate = DiskBasedCache.readLong(is); //2.1.1.2
                //生存时间
                entry.ttl = DiskBasedCache.readLong(is);
                
                entry.softTtl = DiskBasedCache.readLong(is);
                //响应头
                entry.responseHeaders = DiskBasedCache.readStringStringMap(is); //2.1.1.4
                return entry;
            }
        }
  • 2.1.1.1、readInt
//读取4个字节组成的一个int
 static int readInt(InputStream is) throws IOException {
        int n = 0;
        int n = n | read(is) << 0;
        n |= read(is) << 8;
        n |= read(is) << 16;
        n |= read(is) << 24;
        return n;
    }
  • 2.1.1.2 readLong
//读取8个字节组成的一个long
 static long readLong(InputStream is) throws IOException {
        long n = 0L;
        n |= ((long)read(is) & 255L) << 0;
        n |= ((long)read(is) & 255L) << 8;
        n |= ((long)read(is) & 255L) << 16;
        n |= ((long)read(is) & 255L) << 24;
        n |= ((long)read(is) & 255L) << 32;
        n |= ((long)read(is) & 255L) << 40;
        n |= ((long)read(is) & 255L) << 48;
        n |= ((long)read(is) & 255L) << 56;
        return n;
    }
  • 2.1.1.3 readString
// 首先读取8个字节以用来确定后面那个字符串的长度,然后再读取该长度将其转化为字符串
static String readString(InputStream is) throws IOException {
        int n = (int)readLong(is);
        byte[] b = streamToBytes(is, n); //2.1.1.3.1
        return new String(b, "UTF-8");
    }
  • 2.1.1.3.1 streamToBytes
//从流中读取指定长度的字节,然后返回
 private static byte[] streamToBytes(InputStream in, int length) throws IOException {
        byte[] bytes = new byte[length];

        int count;
        int pos;
        for(pos = 0; pos < length && (count = in.read(bytes, pos, length - pos)) != -1; pos += count) {
            ;
        }

        if (pos != length) {
            throw new IOException("Expected " + length + " bytes, read " + pos + " bytes");
        } else {
            return bytes;
        }
    }
  • 2.1.1.4 readStringStringMap
static Map<String, String> readStringStringMap(InputStream is) throws IOException {
       //先读取有几个响应头
        int size = readInt(is);
        Map<String, String> result = size == 0 ? Collections.emptyMap() : new HashMap(size);
		 // 然后一个个读取出来放到result中
        for(int i = 0; i < size; ++i) {
            String key = readString(is).intern();
            String value = readString(is).intern();
            ((Map)result).put(key, value);
        }

        return (Map)result;
    }
  • 2.1.2 this.putEntry(entry.key, entry) DiskBaseCache内部维护了一个HashMap并且是有序的,从里面读取元素是按照LRU的规格读取的,mTotalSize则维护着所有缓存文件的总大小,默认是5M,超出后会从mEntries取出删除
 private void putEntry(String key, DiskBasedCache.CacheHeader entry) {
        if (!this.mEntries.containsKey(key)) {
            this.mTotalSize += entry.size;
        } else {
            DiskBasedCache.CacheHeader oldEntry = (DiskBasedCache.CacheHeader)this.mEntries.get(key);
            this.mTotalSize += entry.size - oldEntry.size;
        }

        this.mEntries.put(key, entry);
    }
2.2 mCache.get 其中mCache对应DiskBaseCache
    public synchronized Entry get(String key) {
        DiskBasedCache.CacheHeader entry = (DiskBasedCache.CacheHeader)this.mEntries.get(key);
        //缓存内容不存在返回null
        if (entry == null) {
            return null;
        } else {
        //根据key获取文件名然后创建一个File对象,创建文件名的规则是把key平分成两部分,分别计算hash然后拼接在一起
            File file = this.getFileForKey(key); //2.2.1
            DiskBasedCache.CountingInputStream cis = null;

            try {
                cis = new DiskBasedCache.CountingInputStream(new FileInputStream(file), (DiskBasedCache.CountingInputStream)null);
                DiskBasedCache.CacheHeader.readHeader(cis);
                //读取响应体
                byte[] data = streamToBytes(cis, (int)(file.length() - (long)cis.bytesRead));
                //将响应头与响应体进行组合返回一个Entry
                Entry var7 = entry.toCacheEntry(data); //2.2.2
                return var7;
            } catch (IOException var15) {
                VolleyLog.d("%s: %s", new Object[]{file.getAbsolutePath(), var15.toString()});
                //读取失败也从Map中移除,并且将缓存文件删除,返回null
                this.remove(key);
            } finally {
                if (cis != null) {
                    try {
                        cis.close();
                    } catch (IOException var14) {
                        return null;
                    }
                }

            }

            return null;
        }
    }

  • 2.2.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;
}

  • 2.2.2 entry.toCacheEntry 将缓存的响应头与缓存的响应体进行组合
 public Entry toCacheEntry(byte[] data) {
            Entry e = new Entry();
            e.data = data;
            e.etag = this.etag;
            e.serverDate = this.serverDate;
            e.ttl = this.ttl;
            e.softTtl = this.softTtl;
            e.responseHeaders = this.responseHeaders;
            return e;
        }
2.3 request.parseNetworkResponse 来看看StringRequest是怎么执行的
//从请求头中读出charset然后把响应体转化为一个字符串
 protected Response<String> parseNetworkResponse(NetworkResponse response) {
        String parsed;
        try {
            parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
        } catch (UnsupportedEncodingException var4) {
            parsed = new String(response.data);
        }

        return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response)); //2.3.1
    }
  • 2.3.1 HttpHeaderParser.parseCacheHeaders 该方法会返回一个Cache.Entry,如果调用parseNetworkResponse的是NetworkDispatcher的话会把这个Cache.Entry写入缓存文件,如果是CacheDispatcher调用的话那么基本没什么用
public static Entry parseCacheHeaders(NetworkResponse response) {
        long now = System.currentTimeMillis();
        Map<String, String> headers = response.headers;
        long serverDate = 0L;
        long serverExpires = 0L;
        long softExpire = 0L;
        long maxAge = 0L;
        boolean hasCacheControl = false;
        String serverEtag = null;
        String headerValue = (String)headers.get("Date");
        if (headerValue != null) {
       	  // 将服务器返回的处理请求的时间转化为long
            serverDate = parseDateAsEpoch(headerValue);
        }

        headerValue = (String)headers.get("Cache-Control");
        if (headerValue != null) {
            hasCacheControl = true;
            String[] tokens = headerValue.split(",");

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

                if (token.startsWith("max-age=")) {
                    try {
                    // 缓存的内容将在maxAge秒后失效
                        maxAge = Long.parseLong(token.substring(8));
                    } catch (Exception var19) {
                        ;
                    }
                    // 本地副本过期前,可以使用本地副本;本地副本一旦过期,必须服务器进行有效性校验。
                } else if (token.equals("must-revalidate") || token.equals("proxy-revalidate")) {
                    maxAge = 0L;
                }
            }
        }

        headerValue = (String)headers.get("Expires");
        if (headerValue != null) {
        // 获取服务器设置的缓存过期时间
            serverExpires = parseDateAsEpoch(headerValue);
        }

        serverEtag = (String)headers.get("ETag");
        //缓存控制头优先于Expires,因为前者限制性更强
        if (hasCacheControl) {
            softExpire = now + maxAge * 1000L;
        } else if (serverDate > 0L && serverExpires >= serverDate) {
        // 如果没有缓存控制头那么根据expires决定
            softExpire = now + (serverExpires - serverDate);
        }

        Entry entry = new Entry();
        entry.data = response.data;
        entry.etag = serverEtag;
        entry.softTtl = softExpire;
        entry.ttl = entry.softTtl;
        entry.serverDate = serverDate;
        entry.responseHeaders = headers;
        return entry;
    }
2.4 mDelivery.postResponse mDelivery是一个ExecutorDelivery实例,如果是SoftTtl过期Ttl没过期则可能会分发两次响应,一次属于缓存,一个属于网络请求,但是如果网络请求是304则只会分发属于缓存的那次响应,续文会讲到
 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, Runnable runnable) {
        request.markDelivered();
        request.addMarker("post-response");
        mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
    }
    
// 也就是在主线程调用ResponseDeliveryRunnable.run
        @Override
        public void run() {
            // If this request has canceled, finish it and don't deliver.
            if (mRequest.isCanceled()) {
                mRequest.finish("canceled-at-delivery");
                return;
            }

            // 判断发布是否成功
            if (mResponse.isSuccess()) {
                mRequest.deliverResponse(mResponse.result); //2.4.1
            } else {
                mRequest.deliverError(mResponse.error); //2.4.2
            }

            //softTtl过期Ttl不过期那么还需要执行mRunnable其实就是把Request加入到网络请求队列中去
            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();
            }
       }
  • 2.4.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);
    }
}

  • 2.4.2 mRequest.deliverError StringRequest没有重写该方法,内部就是调用ErrorListener.onErrorResponse()
public void deliverError(VolleyError error) {
    Response.ErrorListener listener;
    synchronized (mLock) {
        listener = mErrorListener;
    }
    if (listener != null) {
        listener.onErrorResponse(error);
    }
}

3、networkDispatcher.start
 public void run() {
        Process.setThreadPriority(10);

        while(true) {
            Request request;
            while(true) {
                try {
                 //当request没有添加到网络请求队列中会阻塞
                    request = (Request)this.mQueue.take();
                    break;
                } catch (InterruptedException var4) {
                    if (this.mQuit) {
                        return;
                    }
                }
            }
   }         

二、Request的创建

可以继承Request 也可以直接使用StringRequest、JsonObjectRequest等,下面重点说几个方法

  1. getCacheKey 该方法会根据Url和Method 返回一个字符串,该字符串决定了缓存文件的名称
  public String getCacheKey() {
        return this.getUrl();
    }
  1. getHeaders Volley会在做网络请求前调用该方法设置请求参数,所以如果我们要自定义请求头那么可以重写该方法,默认值是一个空Map
public Map<String, String> getHeaders() throws AuthFailureError {
    return Collections.emptyMap();
}
  1. getParams 如果请求方法收Post,Volley会在网络请求调用该方法将其作为输出源写给HttpUrlConnection.getOutputStream,默认返回null
protected Map<String, String> getParams() throws AuthFailureError {
        return null;
    }
  1. parseNetworkResponse 抽象方法需要各子类实现,解析网络响应(可能来自缓存或者网络请求)包装成合适的Response对象返回,比如StringRequest就包装成Response,如果该方法返回null,那么程序会直接crash
  2. cancel 取消请求,如果是在主线程调用的该方法那么就可以保证不会回调Request.deliverResponse和Request.deliverError
  3. hasHadResponseDelivered 用于判断当前请求是否已经分发了,主要用于SoftTtl过期了,Ttl没过期,该情况会先返回缓存的响应的响应然后再去请求如果响应码是304就调用该方法判断请求是否已经分发了,如果已经分发了那么就不进行第二次分发了
  4. getBodyContentType,决定Post和Put请求body的类型
  5. shouldCache,决定该请求是否需要缓存,如果不需要加入到网络请求阻塞队列中(网络请求结束后也不会缓存),否则加入缓存阻塞队列中,我们可以调用setShouldCache来控制是否需要缓存
  6. getTimeoutMs、getRetryPolicy都跟重试策略,以后会讲到

三、将Request填加到RequestQueue中

RequestQueue.add 请求是否需要缓存决定把其放到缓存队列还是网络请求队列中

  public Request add(Request request) {
      // 标志着当前request属于哪个RequestQueue
        request.setRequestQueue(this);
        Set var2 = this.mCurrentRequests;
        synchronized(this.mCurrentRequests) {
            this.mCurrentRequests.add(request);
        }
		// 设置序列号从零开始0、1、2
        request.setSequence(this.getSequenceNumber());
        request.addMarker("add-to-queue");
        // 如果这个请求不需要缓存,那么跳过加入缓存队列,直接添加到网络请求队列
        if (!request.shouldCache()) {
            this.mNetworkQueue.add(request);
            return request;
        } else {
            Map var7 = this.mWaitingRequests;
            synchronized(this.mWaitingRequests) {
                String cacheKey = request.getCacheKey();
                //判断是否缓存过
                if (this.mWaitingRequests.containsKey(cacheKey)) {
                    Queue<Request> stagedRequests = (Queue)this.mWaitingRequests.get(cacheKey);
                    if (stagedRequests == null) {
                        stagedRequests = new LinkedList();
                    }

                    ((Queue)stagedRequests).add(request);
                    this.mWaitingRequests.put(cacheKey, stagedRequests);
                    if (VolleyLog.DEBUG) {
                        VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", new Object[]{cacheKey});
                    }
                } else {
                    this.mWaitingRequests.put(cacheKey, (Object)null);
                    this.mCacheQueue.add(request);
                }

                return request;
            }
        }
    }

RequestQueue的add方法并没有进行网络或对缓存的操作,当请求添加金网络请求队列或者缓存队列时,在后台的网络调度线程和缓存调度线程会轮询各自的请求队列,如果发现请求任务需要处理则开始执行。下面分别来分析CacheDispatcher和NetworkDispatcher的源码

到这里CacheDispatcher 的流程结束,下文主要说NetworkDispatcher

Volley源码解析二

总结
  1. 常规用法会创建一个缓存线程和四个网络请求线程,共五个线程
  2. 文件缓存内容前后顺序是magic、key、eTag、serverDate、lastModified、ttl、softTtl、所有响应头、响应正文
  3. 对于缓存文件是否过期,通过ttl 和 softTtl ,并且 ttl >= softTtl ,不等于的情况只有缓存控制头有stale-while-revalidate和must-revalidate这两个字段
  4. 一个请求可能会有两个响应,当Ttl没过期,SoftTtl过期了就会先把缓存的内容返回然后再去请求网络,如果不是304则再次回调Request.deliverResponse
  5. 对于已经过期的缓存,就放到网络请求队列中去

你可能感兴趣的:(Android,Volley)