Android | Glide 细枝篇
《看完不忘系列》之Glide (树干篇)一文对Glide
加载图片的核心流程做了介绍,细枝篇作为补充,将对一些具体实现细节进行深入。本文篇幅略大,大家可以根据目录索引到感兴趣的章节阅读~
源码基于最新版本4.11.0
,先上一张职责图预览下,一家人就要整整齐齐~
本文约3200字,阅读大约10分钟。如个别大图模糊(官方会压缩),可前往个人站点阅读
Generated API
通过创建一些类,继承相关接口,然后打上注解,由apt来处理这些类,从而实现接口扩展。
全局配置
注解@GlideModule
用来配置全局参数和注册定制的能力,在application里使用AppGlideModule
,在library里使用LibraryGlideModule
,
比如现在的Glide
的Bitmap默认配置是ARGB_8888
,如果项目图片类型比较单一,不需要透明度通道和高色域,可以配置全局的RGB_565
减少一半内存。见默认请求选项,
或者可以根据设备评分来衡量,普通机型配置RGB_565
(在需要透明度通道的场景局部使用ARGB_8888
),高端机型则可以直接配置ARGB_8888
,纵享奢华体验。
行为打包
注解@GlideExtension
可以将一些通用行为打包起来,扩展一个接口方便业务层调用。比如电商App很多页面都有商品列表,这些商品图片的宽高如果是固定的,就可以包装起来,
rebuild一下项目,生成类build/generated/ap_generated_sources/debug/out/com/holiday/srccodestudy/glide/GlideOptions.java
,里面会多出一个方法,
这时,就可以用goods来直接使用这一组打包好的行为了,
Generated API
比较适合短周期/小型项目,中大型项目往往不会直接裸使用Glide
,会包一个中间层来进行隔离(禁止业务层用到Glide
的任何类),以便随时可以升级替换,这个中间层就可以根据需要来自行扩展。
空Fragment取消请求
Glide.with(context),当context是Activity时,每个页面都会被添加一个空fragment,由空fragment持有页面级别RequestManager
来管理请求,那退出页面时是如何取消请求的呢?
with通过RequestManagerRetriever
获取SupportRequestManagerFragment
,
退出页面时,
代码看起来有点绕,大致如下图,
Cache缓存
内存
内存缓存有两级,一是处于活跃状态,正被view使用着的缓存,称活跃资源
;二是没被view使用的,就叫他非活跃资源
吧,
读取内存:
写入内存:
如下图:
磁盘
看看缓存目录/data/data/com.holiday.srccodestudy/cache/image_manager_disk_cache/
,
先看日志文件journal
,
下半部分是操作记录,行开头指操作行为,DIRTY
表示在编辑(处于脏数据状态,别读),CLEAN
(干净状态)表示写好了,可以读了,READ
表示被读入了,REMOVE
则表示被删除,中间很长的一串字符就是缓存键或文件名字,最后的数字是文件大小,如404730 B=395.2 KB,只有处于CLEAN
状态才会写大小。那么图中的文件名是什么意思,为啥key的后面还有.0
后缀?因为一个entry
(日志条目)可以对应多个图片,.0
代表entry
的第一张图片,如果有配置1对多,那就会有.1
、.2
这样的后缀。选一个.0
文件点击右键,Save as
保存到电脑,改个jpg后缀,就能看图了。
来到DiskLruCache
类(看名字知道还是最近最少使用算法
),
那么读取和写入时机在哪呢?我们反向追踪一波get
方法,从DiskLruCache
到DiskLruCacheWrapper
的get
,然后再追,发现有两个类调了get
,分别是DataCacheGenerator
和ResourceCacheGenerator
,前者是原始图片的缓存,后者是经过downsampled
向下采样或transformed
转换过的图片,在磁盘缓存策略中提到:
目前支持的策略允许你阻止加载过程使用或写入磁盘缓存,选择性地仅缓存无修改的原生数据,或仅缓存变换过的缩略图,或是兼而有之。
默认情况下,网络图片缓存的是原始数据,那我们继续跟DataCacheGenerator
,
继续跟modelLoader.buildLoadData
,后边就是把图片文件cacheFile封装成ByteBufferFetcher
,然后调用上边的loadData.fetcher.loadData
进行回调,就不继续跟了,startNext
方法在DecodeJob
里会被调用,树干篇中可知他就是图片加载过程用到的一个Runnable,好了,下面看看缓存写入时机,反向追踪edit
方法,
同样,put
方法也会在DecodeJob
里被调用,就不往上跟了。
合并内存缓存和磁盘缓存,
BitmapPool令人诟病
Glide
有将Bitmap进行池化,默认是LruBitmapPool
,他会决定怎么复用Bitmap、何时回收Bitmap、池子上限时清理,也就是说,他全盘接管了Bitmap的处理,如果项目中有在回调方法外持有Bitmap
、手动回收Bitmap
的场景,会发生意料外的crash,详见资源重用错误的征兆。即,我们要有这样的意识,既然使用了Glide
,就不要再关心Bitmap的事情了,全盘交由BitmapPool
管理即可。
发散:所谓池化,就是设计模式中的享元模式,即维护一个有限个数的对象池来实现对象复用,从而避免频繁的创建销毁对象。比如Handler消息机制中的
Message.obtain
,就是从消息池(链表)里取出对象来复用,池子的消息总数被限制在MAXPOOLSIZE=50。Android内的很多实现都是基于Handler(消息驱动)的,池化能减少很大部分的创建销毁。
Decoder解码
链路有点长,直接看调用栈,
可见最终走的是native层的nativeDecodeStream
,哈迪就不跟了,对inputstream转成bitmap感兴趣的读者自行研究啦~
总结
Glide
有如下优势:
空Fragment感知页面生命周期,避免无效请求
高度可配置,详见配置
三级缓存(网络层缓存如okhttp就不考虑了):内存活跃资源
ActiveResources
、内存非活跃资源LruResourceCache
、磁盘缓存DiskLruCache
可定制,引入apt处理注解,打包行为,扩展接口。(哈迪没怎么用,感觉有点鸡肋,可能以后会真香)
可扩展,可以替换网络层、定制自己的图片来源
ModelLoader
,详见编写定制的ModelLoader无侵入,into可以传入最简单的ImageView
优秀的设计模式运用、应用层优雅的链式调用
至于缺点吧,暂时还没想到。本文只列出了哈迪觉得比较精彩的细节,可能还有遗漏的一些点,大家有补充的可以留下评论,后续我会更新进本文。
参考资料
版权声明: 本文为 InfoQ 作者【哈利迪】的原创文章。
原文链接:【http://xie.infoq.cn/article/1c111b5dd8573f2c7998b1bca】。文章转载请联系作者。
评论