一,概述
glide作为android流行的图片加载框架,笔者认为有必要对此完全解读。glide提供了三级缓存、生命周期Destroy后自动移除缓存、自动适配ImageView,以及提供了各种对图片修饰的操作,如剪裁等。本文通过最简单的使用,挖掘出with、load、into三大核心函数的源码逻辑,进一步理解Glide设计思路。读者可顺着笔者粗糙的源码流程更深入地解读。
二,简单使用

如上,笔者不赘述。
三,核心流程
1,with
众所周知,Glide支持监听生命周期,那么它是怎么做的呢?我们看下with函数的重载,返回值是RequestManager。

笔者查阅官方注释,推测RequestManager负责调度一次请求,如停止、启动或重启,这需要与生命周期者配合。因此,笔者进一步推测RequestManager与生命周期者,即android中的Activity、Fragment、Application是1:1的关系。


从以上RequestManager提供的public方法来看,提供了诸如加载load,pause,resume,clear等调度函数。onStart和onStop是其回调函数,如下

在onStart时resumeRequest,onStop时清除或pauseRequest,取决于clearOnStop字段的设置。
笔者继续更加with函数,看下RequestManager如何创建。
笔者以with重载函数参数为context为例,

首先调用getRetriever获得RequestManagerRetriever,这是负责创建和复用RequestManager的工厂类,全局单例。


RequestManagerRetirever在Glide的构造函数中被初始化,因Glide是单例模式,因此RequestManagerRetirever也是单例。我们跟进到RequestManagerRetirever#get方法,也有很多重载,保持glide#with重载一样。

笔者仍以context为例,

(1)如果context是FragmentActivity,且,当前调用with函数来自主线程,我们跟进

来到重载get(FragmentActivity),如果当前是子线程则进入重载get(Application)。同上述流程,我们跟进getOrCreate,注意传入了FragmentActivity的FragmentManager。在此之前,我们看下LifecycleRequestManagerRetriever的成员。

根据lifecycleToRequestManager这个Map,笔者验证了RequestManager与生命周期者是1:1关系。
接着看getOrCreate

如果不存在RequestManager,则new一个LifeCycleLifeCycle,lifecycle传入,
LifeCycleLifeCycle实现了LifecycleObserver接口,这样就能监听到lifecycle的生命周期了。通过工厂方法build创建,其实现在RequestManagerRetriever,简单new了一个RequestManager,

我们紧接后续逻辑,通过isParentVisible判断是否调用result.onStart回调,result就是RequestManager了,onStart即恢复请求,笔者这是第一次从with进入,因此没有请求。
唉,看到这,笔者发现glide优化了,毕竟在旧版本,glide是通过在FragmentActivity中创建一个隐藏的空Fragment去监听Activity的生命周期,当时笔者就有疑问,为什么不直接监听呢?

笔者注意到,lifeCycle走到onDestroy时自动移除对应的RequestManager。
笔者也注意到,SupportRequestManagerTreeNode这个类,看下实现,

通过getDescendants可知,获得当前FragmentManager的所有RequestManager,包括子片段。主要在调度Request Resume或Pause使用,其意义是递归式地暂停或恢复当前LifeCycle及其附着的子LifeCycle所对应的RequestManager。

笔者继续看其他get方法的重载实现,


再看下Application的get重载,

可知applicationManager全局单例,并且由于传入了ApplicationLifecycle,自然调用不到RequestManager的onStart和onStop方法,没有自动暂停恢复功能。
综上,笔者从多个get方法得出如下结论:
(1),如果传入Applicaton,全局单例,无生命周期监听功能。
(2),如果传入Activity,则直接调用到(1)。
(3),如果传入FragmentActivity,如果在子线程调用到(1),否则创建或复用该FragmentActivity对应的RequestManager,能监听生命周期。
(4),如果传入Fragment,将当前Fragment作为生命周期对象监听,并且创建或复用一个RequestManager。
(5),如果传入View,通过其getContext决定调用到(1)或(3)或(4)。
即with方法决定了在当前上下文中获取的复用RequestManager。
2,load
在load之前,笔者首先看下其as方法,这是RequestManager创建一次RequestBuilder的起点,需读者注意,RequestBuilder继承了BaseRequestOptions类,


可以接收的参数有
(1)BItmap.class,
(2)Drawable.class,
(3)File.class,
(4)GitDrawable.class,

保存至transcodeClass中,并且在构造中根据其类型,获得一些默认的options,这个读者可自行了解。
笔者看下load方法的多个重载,假设以Drawable重载为例跟进



