android glide框架缓存,Glide 缓存流程

本文首发于 vivo互联网技术 微信公众号

链接:作者:连凌能

Android上图片加载的解决方案有多种,但是官方认可的是Glide。Glide提供简洁易用的api,整个框架也方便扩展,比如可以替换网络请求库,同时也提供了完备的缓存机制,应用层不需要自己去管理图片的缓存与获取,框架会分成内存缓存,文件缓存和远程缓存。本文不会从简单的使用着手,会把重点放在缓存机制的分析上。

一、综述

开始之前,关于Glide缓存请先思考几个问题:

Glide有几级缓存?

Glide内存缓存之间是什么关系?

Glide本地文件IO和网络请求是一个线程吗?如果不是,怎么实现线程切换?

Glide网络请求回来后数据直接返回给用户还是先存再返回?

加载开始入口从Engine.load()开始,先看下对这个方法的注释,

会先检查(Active Resources),如果有就直接返回,Active Resources没有被引用的资源会放入Memory Cache,如果Active Resources没有,会往下走。

检查Memory Cache中是否有需要的资源,如果有就返回,Memory Cache中没有就继续往下走。

检查当前在运行中的job中是否有改资源的下载,有就在现有的job中直接添加callback返回,不重复下载,当然前提是计算得到的key是一致的,如果还是没有,就会构造一个新的job开始新的工作。

63e4bee45370d800797279f574008a50.png

ok, find the source code.

二、内存缓存

4dbea07733fcb6db1080d9fca78e243e.png

8ac12eb189a9ecbe5233dbfb2ce4dda8.png

2b51c81f7cdef871590feeb1929beaeb.png

先看到 focus 1,这一步会从 ActiveResources 中加载资源,首先判断是否使用内存缓存,否的话返回null;否则到 ActiveResources 中取数据:

2d87f70c6edecbd64f923e3a6987f173.png

接下来看下ActiveResources, 其实是用过弱引用保存使用过的资源。

ce06500a462e1020fdb8f7bf8ffbce43.png

成功取到数据后回调类型也是内存缓存:

e6bdfe6b219e26c988fff3ca56946b80.png

接着回到Engine.load()中继续看到focus 2,如果在cache中找到就是remove掉,然后返回EngineResource,其中需要EngineResource进行acquire一下,这个后面再看,然后会把资源移到ActiveResources中,也就是上面提到的缓存:

1b92b37fe3cfc0bf63bdefef98e89098.png

其中cache是MemoryCache接口的实现,如果没设置,默认在build的时候是LruResourceCache, 也就是熟悉的LRU Cache:

ac21769c9d577ee63abae104eefe4d48.png

再看下EngineResource,主要是对资源增加了引用计数的功能:

65a77202bb8415a48008af0ff1706651.png

fad3ad8c468e775ccd9aaa1cccc81e97.png

68ebd12a969486e5dd4fc9b1825b5ae0.png

在release后会判断引用计数是否为0,如果是0就会回调onResourceReleased,在这里就是Engine,然后会把资源从ActiveResources中移除,资源默认是可缓存的,因此会把资源放到LruCache中。

aebbcd00e646eba5199dd7f1210c55d9.png

如果是回收呢,看看上面的EngineResource,如果引用计数为0并且还没与回收,就会调用真正的Resource.recycle(),看其中的一个BitmapResource是怎么回收的,就是放到Bitmap池中,也是用的LRU Cache,这个和今天的主题不相关,就不继续往下拓展。

3778ebd989a7bca0306bf3c290c7b205.png

思路再拉到Engine.load()的流程中,接下来该看focus 3,这里再贴一下代码,如果job已经在运行了,那么直接添加一个回调后返回LoadStatus,这个可以允许用户取消任务:

af7334c6af8d69372d4f14d0918d825d.png

接着往下看到focus 4, 到这里就需要创建后台任务去拉取磁盘文件或者发起网络请求。

三、磁盘缓存

48efefbaa3b364f6b1a644e286ba3082.png

先构造两个job,一个是EngineJob,另外一个DecodeJob,其中DecodeJob会根据需要解码的资源来源分成下面几个阶段:

a06129ec85051dde698300d19124374c.png

在构造DecodeJob时会把状态置为INITIALIZE。

构造完两个 Job 后会调用 EngineJob.start(DecodeJob),首先会调用getNextStage来确定下一个阶段,这里面跟DiskCacheStrategy这个传入的磁盘缓存策略有关。

磁盘策略有下面几种:

ALL:缓存原始数据和转换后的数据

NONE:不缓存

DATA:原始数据,未经过解码或者转换

RESOURCE:缓存经过解码的数据

AUTOMATIC(默认):根据`EncodeStrategy`和`DataSource`等条件自动选择合适的缓存方

默认的AUTOMATIC方式是允许解码缓存的RESOURCE:

9ba5e6ad63e92cfb1121f88089ee456d.png

所以在 getNextStage 会先返回Stage.RESOURCE_CACHE,然后在start中会返回diskCacheExecutor,然后开始执行DecodeJob:

bb6c94422ecdc57d19c3c8236fc4bc4f.png

DecodeJob会回调run()开始执行, run()中调用runWrapped执行工作,这里runReason还是RunReason.INITIALIZE ,根据前面的分析指导这里会获得一个ResourceCacheGenerator,然后调用runGenerators:

