Glide是一个快速高效的Android图片加载库,注重于平滑的滚动。Glide提供了易用的API,高性能、可扩展的图片解码管道(decode pipeline),以及自动的资源池技术。
目前,Glide的最新版本为4.2.0,本文是基于4.1.1这个版本来分析的,同属4.x,变化不大。
基本用法
多数情况下,使用Glide加载图片非常简单,一行代码足矣:
|
|
取消加载同样很简单:
|
|
- asBitmap() //指定加载静态图片,如果是gif则加载第一帧。
- asGif() //如果是非gif,则加载失败。
- asXxx() // 较3.x新增了几个as方法。
注解生成流式API(与3.x版本最大区别)
Glide v4 使用 注解处理器 (Annotation Processor) 来生成出一个 API,在 Application 模块中可使用该流式 API 一次性调用到 RequestBuilder, RequestOptions 和集成库中所有的选项。
Generated API 模式的设计出于以下两个目的:
- 集成库可以为 Generated API 扩展自定义选项。
- 在 Application 模块中可将常用的选项组打包成一个选项在 Generated API 中使用
虽然以上所说的工作均可以通过手动创建 RequestOptions 子类的方式来完成,但想将它用好更具有挑战,并且降低了 API 使用的流畅性。
使用 Generated API
Generated API 默认名为 GlideApp ,与 Application 模块中 AppGlideModule的子类包名相同。在 Application 模块中将 Glide.with() 替换为 GlideApp.with(),即可使用该 API 去完成加载工作:
|
|
与 Glide.with() 不同,诸如 fitCenter() 和 placeholder() 等选项在 Builder 中直接可用,并不需要再传入单独的 RequestOptions 对象。
当然,Glide也支持kotlin,更多用法请参考官网。
Glide中文文档、英文文档
主要执行流程
本节主要从源码角度分析Glide的主体流程,相信阅读完本节内容,你对Glide会有更清晰的认识。
简易流程图
你可能现在看不太懂,没关系。下面会从源码角度分析Glide整个执行过程,完了之后,我们在回过来看也许就明白了。
由于Glide源码较复杂,阅读前最好明确目标,认准一个功能点,然后分析这个功能点如何实现即可,只追求主体实现逻辑,莫纠缠细节,点到为止。你说,我就想琢磨细节实现咋办?待下个回合将你这个细节作为目标带入分析,如此循环,各个击破。
以上,是我阅读源码的一些建议,希望对你有帮助。
下面,我们就以图作路,分析下面这句代码。
|
|
目标很明确。Glide是如何将这张图片加载并显示到组件上的? 从问题切入,到代码里找答案。
把图片加载并显示,这个过程,我理解就三步:
- 创建request
- 执行加载
- 回调刷新UI
创建request
获取RequestManager(初次会实例化Glide)
- 从Glide.with()方法开始
|
|
可以看出,with方法重载种类多,值得说一点是,4.x新增了View参数的重载,这样便于在view类中使用。with方法比较简单,重载也是为了方便调用。
我们要知道RequestManager对象是怎么创建的?就先来看看getRetriever()方法。
|
|
以上代码可以看出,RequestManagerRetriever对象通过Glide实例获取,而Glide实例是通过单利模式创建的,这里单利也是经典的“双重校验”模式。有关Glide实例化的细节,我们后面用到再讲。
那问题简单了,RequestManager对象就是通过RequestManagerRetriever的get方法创建并返回的。
|
|
以上为RequestManagerRetriever类,我只贴了部分重要的代码。
可以看出get方法重载参数虽然很多,但最终就返回两种类型的requestManager。一种是ApplicationManager,它自动和应用的生命周期同步,应用退出,Glide也就停止加载;另外一种则是带有Fragment生命周期的requestManager。对应上述代码中 num=1-5注释,可以看出,Glide添加一个隐藏的Fragment,获取对应的生命周期回调事件,这样就可在Activity销毁时停止加载图片了。这种方式比较巧妙,不仅是Glide,RxPermission项目也是这样使用的。
到这里Glide.with()方法就分析完了。它主要完成了Glide实例化,并返回requestManager对象。
通过RequestBuilder创建Request
- 跟进load(url)方法
|
|
load方法比较简单,根据传入的model类型对应有多个重载,但最终也只是将其缓存到model变量。这一节,我们是分析request的创建,现在到了RequestBuilder,看名字就知道是创建request的,哪它在哪里创建的呢?我们接着看into方法。
|
|
到这里Request的创建就分析完了,最终通过RequestBuilder生成了一个SingleRequest实例。这个SingleRequest类中有各种属性,大部分都是默认了,当然可以在使用时通过RequestOptions配置。简单回顾下。还是在那句代码贴过来。
|
|
咋一看都分析差不多了,但这只是假象。执行到into方法,流程刚刚开始,加载、缓存、转换等逻辑都在后面。so,我们继续。
执行数据加载
我们先回到RequestBuilder类中into(Y target)方法,也是执行加载的入口。
|
|
以上代码分别来至几个类。很容易看出执行流程:into()=>track()=>runRequest()=>begin(),这里分析下begin方法。
num=0(对应代码中注释num=0处)
可以看到判断model变量为null,就回调onLoadFailed方法,这个方法就会设置我们配置的error placeholder资源。这里的model变量就是我们通过load(myUrl)方法传入的图片地址。
num=1
这里主要是判断overrideWidth, overrideHeight是否可用。分两种情况:1.如果设置了override(int width, int height) ,直接处理onSizeReady方法逻辑。2.没有设置override,Glide就会等到系统计算完组件宽高后再回调onSizeReady。所以两种情况最后都会调用onSizeReady方法。
num=2
开始前,回调设置placeholderDrawable,和num=0类似。
num=3
加载完成回调。这里是加载、缩放、转换之后的数据,可直接用于UI显示。后面再分析是怎么回调刷新UI的。
到这里,默认流程下一步就会走到onSizeReady方法。
|
|
可以看到,调用了Engine的load方法。重点来了。
|
|
先说一下Glide的缓存策略
默认情况下,Glide 会在开始一个新的图片请求之前检查以下多级的缓存:
- 活动资源 (Active Resources) - 正在显示的资源
- 内存缓存 (Memory cache) - 显示过的资源
- 资源类型(Resource) - 被解码、转换后的资源
数据来源 (Data) - 源文件(未处理过)资源
其实也就是内存缓存+磁盘缓存。
以上代码中 注释:num=4、5 处,表示从内存缓存中获取资源,如果命中,直接返回,没命中则创建任务并执行(对应 注释:num=6、7 )。本文主要分析工作流程,缓存部分就不细说了。
我们接着往下看,DecodeJob内部到底做了什么?,可以说DecodeJob是整个流程中的重点,也是我认为设计得很巧妙地方。
|
|
以上代码,为方便理解,我加了部分注释。这里再捋一捋思路和逻辑。从最开始没有命中内存缓存开始,然后执行Engine的start方法,默认情况会获取到cacheExecutor执行器来执行decodeJob任务;继续decodeJob的run方法,因为RunReason==INITIALIZE,接着获取stage,默认会返回Stage.RESOURCE_CACHE,这时通过getNextGenerator就返回了ResourceCacheGenerator加载器,紧接着就是调用 ResourceCacheGenerator的startNext方法 ,从转换后的缓存中读取已缓存的资源,如果命中则结束任务并回调结果,反之,任务切换到DataCacheGenerator加载器继续执行,若还是未命中,则切换到SourceGenerator加载器(第一次加载,由于没有任何缓存,就会走到这里),这时会通过任务调度,将线程运行环境切换到 SourceExecutor执行器来执行,最后,待SourceGenerator加载完成后结束任务,回调结果,流程结束。
现在,我们再来看看这张流程图。是不是已经有了一定的理解,不明白之处,请结合上文和源码继续研究吧。
你可能会问,Glide执行流程就分析完了? 加载网络图片的代码逻辑都没看到啊? 按照只分析主流程的思路,点到为止,以上内容就算是分析完了。但相信很多同学都想知道 加载网络图片代码逻辑到底在哪里?Glide是怎么调用这块代码的? 面对这两个问题,我们就继续吧。
这里就需要从Glide实例初始化开始说起。我们来看下Glide的构造方法。
|
|
以上代码可以看出,完成对registry对象的各种功能类注册(这种设计提高了其扩展性),太多了有木有眼花,各种loader、encoder、decoder、transcoder等等,带着问题我们只分析和网络图片加载相关的。
|
|
以上代码可以看出,分别对String.class、Uri.class、GlideUrl.class三种类型注入了不同的Factory,这个Factory使用创建ModelLoader的,ModelLoader就是用来加载图片的。说的不是很清楚,这里只需要明白,registry分别对这三种类型注册了生成ModelLoader的工厂类。
接着,进入Registry类中,看看怎么缓存这些功能类的。
|
|
通过以上代码分析,知道了ModelLoaderFactory在Glide实例化时被注册到了一个列表中,以待用时获取。 在分析DecodeJob代码逻辑时,我们知道SourceGenerator是加载图片资源的,下面我们就看下SourceGenerator是怎么获取上面注册的ModelLoader并完成数据加载的。
|
|
以上代码 num=9 注释处,可以看出loadData对象是通过helper.getLoadData()返回并在while中条件筛选得到。接着看下这个helper类是什么来头。
首先DecodeHelper是在DecodeJob中实例化的。
|
|
知道model变量的类型,对获取ModelLoader逻辑理解很重要。现在我们去DecodeHelper类中查看getLoadData方法。(对应上一个代码块中 num=8注释处 )
|
|
到这里,我们再理一下思路。资源加载器SourceGenerator,使用特定model类型,通过Register获取已经注册过的ModelLoader列表,当然这个ModelLoader是通过注册的xxxFactory.build而来,拿到ModelLoader列表后,在通过modelLoader.buildLoadData方法转化为LoadData对象列表,LoadData对象持有一个DataFetcher引用,最后就是在加载器SourceGenerator中调用以下代码执行加载数据。
|
|
再来看之前注入的三个loaderFactory
|
|
告诉大家,通过Glide.with(this).load(myUrl).into(view),真正加载网络图片对应的loader是HttpGlideUrlLoader。先来看下为什么是它?明明传入的是String而非GlideUrl。
按照类型注册,那匹配会先获取到StringLoader.StreamFactory。
|
|
在 num=10 注释处,MultiModelLoaderFactory通过Uri.class和InputStream.class创建一个ModelLoader给StringLoader,所以StringLoader的加载功能转移了。而且根据注册关系知道转移到了HttpUriLoader中。
|
|
到这里就知道了,load(myUrl) 实际加载是使用的HttpGlideUrlLoader,对应的Fetcher就是HttpUrlFetcher。
最后贴下HttpUrlFetcher代码
|
|
历经千山万水,😁,终于看到了网络通讯代码。比较简单,就是通过HttpURLConnection获取数据流并返回。当然你也可以使用Okhttp来加载,具体用法请查询官网。
到这里,已经分析完Glide的整个加载过程,剩下就简单说下回调刷新UI部分。
回调刷新UI
回到DecodeJob类中
|
|
以上代码看出,DecodeJob加载完数据后,会做转换、缓存等操作,这些咱不细究,关注回调流程即可。
|
|
从以上代码得知,回调从Decodejob出来,在EngineJob中切换到主线程并一路回调到DrawableImageViewTarget中,至于为什么默认是DrawableImageViewTarget,请查看RequestBuilder中into方法。下面我们再看下DrawableImageViewTarget相关代码,也是设置显示图片的地方。
|
|
可以看到,onResourceReady在父类ImageViewTarget中回调,然后调用setResource将图片设置并显示出来。代码执行到这里,回调过程也就完了。Glide的整个执行流程也分析完了。
总结
图片加载相关开源项目也有好几个(Picasso、Fresco、ImageLoader),为啥Glide能占一席之地。有几点:1.Google 推荐, 2. 专注平滑的滚动, 3. 简单易用的API , 4.高性能、可扩展。
经过这段时间的学习,我发现,Glide先通过简单易用吸引你,阅读源码后发现其功能之强大,所以可见代码设计、封装均是上佳之作;然后又发现,其功能扩展也能随心所欲,这里推荐一个图片转换库glide-transformations;源码中,我比较欣赏Decodejob类相关部分设计,功能无缝切换,线程调度自然。总之,Glide源码值得你用心拜读。
杂谈
文章太长
虽然一直在强调只分析主流程,不关心细节,但还是写了怎么多,囧~。后面得注意了,毕竟长篇幅,问题难免考虑周全,也不易于阅读。
RTFSC (Read the fucking source code )
遇到问题,第一时间想到的应该是阅读源码。
将知识点与自己连接起来
我以前一直有个问题,看到喜欢的文章就会收藏,想的是后面慢慢看,或者用到的时候能找到就行;但久了发现收藏的文章基本和自己没啥关系,什么用到的时候能找到?别想,基本能全忘记,啥印象都没。但现在看到好文章,我至少得评论下,甚至花时间梳理,这样对知识点就有了连接,才有可能日后为你所用。
限于水平有限,文中定有错误和疏漏之处,恳请赐教。若有不明白之处,欢迎随时评论交流。
参考
- Glide项目地址
- Glide中文文档、英文文档
- Glide最全解析系列文章 - 郭神基于3.7.0版本分析的,通俗易懂,推荐阅读。