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】。文章转载请联系作者。











评论