精华内容
下载资源
问答
  • Okhttp缓存拦截

    2019-04-02 00:04:59
    //以下为拦截器 public static OkHttpClient getOkhttpClient(Context context) { //创建缓存路径,定义缓存文件的大小 Cache cache = new Cache(new File(context.getCacheDir(), “Cache”), 1024 * 1024 * 10)...

    //以下为拦截器
    public static OkHttpClient getOkhttpClient(Context context) {
    //创建缓存路径,定义缓存文件的大小
    Cache cache = new Cache(new File(context.getCacheDir(), “Cache”), 1024 * 1024 * 10);
    MyCacheinterceptor myCacheinterceptor = new MyCacheinterceptor(context);
    OkHttpClient okHttpClient = new OkHttpClient.Builder()
    .addInterceptor(myCacheinterceptor)
    .addNetworkInterceptor(myCacheinterceptor)
    .cache(cache)
    .build();
    return okHttpClient;
    }

    private static class MyCacheinterceptor implements Interceptor {
        private Context context;
    
        public MyCacheinterceptor(Context context) {
            this.context = context;
        }
    
        @Override
        public Response intercept(Chain chain) throws IOException {
            Request request = chain.request();
            //这里就是说判读我们的网络条件,要是有网络的话我么就直接获取网络上面的数据,要是没有网络的话我么就去缓存里面取数据
            if (!SystemFacade.isNetworkConnected(context)) {
                request = request.newBuilder().cacheControl(CacheControl.FORCE_CACHE).build();
    
            }
            Response originalResponse = chain.proceed(request);
            if (SystemFacade.isNetworkConnected(context)) {
                int maxAge = 0;
                return originalResponse.newBuilder()
                        // 清除头信息,因为服务器如果不支持,会返回一些干扰信息,不清除下面无法生效
                        .removeHeader("Pragma")
                        .header("Cache-Control", "public ,max-age=" + maxAge)
                        .build();
            } else {
                int maxStale = 15;
                return originalResponse.newBuilder()
                        // 清除头信息,因为服务器如果不支持,会返回一些干扰信息,不清除下面无法生效
                        .removeHeader("Pragma")
                        //这里的设置的是我们的没有网络的缓存时间,想设置多少就是多少。
                        .header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
                        .build();
            }
    
        }
    
    
    }
    

    //获取网络
    public static boolean isNetworkConnected(Context context) {
    if (context != null) {
    ConnectivityManager mConnectivityManager = (ConnectivityManager) context
    .getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkInfo mNetworkInfo = mConnectivityManager.getActiveNetworkInfo();
    if (mNetworkInfo != null) {
    return mNetworkInfo.isAvailable();
    }
    }
    return false;
    }

    展开全文
  • OKHTTP缓存拦截

    2019-04-02 00:02:34
    //以下为拦截器 public static OkHttpClient getOkhttpClient(Context context) { //创建缓存路径,定义缓存文件的大小 Cache cache = new Cache(new File(context.getCacheDir(), "Cache"), 1024 * 1024 * 10); ...

    package com.c03.jy.wanandroid.playandroid.data.source.remote.retrofit;

    import android.content.Context;
    import android.net.ConnectivityManager;
    import android.net.NetworkInfo;

    import com.c03.jy.wanandroid.playandroid.MyApplication;
    import com.c03.jy.wanandroid.playandroid.data.AppConstact;
    import com.c03.jy.wanandroid.playandroid.utils.SystemFacade;

    import java.io.File;
    import java.io.IOException;
    import java.util.concurrent.TimeUnit;

    import okhttp3.Cache;
    import okhttp3.CacheControl;
    import okhttp3.Interceptor;
    import okhttp3.OkHttpClient;
    import okhttp3.Request;
    import okhttp3.Response;
    import okhttp3.logging.HttpLoggingInterceptor;
    import retrofit2.Retrofit;
    import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
    import retrofit2.converter.gson.GsonConverterFactory;

    public class DataService {

    private  static final long DEFAULT_TIMEOUT=20000;
    private static volatile WARetrofitService mRetrofitService;
    
    public static WARetrofitService getService(){
        if (mRetrofitService==null){
            synchronized (DataService.class){
                    if(mRetrofitService==null){
                        HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
                        loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS);
    
                        OkHttpClient okHttpClient = new OkHttpClient.Builder()
                                .addInterceptor(loggingInterceptor)
    
                                .connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
                                .writeTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
                                .readTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
                                .build();
                        OkHttpClient okhttpClient = getOkhttpClient(MyApplication.context);
                        Retrofit build = new Retrofit.Builder()
                                .client(okHttpClient)
                                .client(okhttpClient)
                                .addConverterFactory(GsonConverterFactory.create())
                                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                                .baseUrl(AppConstact.BASE_URL)
                                .build();
                        mRetrofitService = build.create(WARetrofitService.class);
    
    
                    }
            }
        }
        return mRetrofitService;
    }
    
    
    //    ==================================================================================================
    //以下为拦截器
    public static OkHttpClient getOkhttpClient(Context context) {
        //创建缓存路径,定义缓存文件的大小
        Cache cache = new Cache(new File(context.getCacheDir(), "Cache"), 1024 * 1024 * 10);
        MyCacheinterceptor myCacheinterceptor = new MyCacheinterceptor(context);
        OkHttpClient okHttpClient = new OkHttpClient.Builder()
                .addInterceptor(myCacheinterceptor)
                .addNetworkInterceptor(myCacheinterceptor)
                .cache(cache)
                .build();
        return okHttpClient;
    }
    
    private static class MyCacheinterceptor implements Interceptor {
        private Context context;
    
        public MyCacheinterceptor(Context context) {
            this.context = context;
        }
    
        @Override
        public Response intercept(Chain chain) throws IOException {
            Request request = chain.request();
            //这里就是说判读我们的网络条件,要是有网络的话我么就直接获取网络上面的数据,要是没有网络的话我么就去缓存里面取数据
            if (!isNetworkAvailable(context)) {
                request = request.newBuilder().cacheControl(CacheControl.FORCE_CACHE).build();
    
            }
            Response originalResponse = chain.proceed(request);
            if (isNetworkAvailable(context)) {
                int maxAge = 0;
                return originalResponse.newBuilder()
                        // 清除头信息,因为服务器如果不支持,会返回一些干扰信息,不清除下面无法生效
                        .removeHeader("Pragma")
                        .header("Cache-Control", "public ,max-age=" + maxAge)
                        .build();
            } else {
                int maxStale = 15;
                return originalResponse.newBuilder()
                        // 清除头信息,因为服务器如果不支持,会返回一些干扰信息,不清除下面无法生效
                        .removeHeader("Pragma")
                        //这里的设置的是我们的没有网络的缓存时间,想设置多少就是多少。
                        .header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
                        .build();
            }
    
        }
    
    
    }
    
    
    //网络判断请求
    public static boolean isNetworkAvailable(Context context) {
        if (context != null) {
            ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
            NetworkInfo info = cm.getActiveNetworkInfo();
            if (info != null) {
                return info.isAvailable();
            }
        }
        return false;
    }
    

    }

    展开全文
  • okhttp缓存拦截器应用

    2019-10-12 14:23:24
    之前只是听说缓存,今天我自己也尝试了一下 ,运用了单例模式,我其实对这些都不是很熟悉,我就是学到哪记录到哪,如果有不对的地方,希望各位指教。 package com.silent.fuxiokhttpdemo; import android.content....

    之前只是听说缓存,今天我自己也尝试了一下 ,运用了单例模式,我其实对这些都不是很熟悉,我就是学到哪记录到哪,如果有不对的地方,希望各位指教。

    package com.silent.fuxiokhttpdemo;
    
    import android.content.Context;
    
    import java.io.File;
    import java.io.IOException;
    
    import okhttp3.Cache;
    import okhttp3.Call;
    import okhttp3.Callback;
    import okhttp3.Interceptor;
    import okhttp3.OkHttpClient;
    import okhttp3.Request;
    import okhttp3.RequestBody;
    import okhttp3.Response;
    
    /**
     * 单例类-懒汉式
     * 1、私有化构造方法
     * 2、提供私有的静态的本类变量
     * 3、提供公有的静态的同步方法,并且返回值是本类对象
     *      判断本类变量指向的对象是否为空,如果为空则创建对象
     *
     *
     * 现有线程A,B
     * A.start():进入就绪状态:相当于跑步时在起跑线上的准备
     * B.start():进入就绪状态:相当于跑步时在起跑线上的准备
     * A,B争夺CPU的执行权
     * A先争夺上CPU执行权-》进入运行状态-》OkHttpUtils2.getInstance->if (utils2==null)
     * B争夺上CPU执行权->进入运行状态-》OkHttpUtils2.getInstance-》if (utils2==null) utils2 = new OkHttpUtils2();
     * A又争夺上CPU的执行权->进入运行状态-》utils2 = new OkHttpUtils2();  return utils2;-》run方法执行完成-》线程进入死亡状态
     * B争夺上CPU的执行权-》进入运行状态-》return utils2;-》run方法执行完成-》线程进入死亡状态
     *
     *
     * 同步锁:synchronized
     * 使用有3种:
     *      1、同步方法
     *      2、对象同步代码块
     *      3、class同步代码块
     *
     * =============================分隔线===================================
     *
     *1、导包,添加网络权限
     *2、创建单例类
     *3、创建OkHttpClient对象
     *4、提供GET\POST方法
     *5、GET:
     *      1、创建Request对象
     *      2、通过OkHttpClient对象调用newCall方法,得到Call对象
     *      3、通过Call对象发起同步或异步请求:execute 同步  enqueue异步
     *
     *6、POST:
     *
     *      1、创建Request对象,添加url,添加RequestBody对象
     *      2、通过OkHttpClient对象调用newCall方法,得到Call对象
     *      3、通过Call对象发起同步或异步请求:execute 同步  enqueue异步
     *
     */
    public class OkHttpUtils2 {
        public static final long MAX_SIZE = 8*1024*1024;
        private static OkHttpUtils2 utils2;
        private OkHttpClient client;
        private OkHttpUtils2(Context context){
            //创建Cache对象,并设置缓存路径以及缓存区域大小
            File cacheDir = context.getCacheDir();//获取应用的缓存路径
            Cache cache = new Cache(cacheDir,MAX_SIZE);
            client = new OkHttpClient.Builder().cache(cache).addNetworkInterceptor(new Interceptor() {
                @Override
                public Response intercept(Chain chain) throws IOException {
                    // 1、通过Chain对象获取Request对象
                    Request request = chain.request();
                    //2、通过Chain对象重新发起请求,并获取Response对象
                    Response proceed = chain.proceed(request);
                    //移除头部信息:Pragma、Cache-Control
                   Response response =  proceed.newBuilder().removeHeader("Pragma")
                            .removeHeader("Cache-Control").addHeader("Cache-Control","max-age="+(60)).build();
                    return response;
                }
            }).build();
        }
        public static synchronized OkHttpUtils2 getInstance(Context context){
            //加锁
            if (utils2==null)
                utils2 = new OkHttpUtils2(context);
            return utils2;
            //解锁
        }
        public void sendGet(String url, Callback callback){
            Request request = new Request.Builder().url(url).build();
            Call call = client.newCall(request);
            call.enqueue(callback);
        }
        public void sendPost(String url, RequestBody body, Callback callback){
            Request request = new Request.Builder().url(url)
    //                .post(body)
                    .method("POST",body)//设置POST请求的两种方式
    
                    .build();
            Call call = client.newCall(request);
            call.enqueue(callback);
        }
    }
    
    展开全文
  • 缓存有关的Header Expires Expires: Thu, 12 Jan 2017 11:01:33 GMT 表示到期时间,一般用在response报文中,当超过此时间响应将被认为是无效的而需要网络连接,反之直接使用缓存 条件GET 客户端发送条件...

    与缓存有关的Header

    • Expires
      Expires: Thu, 12 Jan 2017 11:01:33 GMT
      表示到期时间,一般用在response报文中,当超过此时间响应将被认为是无效的而需要网络连接,反之直接使用缓存

    • 条件GET
      客户端发送条件get请求,如果缓存是有效的,则返回304 Not Modifiled,否则才返回body。

    • ETag
      ETag是对资源文件的一种摘要,当客户端第一次请求某个对象,服务器在响应头返回
      ETag: “5694c7ef-24dc”
      客户端再次请求时,通过发送
      If-None-Match:”5694c7ef-24dc”
      交给服务器进行判断,如果仍然可以缓存使用,服务器就直接返回304 Not Modifiled

    • Vary
      Vary: *
      告诉客户端和缓存服务器不要缓存任何信息
      Vary: header-name, header-name, …
      逗号分隔的一系列http头部名称,用于确定缓存是否可用
      作用:动态服务,防止客户端误使用了用于pc端的缓存。即使请求的是相同资源,因Vary指定的首部字段不同,也必须从源服务器请求

    • Cache Control
      客户端可以在HTTP**请求**中使用的标准 Cache-Control 指令
      Cache-Control: max-age=
      Cache-Control: max-stale[=]
      Cache-Control: min-fresh=
      Cache-control: no-cache
      Cache-control: no-store
      Cache-control: no-transform
      Cache-control: only-if-cached
      服务器可以在响应中使用的标准 Cache-Control 指令。
      Cache-control: must-revalidate
      Cache-control: no-cache
      Cache-control: no-store
      Cache-control: no-transform
      Cache-control: public
      Cache-control: private
      Cache-control: proxy-revalidate
      Cache-Control: max-age=
      Cache-control: s-maxage=

    1) 可缓存性
    public
    表明其他用户也可以利用缓存。
    private
    表明缓存只对单个用户有效,不能作为共享缓存。
    no-cache
    强制所有缓存了该响应的缓存用户,在使用已存储的缓存数据前,发送带验证的请求到原始服务器
    no-store
    缓存不应存储有关客户端请求或服务器响应的任何内容。
    only-if-cached
    表明客户端只接受已缓存的响应,并且不要向原始服务器检查是否有更新的拷贝(相当于禁止使用网络连接)
    2) 到期
    max-age=<seconds>
    设置缓存存储的最大周期,超过这个时间缓存被认为过期(单位秒)。与Expires相反,时间是相对于请求的时间。
    s-maxage=<seconds>
    覆盖max-age 或者 Expires 头,但是仅适用于共享缓存(比如各个代理),并且私有缓存中它被忽略。
    max-stale[=<seconds>]
    表明客户端愿意接收一个已经过期的资源,且可选地指定响应不能超过的过时时间。
    min-fresh=<seconds>
    表示客户端希望在指定的时间内获取最新的响应。
    3) 有效性
    must-revalidate
    缓存必须在使用之前验证旧资源的状态,并且不可使用过期资源。
    proxy-revalidate
    与must-revalidate作用相同,但它仅适用于共享缓存(例如代理),并被私有缓存忽略。
    immutable
    表示响应正文不会随时间而改变。资源(如果未过期)在服务器上不发生改变,因此客户端不应发送重新验证请求头(例如If-None-Match或If-Modified-Since)来检查更新,即使用户显式地刷新页面。

    缓存策略

    CacheInterceptor

    @Override public Response intercept(Chain chain) throws IOException {
        Response cacheCandidate = cache != null
            ? cache.get(chain.request())
            : null;
    
        long now = System.currentTimeMillis();
    
        CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
        Request networkRequest = strategy.networkRequest;
        Response cacheResponse = strategy.cacheResponse;
        ...
        if (cacheCandidate != null && cacheResponse == null) {
          closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
        }
    
        // If we're forbidden from using the network and the cache is insufficient, fail.
        if (networkRequest == null && cacheResponse == null) {
          return new Response.Builder()
              .request(chain.request())
              .protocol(Protocol.HTTP_1_1)
              .code(504)
              .message("Unsatisfiable Request (only-if-cached)")
              .body(Util.EMPTY_RESPONSE)
              .sentRequestAtMillis(-1L)
              .receivedResponseAtMillis(System.currentTimeMillis())
              .build();
        }
    
        // If we don't need the network, we're done.
        if (networkRequest == null) {
          return cacheResponse.newBuilder()
              .cacheResponse(stripBody(cacheResponse))
              .build();
        }
    
        Response networkResponse = null;
        try {
          networkResponse = chain.proceed(networkRequest);
        } finally {
          // If we're crashing on I/O or otherwise, don't leak the cache body.
          if (networkResponse == null && cacheCandidate != null) {
            closeQuietly(cacheCandidate.body());
          }
        }
    
        // If we have a cache response too, then we're doing a conditional get.
        if (cacheResponse != null) {
          if (networkResponse.code() == HTTP_NOT_MODIFIED) {
            Response response = cacheResponse.newBuilder()
                .headers(combine(cacheResponse.headers(), networkResponse.headers()))
                .sentRequestAtMillis(networkResponse.sentRequestAtMillis())
                .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
                .cacheResponse(stripBody(cacheResponse))
                .networkResponse(stripBody(networkResponse))
                .build();
            networkResponse.body().close();
    
            // Update the cache after combining headers but before stripping the
            // Content-Encoding header (as performed by initContentStream()).
            cache.trackConditionalCacheHit();
            cache.update(cacheResponse, response);
            return response;
          } else {
            closeQuietly(cacheResponse.body());
          }
        }
    
        Response response = networkResponse.newBuilder()
            .cacheResponse(stripBody(cacheResponse))
            .networkResponse(stripBody(networkResponse))
            .build();
    
        if (cache != null) {
          if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
            // Offer this request to the cache.
            CacheRequest cacheRequest = cache.put(response);
            return cacheWritingResponse(cacheRequest, response);
          }
    
          if (HttpMethod.invalidatesCache(networkRequest.method())) {
            try {
              cache.remove(networkRequest);
            } catch (IOException ignored) {
              // The cache cannot be written.
            }
          }
        }
    
        return response;
      }
       
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90

    主要做三件事:

    • 根据Request和之前缓存的Response得到CacheStrategy
    • 根据CacheStrategy决定是请求网络还是直接返回缓存
    • 如果2中决定请求网络,则在这一步将返回的网络响应和本地缓存对比,对本地缓存进行增删改操作

    CacheStrategy

    public final class CacheStrategy {
      /** The request to send on the network, or null if this call doesn't use the network. */
      public final @Nullable Request networkRequest;
    
      /** The cached response to return or validate; or null if this call doesn't use a cache. */
      public final @Nullable Response cacheResponse;
    
      CacheStrategy(Request networkRequest, Response cacheResponse) {
        this.networkRequest = networkRequest;
        this.cacheResponse = cacheResponse;
      }
      ...
    }
       
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 作用:将“请求”和旧的缓存进行分析比较,决定是发起网络请求还是直接使用缓存。具体来说,根据networkRequest和cacheResponse是否为空执行不同的动作
    networkRequestcacheResponse结果
    nullnull禁止进行网络请求,但缓存不存在或者过期,只能返回503错误
    nullnon-null缓存可以使用,直接返回缓存,不用请求网络
    non-nullnull缓存不存在或者过期,直接访问网络
    non-nullnon-null条件get,请求网络

    CacheStrategy的加工过程

    主要是读取请求头和响应头中有关缓存的HTTP 字段生成CacheStrategy对象,可结合开头与缓存有关的Header查看源码,这里不再赘述

    缓存UML类图

    这里写图片描述

    Cache

    • OkHttp的缓存“门面”,对外提供增删改查方法
    • 通过内部的DiskLruCache来管理缓存对象
    • 每一条缓存记录的key是url的md5值

    OkHttp的缓存文件,如下图
    这里写图片描述

      public Cache(File directory, long maxSize) {
        this(directory, maxSize, FileSystem.SYSTEM);
      }
       
    • 1
    • 2
    • 3
        //OkHttpClient指定Cache
        public Builder cache(@Nullable Cache cache) {
          this.cache = cache;
          this.internalCache = null;
          return this;
        }
       
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    使用时需指定缓存目录和缓存大小上限

    DiskLruCache

    Cache内部通过DiskLruCache管理cache在文件系统层面的创建,读取,自动清理等工作

    public final class DiskLruCache implements Closeable, Flushable {
      static final String JOURNAL_FILE = "journal";
      static final String JOURNAL_FILE_TEMP = "journal.tmp";
      static final String JOURNAL_FILE_BACKUP = "journal.bkp";
      static final String MAGIC = "libcore.io.DiskLruCache";
      static final String VERSION_1 = "1";
      static final long ANY_SEQUENCE_NUMBER = -1;
      static final Pattern LEGAL_KEY_PATTERN = Pattern.compile("[a-z0-9_-]{1,120}");
      private static final String CLEAN = "CLEAN";//缓存记录的4种状态
      private static final String DIRTY = "DIRTY";
      private static final String REMOVE = "REMOVE";
      private static final String READ = "READ";
        final FileSystem fileSystem;
      final File directory;
      private final File journalFile;//缓存日志
      private final File journalFileTmp;
      private final File journalFileBackup;
      private final int appVersion;
      private long maxSize;
      final int valueCount;
      private long size = 0;
      BufferedSink journalWriter;
      final LinkedHashMap<String, Entry> lruEntries = new LinkedHashMap<>(0, 0.75f, true);//Entry是缓存文件的描述
      ...
      }
       
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    DiskLruCache通过journal文件和lruEntries(LinkedHashMap)共同管理缓存文件

    journal

    作用:
    - 在进程启动时重建DiskLruCache(lruEntries),将磁盘中的缓存文件和url对应关系加载到内存中
    - 记录和跟踪缓存文件的状态
    - 保证对缓存文件读写操作的原子性

    这里写图片描述

    • 前5行固定不变,分别为:常量:libcore.io.DiskLruCache;diskLruCache版本;应用程序版本;valueCount(表示一个Entry对应的文件数量,在Cache中为2),空行
    • 接下来每一行对应一个cache entry的一次状态记录,其格式为:[状态(DIRTY,CLEAN,READ,REMOVE),key(url的md5值),文件大小(两个文件:响应头和响应体)]。中间以空格隔开。
    • DIRTY 表示缓存正在被插入、更新或删除,在磁盘中操作成功后会有一条对应的CLEAN或REMOVE记录。否则该DIRTY记录无效(操作未成功,被异常中断过)。相当于DIRTY对应的只是临时文件。
    • CLEAN 表示该条缓存是一个有效的记录,可以正常读取(get)。
    • READ 表示该条缓存最近被读取过
    • REMOVE 表示该条记录对应的缓存文件已经被删除了

    DiskLruCache.Entry

    private final class Entry {
        final String key;
    
        /** Lengths of this entry's files. */
        final long[] lengths;
        final File[] cleanFiles;
        final File[] dirtyFiles;
    
        /** True if this entry has ever been published. */
        boolean readable;
    
        /** The ongoing edit or null if this entry is not being edited. */
        Editor currentEditor;
       
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • Entry是缓存文件在文件系统层面的引用
    • key是url的md5值
    • 每个Entry可以对应多个文件,具体由DiskLruCache的valueCount决定,默认是2(响应头和响应体)
    • cleanFiles代表正常有效的可读缓存,dirtyFiles表示缓存文件正在被创建或更新(但还没完成,只是临时文件),操作完成后会将dirtyFiles重命名为cleanFiles,并将旧的cleanFiles删除
        Entry(String key) {
          this.key = key;
    
          lengths = new long[valueCount];
          cleanFiles = new File[valueCount];
          dirtyFiles = new File[valueCount];
    
          // The names are repetitive so re-use the same builder to avoid allocations.
          StringBuilder fileBuilder = new StringBuilder(key).append('.');
          int truncateTo = fileBuilder.length();
          for (int i = 0; i < valueCount; i++) {
            fileBuilder.append(i);
            cleanFiles[i] = new File(directory, fileBuilder.toString());
            fileBuilder.append(".tmp");
            dirtyFiles[i] = new File(directory, fileBuilder.toString());
            fileBuilder.setLength(truncateTo);
          }
        }
       
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 生成Entry对象的同时,生成以url的md5命名的File对象(但缓存文件还未写入磁盘)。文件名如下图:
      这里写图片描述 这里写图片描述

      DiskLruCache的初始化

    public synchronized void initialize() throws IOException {
        assert Thread.holdsLock(this);
    
        if (initialized) {
          return; // Already initialized.
        }
    
        // If a bkp file exists, use it instead.
        if (fileSystem.exists(journalFileBackup)) {
          // If journal file also exists just delete backup file.
          if (fileSystem.exists(journalFile)) {
            fileSystem.delete(journalFileBackup);
          } else {
            fileSystem.rename(journalFileBackup, journalFile);
          }
        }
    
        // Prefer to pick up where we left off.
        if (fileSystem.exists(journalFile)) {
          try {
            readJournal();
            processJournal();
            initialized = true;
            return;
          } catch (IOException journalIsCorrupt) {
            Platform.get().log(WARN, "DiskLruCache " + directory + " is corrupt: "
                + journalIsCorrupt.getMessage() + ", removing", journalIsCorrupt);
          }
    
          // The cache is corrupted, attempt to delete the contents of the directory. This can throw and
          // we'll let that propagate out as it likely means there is a severe filesystem problem.
          try {
            delete();
          } finally {
            closed = false;
          }
        }
    
        rebuildJournal();
    
        initialized = true;
      }
       
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 根据journal文件重建lruEntries,并删除dirty缓存
    • 如果journal文件初始化失败会重建journal文件
    private void readJournal() throws IOException {
        BufferedSource source = Okio.buffer(fileSystem.source(journalFile));
        try {
          String magic = source.readUtf8LineStrict();
          String version = source.readUtf8LineStrict();
          String appVersionString = source.readUtf8LineStrict();
          String valueCountString = source.readUtf8LineStrict();
          String blank = source.readUtf8LineStrict();
          if (!MAGIC.equals(magic)
              || !VERSION_1.equals(version)
              || !Integer.toString(appVersion).equals(appVersionString)
              || !Integer.toString(valueCount).equals(valueCountString)
              || !"".equals(blank)) {
            throw new IOException("unexpected journal header: [" + magic + ", " + version + ", "
                + valueCountString + ", " + blank + "]");
          }
    
          int lineCount = 0;
          while (true) {
            try {
              readJournalLine(source.readUtf8LineStrict());
              lineCount++;
            } catch (EOFException endOfJournal) {
              break;
            }
          }
          redundantOpCount = lineCount - lruEntries.size();
    
          // If we ended on a truncated line, rebuild the journal before appending to it.
          if (!source.exhausted()) {
            rebuildJournal();
          } else {
            journalWriter = newJournalWriter();
          }
        } finally {
          Util.closeQuietly(source);
        }
      }
       
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    readJournal方法开头校验Journal文件的前面5行是否正确,如果不正确抛出异常,并在之后重建Journal文件。然后通过readJournalLine方法,逐行读取Journal文件的每条缓存记录,并更新对应Entry

      private void processJournal() throws IOException {
        fileSystem.delete(journalFileTmp);
        for (Iterator<Entry> i = lruEntries.values().iterator(); i.hasNext(); ) {
          Entry entry = i.next();
          if (entry.currentEditor == null) {
            for (int t = 0; t < valueCount; t++) {
              size += entry.lengths[t];
            }
          } else {
            entry.currentEditor = null;
            for (int t = 0; t < valueCount; t++) {
              fileSystem.delete(entry.cleanFiles[t]);
              fileSystem.delete(entry.dirtyFiles[t]);
            }
            i.remove();
          }
        }
      }
       
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    processJournal方法将DIRTY(且无对应CLEAN)的entry从内存和磁盘中一并删除

    Response缓存的添加

    //CacheInterceptor中intercept方法
    if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
            // Offer this request to the cache.
            CacheRequest cacheRequest = cache.put(response);//缓存响应头
            return cacheWritingResponse(cacheRequest, response);//缓存响应体
    }
       
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    响应头的缓存

    @Nullable CacheRequest put(Response response) {
        String requestMethod = response.request().method();
    
        if (HttpMethod.invalidatesCache(response.request().method())) {
          try {
            remove(response.request());
          } catch (IOException ignored) {
            // The cache cannot be written.
          }
          return null;
        }
        if (!requestMethod.equals("GET")) {
          // Don't cache non-GET responses. We're technically allowed to cache
          // HEAD requests and some POST requests, but the complexity of doing
          // so is high and the benefit is low.
          return null;
        }
    
        if (HttpHeaders.hasVaryAll(response)) {
          return null;
        }
    
        Entry entry = new Entry(response);
        DiskLruCache.Editor editor = null;
        try {
          editor = cache.edit(key(response.request().url()));
          if (editor == null) {
            return null;
          }
          entry.writeTo(editor);
          return new CacheRequestImpl(editor);
        } catch (IOException e) {
          abortQuietly(editor);
          return null;
        }
      }
       
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 检查请求头的方法,非”get”的响应不缓存
    • 响应头中包含“Vary:*”的不缓存
    • 通过DiskLruCache.Editor将响应头信息缓存到磁盘中
    • 生成CacheRequest对象为下一步将响应体信息缓存到磁盘中做准备
      public @Nullable Editor edit(String key) throws IOException {
        return edit(key, ANY_SEQUENCE_NUMBER);
      }
       
    • 1
    • 2
    • 3
    synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException {
        initialize();
    
        checkNotClosed();
        validateKey(key);
        Entry entry = lruEntries.get(key);
        if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null
            || entry.sequenceNumber != expectedSequenceNumber)) {
          return null; // Snapshot is stale.
        }
        if (entry != null && entry.currentEditor != null) {
          return null; // Another edit is in progress.
        }
        if (mostRecentTrimFailed || mostRecentRebuildFailed) {
          // The OS has become our enemy! If the trim job failed, it means we are storing more data than
          // requested by the user. Do not allow edits so we do not go over that limit any further. If
          // the journal rebuild failed, the journal writer will not be active, meaning we will not be
          // able to record the edit, causing file leaks. In both cases, we want to retry the clean up
          // so we can get out of this state!
          executor.execute(cleanupRunnable);
          return null;
        }
    
        // Flush the journal before creating files to prevent file leaks.
        journalWriter.writeUtf8(DIRTY).writeByte(' ').writeUtf8(key).writeByte('\n');
        journalWriter.flush();//在jounar文件上添加DIRTY记录,表示该文件正在被加入缓存
    
        if (hasJournalErrors) {
          return null; // Don't edit; the journal can't be written.
        }
    
        if (entry == null) {
          entry = new Entry(key);//建立和url对应的Entry对象
          lruEntries.put(key, entry);//将Entry保存到LinkedHashMap中
        }
        Editor editor = new Editor(entry);
        entry.currentEditor = editor;
        return editor;
      }
       
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 在journal文件上写入DIRTY记录,表示该条缓存正在被写入
    • 新建和url对应的Entry对象,将Entry保存到LinkedHashMap中
    • 返回Editor对象,方便下一步真正写入文件流

    DiskLruCache.Editor

      public final class Editor {
        final Entry entry;
        final boolean[] written;
        private boolean done;
    
        Editor(Entry entry) {
          this.entry = entry;
          this.written = (entry.readable) ? null : new boolean[valueCount];
        }
       
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 每一个DiskLruCache.Editor对象对应一个DiskLruCache.Entry对象
    • 负责返回和DIRTY FILE对应的output stream(new sink(int index))及commit修改

    Cache.Entry

        Entry(Response response) {
          this.url = response.request().url().toString();
          this.varyHeaders = HttpHeaders.varyHeaders(response);
          this.requestMethod = response.request().method();
          this.protocol = response.protocol();
          this.code = response.code();
          this.message = response.message();
          this.responseHeaders = response.headers();
          this.handshake = response.handshake();
          this.sentRequestMillis = response.sentRequestAtMillis();
          this.receivedResponseMillis = response.receivedResponseAtMillis();
        }
       
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 将Response中除了Responsebody外的信息提取出来
    public void writeTo(DiskLruCache.Editor editor) throws IOException {
          BufferedSink sink = Okio.buffer(editor.newSink(ENTRY_METADATA));//获取输出流
    
          sink.writeUtf8(url)
              .writeByte('\n');
          sink.writeUtf8(requestMethod)
              .writeByte('\n');
          sink.writeDecimalLong(varyHeaders.size())
              .writeByte('\n');
          for (int i = 0, size = varyHeaders.size(); i < size; i++) {
            sink.writeUtf8(varyHeaders.name(i))
                .writeUtf8(": ")
                .writeUtf8(varyHeaders.value(i))
                .writeByte('\n');
          }
    
          sink.writeUtf8(new StatusLine(protocol, code, message).toString())
              .writeByte('\n');
          sink.writeDecimalLong(responseHeaders.size() + 2)
              .writeByte('\n');
          for (int i = 0, size = responseHeaders.size(); i < size; i++) {
            sink.writeUtf8(responseHeaders.name(i))
                .writeUtf8(": ")
                .writeUtf8(responseHeaders.value(i))
                .writeByte('\n');
          }
          sink.writeUtf8(SENT_MILLIS)
              .writeUtf8(": ")
              .writeDecimalLong(sentRequestMillis)
              .writeByte('\n');
          sink.writeUtf8(RECEIVED_MILLIS)
              .writeUtf8(": ")
              .writeDecimalLong(receivedResponseMillis)
              .writeByte('\n');
    
          if (isHttps()) {
            sink.writeByte('\n');
            sink.writeUtf8(handshake.cipherSuite().javaName())
                .writeByte('\n');
            writeCertList(sink, handshake.peerCertificates());
            writeCertList(sink, handshake.localCertificates());
            sink.writeUtf8(handshake.tlsVersion().javaName()).writeByte('\n');
          }
          sink.close();
        }
       
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 将除了响应体外的信息通过输出流写入到磁盘中
     private static final int ENTRY_METADATA = 0;//对应响应头
     private static final int ENTRY_BODY = 1;//对应响应体
       
    • 1
    • 2
    public Sink newSink(int index) {//在valuecount为2时,index为0或1
          synchronized (DiskLruCache.this) {
            if (done) {
              throw new IllegalStateException();
            }
            if (entry.currentEditor != this) {
              return Okio.blackhole();
            }
            if (!entry.readable) {
              written[index] = true;
            }
            File dirtyFile = entry.dirtyFiles[index];
            Sink sink;
            try {
              sink = fileSystem.sink(dirtyFile);//获取临时文件xxx.0.tmp对应的输出流
            } catch (FileNotFoundException e) {
              return Okio.blackhole();
            }
            return new FaultHidingSink(sink) {
              @Override protected void onException(IOException e) {
                synchronized (DiskLruCache.this) {
                  detach();
                }
              }
            };
          }
        }
       
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    获取和指定File对应的输出流

    Blockquote
    Sink是okio对OutputStream的封装,可简单理解为OutputStream,与之对应的还有source,是okio对InputStream的封装

    CacheRequestImpl(final DiskLruCache.Editor editor) {
          this.editor = editor;
          this.cacheOut = editor.newSink(ENTRY_BODY);//拿到xxx.1.tmp
          this.body = new ForwardingSink(cacheOut) {
            @Override public void close() throws IOException {
              synchronized (Cache.this) {
                if (done) {
                  return;
                }
                done = true;
                writeSuccessCount++;
              }
              super.close();
              editor.commit();
            }
          };
        }
       
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    private Response cacheWritingResponse(final CacheRequest cacheRequest, Response response)
          throws IOException {
        // Some apps return a null body; for compatibility we treat that like a null cache request.
        if (cacheRequest == null) return response;
        Sink cacheBodyUnbuffered = cacheRequest.body();
        if (cacheBodyUnbuffered == null) return response;
    
        final BufferedSource source = response.body().source();//从Response中取出body
        final BufferedSink cacheBody = Okio.buffer(cacheBodyUnbuffered);//通过cacheBody将文件写入存储设备中
    
        Source cacheWritingSource = new Source() {
          boolean cacheRequestClosed;
    
          @Override public long read(Buffer sink, long byteCount) throws IOException {
            long bytesRead;
            try {
              bytesRead = source.read(sink, byteCount);
            } catch (IOException e) {
              if (!cacheRequestClosed) {
                cacheRequestClosed = true;
                cacheRequest.abort(); // Failed to write a complete cache response.
              }
              throw e;
            }
    
            if (bytesRead == -1) {
              if (!cacheRequestClosed) {
                cacheRequestClosed = true;
                cacheBody.close(); // The cache response is complete!
              }
              return -1;
            }
    
            sink.copyTo(cacheBody.buffer(), sink.size() - bytesRead, bytesRead);
            cacheBody.emitCompleteSegments();
            return bytesRead;
          }
    
          @Override public Timeout timeout() {
            return source.timeout();
          }
    
          @Override public void close() throws IOException {
            if (!cacheRequestClosed
                && !discard(this, HttpCodec.DISCARD_STREAM_TIMEOUT_MILLIS, MILLISECONDS)) {
              cacheRequestClosed = true;
              cacheRequest.abort();
            }
            source.close();
          }
        };
    
        String contentType = response.header("Content-Type");
        long contentLength = response.body().contentLength();
        return response.newBuilder()
            .body(new RealResponseBody(contentType, contentLength, Okio.buffer(cacheWritingSource)))
            .build();
      }
       
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    这一步涉及较多okio的知识,但主要意思是将ResponseBody通过写io缓存到磁盘中

    提交更改

        public void commit() throws IOException {
          synchronized (DiskLruCache.this) {
            if (done) {
              throw new IllegalStateException();
            }
            if (entry.currentEditor == this) {
              completeEdit(this, true);
            }
            done = true;
          }
        }
       
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    synchronized void completeEdit(Editor editor, boolean success) throws IOException {
        Entry entry = editor.entry;
        if (entry.currentEditor != editor) {
          throw new IllegalStateException();
        }
    
        // If this edit is creating the entry for the first time, every index must have a value.
        if (success && !entry.readable) {
          for (int i = 0; i < valueCount; i++) {
            if (!editor.written[i]) {
              editor.abort();
              throw new IllegalStateException("Newly created entry didn't create value for index " + i);
            }
            if (!fileSystem.exists(entry.dirtyFiles[i])) {
              editor.abort();
              return;
            }
          }
        }//保证两个Dirty File都“写完”才提交
    
        for (int i = 0; i < valueCount; i++) {
          File dirty = entry.dirtyFiles[i];
          if (success) {
            if (fileSystem.exists(dirty)) {
              File clean = entry.cleanFiles[i];
              fileSystem.rename(dirty, clean);
              long oldLength = entry.lengths[i];
              long newLength = fileSystem.size(clean);
              entry.lengths[i] = newLength;
              size = size - oldLength + newLength;
            }//将写完的Dirty File(xxx.0.tmp)重命名为Clean File的名字(xxx.0),并删除Dirty File
          } else {
            fileSystem.delete(dirty);
          }
        }
    
        redundantOpCount++;
        entry.currentEditor = null;
        if (entry.readable | success) {//更新journar文件
          entry.readable = true;
          journalWriter.writeUtf8(CLEAN).writeByte(' ');
          journalWriter.writeUtf8(entry.key);
          entry.writeLengths(journalWriter);
          journalWriter.writeByte('\n');
          if (success) {
            entry.sequenceNumber = nextSequenceNumber++;
          }
        } else {
          lruEntries.remove(entry.key);
          journalWriter.writeUtf8(REMOVE).writeByte(' ');
          journalWriter.writeUtf8(entry.key);
          journalWriter.writeByte('\n');
        }
        journalWriter.flush();
    
        if (size > maxSize || journalRebuildRequired()) {
          executor.execute(cleanupRunnable);
        }
      }
       
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    commit成功代表写入缓存完成

    缓存的清理

    boolean journalRebuildRequired() {
        final int redundantOpCompactThreshold = 2000;
        return redundantOpCount >= redundantOpCompactThreshold
            && redundantOpCount >= lruEntries.size();
      }
       
    • 1
    • 2
    • 3
    • 4
    • 5

    journal文件的缓存条目数量同时超出阈值(2000)和Entry的数量,说明journal需要重建

    private final Runnable cleanupRunnable = new Runnable() {
        public void run() {
          synchronized (DiskLruCache.this) {
            if (!initialized | closed) {
              return; // Nothing to do
            }
    
            try {
              trimToSize();
            } catch (IOException ignored) {
              mostRecentTrimFailed = true;
            }
    
            try {
              if (journalRebuildRequired()) {
                rebuildJournal();
                redundantOpCount = 0;
              }
            } catch (IOException e) {
              mostRecentRebuildFailed = true;
              journalWriter = Okio.buffer(Okio.blackhole());
            }
          }
        }
      };
       
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    void trimToSize() throws IOException {
        while (size > maxSize) {
          Entry toEvict = lruEntries.values().iterator().next();
          removeEntry(toEvict);
        }
        mostRecentTrimFailed = false;
      }
       
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在循环中不断删除“旧”文件,直到剩余缓存文件的大小总和小于DiskLruCache初始化时传入的maxSize

    boolean removeEntry(Entry entry) throws IOException {
        if (entry.currentEditor != null) {
          entry.currentEditor.detach(); // Prevent the edit from completing normally.
        }
    
        for (int i = 0; i < valueCount; i++) {
          fileSystem.delete(entry.cleanFiles[i]);//删除缓存文件
          size -= entry.lengths[i];
          entry.lengths[i] = 0;
        }
    
        redundantOpCount++;
        journalWriter.writeUtf8(REMOVE).writeByte(' ').writeUtf8(entry.key).writeByte('\n');//更新journal
        lruEntries.remove(entry.key);//移除entry
    
        if (journalRebuildRequired()) {
          executor.execute(cleanupRunnable);
        }
    
        return true;
      }
       
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    重建journal

    synchronized void rebuildJournal() throws IOException {
        if (journalWriter != null) {
          journalWriter.close();
        }
    
        BufferedSink writer = Okio.buffer(fileSystem.sink(journalFileTmp));
        try {
          writer.writeUtf8(MAGIC).writeByte('\n');
          writer.writeUtf8(VERSION_1).writeByte('\n');
          writer.writeDecimalLong(appVersion).writeByte('\n');
          writer.writeDecimalLong(valueCount).writeByte('\n');
          writer.writeByte('\n');
    
          for (Entry entry : lruEntries.values()) {
            if (entry.currentEditor != null) {
              writer.writeUtf8(DIRTY).writeByte(' ');
              writer.writeUtf8(entry.key);
              writer.writeByte('\n');
            } else {
              writer.writeUtf8(CLEAN).writeByte(' ');
              writer.writeUtf8(entry.key);
              entry.writeLengths(writer);
              writer.writeByte('\n');
            }
          }
        } finally {
          writer.close();
        }
    
        if (fileSystem.exists(journalFile)) {
          fileSystem.rename(journalFile, journalFileBackup);
        }
        fileSystem.rename(journalFileTmp, journalFile);
        fileSystem.delete(journalFileBackup);
    
        journalWriter = newJournalWriter();
        hasJournalErrors = false;
        mostRecentRebuildFailed = false;
      }
       
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 在journalFileTmp中新建journal,且只保留 DIRTY和CLEAN的记录
    • journalFileTmp新建完成后重命名为journalFile,并将旧的journalFile重命名为journalFileBackup

      DiskLruCache总结

    • 通过LinkedHashMap实现LRU替换
    • 通过journal保证Cache操作的原子性及可用性
    • 每一条缓存对应两个状态副本:DIRTY,CLEAN。CLEAN表示当前可用的Cache。DIRTY为编辑状态的cache。由于更新和创建都只操作DIRTY状态的副本,实现了读和写的分离。
    • 每一个url对应四个文件,两个状态(DIRY,CLEAN),每个状态对应两个文件:0文件对应存储meta数据,1文件存储body数据。
            <link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/markdown_views-ea0013b516.css">
                </div>
    
    展开全文

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 6,349
精华内容 2,539
关键字:

okhttp缓存拦截