66c8b5f3ba2a4062a19f30b5798c9640.png

在 runGenerators 中,会调用 startNext,目前currentGenerator是ResourceCacheGenerator, 那么就是调用它的startNext方法:

27788f4e713cbc36f7bbd014665330c5.png

看下ResourceCacheGenerator.startNext(), 这里面就是重点逻辑了,首先从Registry中获取支持资源类型的ModelLoader(其中ModelLoader是在构造Glide的时候传进去), 然后从ModelLoader中构造LoadData,接着就能拿到DataFetcher,(关于ModelLoader/LoadData/DataFetcher之间的关系不在本次范围内,后面有机会再另写)通过它的loadData方法加载数据:

bc4c1caa78f2db389f09a42401f6daa0.png

9036d0d927059cc5c1004447deef8556.png

如果在Resource中找不到需要的资源,那么startNext就会返回false,在runGenerators中就会进入循环体内:

接着会重复上面执行getNextStage,由于现在Stage已经是RESOURCE_CACHE,所以接下来会返回DataCacheGenerator,执行逻辑和上面的ResourceCacheGenerator是一样的,如果还是没有找到需要的,进入循环体内。

此时getNextStage会根据用于是否设置只从磁盘中获取资源,如果是就会通知失败,回调onLoadFailed;如果不是就设置当前Stage为Stage.SOURCE,接着往下走。

状态就会进入循环内部的if条件逻辑里面,调用reschedule。

在reschedule把runReason设置成SWITCH_TO_SOURCE_SERVICE,然后通过callback回调。

DecodeJob中的callback是EngineJob传递过来的,所以现在返回到EngineJob。

在EngineJob中通过getActiveSourceExecutor切换到网络线程池中,执行DecodeJob,下面就准备开始发起网络请求。

四、网络缓存

在Stage.SOURCE阶段,通过getNextGenerator返回的是SourceGenerator,所以目前的currentGenerator就是它。

流程还是一样的,SourceGenerator还是调用startNext方法,获取到对应的DataFetcher,这里其实是HttpUrlFetcher,发起网络请求。

f462ca15d5db26bbc1b3a8a5ffa59f93.png

先缓一缓,本文其实到了上面已经可以结束了,Glide涉及到的五级缓存都已经涉及到了,是真的就可以结束了吗?不是的,网络请求回来和缓存还有关系吗?接着看到HttpUrlFetcher,下载成功后回调onDataReady,其中callback是SourceGenerator:

575b2854ce07db184e77b440061ac6b2.png

正常情况会进入if判断逻辑里面,赋值dataToCache,然后回调cb.reschedule,而cb就是DecodeJob构造SourceGenerator的时候传入,cb是DecodeJob。

6426ec68cc9b36e2bf94d377b2babba8.png

DecodeJob在reschedule回调EngineJob,最后还是回到SourceGenerator中的startNext()逻辑。

f86b60467186ddd81e5c93cddf56f016.png

和第一次进来的逻辑不一样,现在dataToCache != null,进入第一个if逻辑。

在逻辑里面调用cacheData,逻辑很明显,保持数据到本地,然后会构造一个DataCacheGenerator。

而DataCacheGenerator前面已经分析过了,就是用来加载本地原始数据的,这回会加载成功,返回true。

cf3fa1f8ad7c8b426f606414d127d499.png

接下来就是一系列的回调了:

DataCacheGenerator的startNext逻辑里面会给DataFetcher传递自身作为callback,在加载本地数据成功后回调onDataReady。

f2dcbde8e3549dcbda7675cbb36c1740.png

而cb现在是SourceGenerator传递过来,SourceGenerator再回调它自己的cb,是DecodeJob在构造它的时候传过来。

7389ab542f0b1c92364fe6fbe6fa2a2e.png

在上面SourceGenerator把DecodeJob切换到ActiveSourceExecutor线程中执行,还记得一开始DecodeJob是在哪启动的吗?在EngineJob中启动,然后是把DecodeJob放到diskCacheExecutor中执行。

07154860cd86759a2888916bd49b992d.png所以上面在DecodeJob的onDataFetcherReady会走到第一个if逻辑里面,然后赋值runReason = RunReason.DECODE_DATA,再一次回调Engine.reschedule,将工作线程切换到ActiveSourceExecutor。

986625860466ccf845d5b00d20f90cd8.png然后还是走到DecodeJob, 现在会进入DECODE_DATA分支,在这里面会调用ResourceDecoder把数据解码:

c3e21e73837ba7f7ba66421f867da4a0.png

解码成功后调用notifyComplete(result, dataSource);

5eb461839ed1ce3845f61b3fdae3170f.png

五、总结

现在回答一下开头的几个问题。

1、有几级缓存?五级,分别是什么?

活动资源 (Active Resources)

内存缓存 (Memory Cache)

资源类型(Resource Disk Cache)

原始数据 (Data Disk Cache)

网络缓存

2、Glide内存缓存之间是什么关系?

专门画了一幅图表明这个关系,言简意赅。

d693aafa8b1dc90ee3a0224e6a596409.png

3、Glide本地文件IO和网络请求是一个线程吗?

明显不是,本地IO通过diskCacheExecutor,而网络IO通过ActiveSourceExecutor

4、Glide网络请求回来后数据直接返回给用户还是先存再返回?

不是直接返回给用户,会在SourceGenerator中构造一个DataCacheGenerator来取数据。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值