大半年前将okhttp引入工程,当时还专门去分析过她的缓存的实现,现在印象又模糊了,所以还是拿小本本记录下。源码地址DiskLruCache。
- 内存数据是什么数据结构存储的?
- 关闭app 以后重新打开是怎么恢复缓存在本地的信息的?
第一个问题是LinkedHashMap
保存的缓存数据。第二个问题是读取journal 文件里面信息恢复的。
分析
journal 中记录的数据有如下4种“状态”,对“状态”理解以后有助于查看本地的缓存记录以及对代码的理解。
操作符 | 含义 | 备注 |
---|---|---|
DIRTY | 代表一个数据是“脏”状态。数据将会被更新或者被删除 | 简单理解是需要更新数据了。 |
CLEAN | 代表一个数据是“干净”状态,可以被读取使用了。后面会记录缓存数据的大小 | 简单理解是增加了缓存数据。 |
REMOVE | 表示删除某条记录 | |
READ | 通过LRU 获取数据 |
流程分析
DiskLruCache#create
用来创建一个DiskLruCache
的实例。okhttp 里面只会持有一个DiskLruCache
实例(使用OkhttpClient#newBuilder
来构造OkhttpClient.Builder
)。
1.DiskLruCache#create
中构造OkHttp DiskLruCache
线程池
|
|
里面会创建一个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
代表的是从缓存中获取数据。
保存在内存中的数据格式是:
|
|
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 文件的一致性。