okhttp缓存探究

大半年前将okhttp引入工程,当时还专门去分析过她的缓存的实现,现在印象又模糊了,所以还是拿小本本记录下。源码地址DiskLruCache

  1. 内存数据是什么数据结构存储的?
  2. 关闭app 以后重新打开是怎么恢复缓存在本地的信息的?

第一个问题是LinkedHashMap保存的缓存数据。第二个问题是读取journal 文件里面信息恢复的。

分析

journal 中记录的数据有如下4种“状态”,对“状态”理解以后有助于查看本地的缓存记录以及对代码的理解。

操作符 含义 备注
DIRTY 代表一个数据是“脏”状态。数据将会被更新或者被删除 简单理解是需要更新数据了。
CLEAN 代表一个数据是“干净”状态,可以被读取使用了。后面会记录缓存数据的大小 简单理解是增加了缓存数据。
REMOVE 表示删除某条记录
READ 通过LRU 获取数据

流程分析

DiskLruCache#create

用来创建一个DiskLruCache的实例。okhttp 里面只会持有一个DiskLruCache实例(使用OkhttpClient#newBuilder来构造OkhttpClient.Builder)。

1.DiskLruCache#create中构造OkHttp DiskLruCache线程池

1
2
Executor executor = new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(), Util.threadFactory("OkHttp DiskLruCache", true));

里面会创建一个OkHttp DiskLruCache线程池,这个线程池常驻线程数量为0,并且最多只能有一个线程(相当于是一个单线程的实例)。OkHttp DiskLruCache用来清理缓存数据信息,比如缓存信息超过允许的最大值的时候。该线程池也会处理journal 文件的冗余信息,防止文件太大。具体的逻辑是在DiskLruCache#rebuildJournal处理,会使用当前内存中的缓存数据lruEntries来生成一个新的journal 文件。里面使用了3个文件,journalFile(最终生成的文件)、journalFileBackup(备份文件,防止失败以后重新恢复)、journalFileTmp(临时文件,数据先更新在临时文件再做最后处理)。

2.创建DiskLruCache实例

构造函数里面会创建journalFile、journalFileBackup 和journalFileTmp 文件。valueCount 代表的是每一个缓存对应的文件数,比如okhttp 需要缓存的头信息以及实体信息,所以valueCount 为2。

DiskLruCache#get

代表的是从缓存中获取数据。

保存在内存中的数据格式是:

1
final LinkedHashMap<String, Entry> lruEntries = new LinkedHashMap<>(0, 0.75f, true);

Key(String)代表的是一个缓存的唯一标识(okhttp 中代表的是url 的MD5 值),Value(Entry)则代表的是缓存的数据。

app 重新启动,会通过DiskLruCache#readJournal一行行的读取journal 文件中的信息,并且恢复到DiskLruCache.lruEntries中。主要的代码逻辑在DiskLruCache#readJournalLine中,会根据REMOVE、CLEAN、DIRTY、READ 做不同逻辑处理。

缓存同步问题

DiskLruCache#get(String key)DiskLruCache#edit(String key, long expectedSequenceNumber)都是synchronized修饰的,所以每次只有一个线程在缓存中读或者取(上面有分析只有一个DiskLruCache实例)。这样是否会影响到get/edit的性能?或许会,但是最终只有一个线程处理才能保证journal 文件的一致性。