最终都会调用到loadGeneric,并且将load传入的参数保存到model中,以构造者设计方式设计此RequestBuilder,
RequestBuilder其重要是apply函数,

负责保存对图片的操作,每个操作对应RequestOptions类,方法如下

看到这,load方法就结束了。做了四件事情,
(1)创建一个RequestBuilder对象,
(2)保存图片目标类,记录在RequestBuilder#transcodeClass。
(3)保存图片来源,记录在RequestBuilder#mode。
(4)保存对图片的各种RequestOptions操作,记录在RequestBuilder的Set集合中。
对于缓存的实现,主要在RequestOptionsr和RequestBuilder的父类BaseRequestOptions中,通过一系列成员保存option操作,读者可自行研究。对于缓存策略,笔者将在into中讲述。
3,into

target必须是实现了Target接口,看下Target定义如下,

笔者在这里理解为资源相关的回调,如资源加载开始、失败、完成、清除等,其接收一个泛型字段,TranscodeType,这个和RequestBuilder中的transcodeClass相同。
再看下Target的实现类,

笔者在此发现了ImageViewTarget这个类,因此推测into(ImageView)是将ImageView封装进ImageVIewTarget对象,其根据transcodeClass决定使用BitmapImageVIewTarget还是DrawableImageVIewTarget。另外,Target接口继承了LifecycleListener接口,笔者推测此处与RequestManager联合,做一些事情,如动画相关等等等。
笔者跟进Into,最终全部调用到private的into重载,

笔者注意到传入callbackExecutor是Executor.mainThreadExecutor。
通过buildRequest方法创建一个Request,Request是一个接口,看下定义,

很简单,负责一次请求的开始、清除、暂停,以及状态获取。只有如下三个实现,

那么,具体返回哪个Request呢,笔者继续跟进buildRequestRecursive。

现创建mainRequest,其是正常请求,如果我们设置了加载失败的相关选项,则创建一个ErrorRequestCoordinator,将正常请求保存到primary成员中。当加载失败时,调用error的begin,如下,

笔者对失败Request不展开说,跟进buildThumbnailRequestRecursive,创建了一个ThumbnailRequestCoordinator。看类名,这是和所谓图相关的Request,跟进,

发现方法内通过obtainRequest创建了一个fullRequest,跟进,

又创建了一个SingleRequest,并且设置到ThumbnailRequestCoordinator的full成员。以及下面逻辑设置SingleRequest到thumb成员。
笔者在此总结下,通过RequestBuilder#buildRequestRecursive方法,最先创建一个ErrorRequestCoordinator,其primary成员保存ThumbnailRequestCoordinator**(如果设置了加载失败的选项图),**error保存错误选项ThumbnailRequestCoordinator(通过errorBuilder创建)。然后通过buildThumbnailRequestRecursive创建ThumbnailRequestCoordinator,fill成员保存通过obtainRequest创建的SingleRequest。thumb成员也是通过不同的RequestBuilder#obtainRequest创建的SingleRequest。
层层包括Request,这就用到了装饰设计模式。笔者画了以下类图供读者理解。

笔者回到主线,从RequestManager#track出发,



笔者假设没有paused,且知道这一个request是ErrorRequestCoordinator,跟进,

当primary没有运行,调用到ThumbnailRequestCoordinator#begin,跟进,

第一次调用thumb#begin,先请求缩略图,随后调用full#begin,请求完整请求。thumb和full均是SingleRequest,跟进。

model为null,非合理源,回调onLoadFailed,

如果正在running,抛出异常;如果已经完成,直接回调onResourcReady,数据源传入MEMORY_CACHE,毕竟确实在内存中(只有开启内存才会有此调用)。

笔者继续跟进,

onSizeReady,传入剪裁大小,最后调用onLoadStart,获取到预览图,通知Target。
笔者从注释知onSizeReady开始进入异步,因此进入核心方法onSizeReady,

将状态标记为RUNNING,调用engine.load方法,这是什么呢?通过该类成员猜测,这是干具体事的类。

Engine存在如cache(LRU),activeResources等缓存成员,解码decodeJobFactory等。关于该类具体工作,笔者借下文讲解。
4,Engine#load

通过load方法,传入资源model,宽高,返回类型transcodeClass,一些选项等,如是否开启缓存,以及callbackExecutor。随后通过KeyFactory生成key值,这个笔者推测与缓存相关。

跟进loadFromMemory,

如果开启跳过缓存,直接返回null,
先从ActiveResources中获取,如果没有再从Cache中获取,如果没有返回null,
我们看下loadFromActiveResourcesResources,

如果获取到不为null的值,调用acquire引用计算+1。再看下loadFromCahce方法,

从cache中获取(注意,直接从cache中remove),如果获取到引用计数+1,并且放入ActiveResources(这里面的资源计数一定大于0,表示正在被使用)中。
这里的cache是什么呢?答案是默认LruResourceCache,可以通过Glide.Builder#setMemoryCache定义用户自己的缓存,在此不赘述。
笔者注意到Glide对资源管理采用引用计数的方式,因此看下计数到0时发生什么?

调用listener#onResourceRelease,跟进看下实现,

从ActiveResources中移除,并且如果资源可缓存,存放入cache中,否则recycle掉。
笔者回到主线,如果没有缓存,调用到waitForExistingOrStartNewJob方法,

跟进,

创建一个引擎Job,创建一个解码Job

加入到jobs中国。调用start方法,注意到addCallback传入了回调和回调线程。
跟进start,

获得一个GlideExecutor,根据策略决定用哪个,笔者直接跟进DecodeJob的run方法,


根据runReason有三种状态执行,如初始化,切换资源服务,解码。第一次进入现初始化,调用runGenerators,

做了些初始化工作,调用startNext方法,generator有三个实现,如下,

至于获得哪一个,得从策略决定,


更具体的笔者暂不深跟,只要知道这里面就会请求到网络或磁盘即可。当资源准备然后切换到SWITHC_TO_SOURCE_SERVICE来解析资源,

解完成后,



切换回调线程(一般是主线程),通知资源调用完毕。
层层回调到SingleRequset#onResourceReady方法,进一步调用到Target的onResourceReady方法,

即完成了一次请求。如果是ImageVIewTarget,则进一步调用ImageView#setDrawalbe或setBitmap方法。
笔者跟到这,再回溯一下内存缓存。
如果再次进行请求,并且开启了内存缓存,怎么走呢?

由于target已存在内存,同一个request调用时,直接通过previous.begin调用,由于上次request加载完毕,因此,我们就通过内存缓存的形式返回了。

四,裁剪相关
为了防止加载大图片导致内存溢出,glide提供了裁剪功能,即传入指定宽高或sizeMultiplier
,可避免加载原图大小而崩溃,



在解码时,会根据width、height去解码,因此可以避免大图片问题崩溃。
五,恢复/暂停加载
在RequestManager中,由于监听了lifeCycler,

1,pause
笔者假设用户未设置clearOnStop,进入pauseRequests,笔者以SingleQuest为例,



简单cancel,如果有release,则释放。
clearOnStop如果为true,就清空所有的Target,间接调用到Request#clear方法,

2,resume
如下图,仍调用到Request#begin,不赘述。

六,低内存释放缓存
在Application的onLowMemory回调中,写入此方法,

或读者可自定义一个MemoryCache,内部采用SoftRefenrce方式设置到glide中。
我们看下clearMemory实现,

这三个缓存池全局单例,通过引用方式传入到Engine(Engine也是全局单例,Request所使用到的Engine来自GlideContext)。
另外,笔者提醒读者注意,只清除memroyCache、bitmapPool、arrayPool的缓存,ActiveResources不会清除。
笔者在此提问,Glide默认的缓存大小是多少呢?有兴趣的读者可以阅读MemorySizeCalculator,glide会根据当前设备ram是否是低内存ram,以及屏幕宽高动态计算出一个合适的大小。
七,三级缓存
所谓glide提供了哪三级缓存?根据核心流程的源码走读,我们看到了内存缓存。
glide提供内存缓存,磁盘缓存(持久化到文件系统中),网络缓存这三种。
内存缓存上文已经介绍过,我们接下来介绍下磁盘缓存入口,
在DecodeJob#notifyEncodeAndRelease处,


glide使用了DiskLruCache来存储,我们跟进,

如果current不为null,表示缓存存在,直接返回,不然通过editor写入到磁盘。
笔者下面介绍网络获取相关,
当需要从网络中获取资源时,glide会调用HttpUrlFetcher类,

连接后,如果状态ok,拿到InputStream,通过getStreamForSuccessfulRequest发送处url Connect,并且获取inputStream,

最后解析stream,存储LRUResoureCache中。
笔者粗陋地理解所谓的网络缓存要么是缓存在网络层,要么是从网络中解析的数据到了内存缓存中。