目标是读懂Glide的第一个版本代码
2012年的代码, library模块居然能这么顺利的运行?? 让我很惊叹
调用链
从一个基本的例子 Glide.load("https://png").into(imageView);
入手
load() 会返回一个Request 对象, 后面的流程都是从Request#into方法开始的
private方法都用 下划线标出
-
into() -> 1. _finish(Target) -> 2. _getImagePresenter()#setModel() -> 3. Target#getSize(cb) setModel函数内的getSize(cb) 是结尾, 可以看到, 最终是注册一个onSizeReady的监听回调
-
onSizeReady() -> 5. _fetchImage() -> 6. ImageLoader#fetchImage(cb)
这一步fetchImage又注册了一个回调
ImageManagerLoader#fetchImage() -> 7. ImageManager#getImage(cb)
这里走了两步又注册了一个回调
ImageManager#getImage() -> 8. returnFromCache(cb) or-> 9. ImageManagerJob#run() -> 10. _getFromDiskCache() -> 11. _resizeWithPool() -> 12. StreamLoader#loadStream(cb) 这里是获取Image的操作, 第8步是取内存缓存, 最后注册一个StreamReady回调
-
onStreamReady() -> 14. _finishResize() -> 15. _putMemoryAndDiskCache() -> 16. mainHander#onComplete() 到mainHander结束, 可以看到回调还是挺多的, 最后是各种callback回去了, 直到
-
onImageReady(Bitmap) -> 18. setImageBitmap()
其实这些调用过程, 回调自己通过代码跳转+断点很容易走完, 这里简单列出, 没有什么值得细说的.
网络图片的获取和本地图片的获取两种都被抽象成了StreamLoader, 获取到的资源是 InputStream 对象.
两级缓存
第一级-内存缓存
- 读 ImageManager#returnFromCache() 主线程, 命中缓存后直接回调 onLoadCompleted
- 写 ImageManager#finishSize() 写内存换粗是在主线程 疑问: 发现写内存缓存之前都要再次判断是否已经有了该缓存, 为什么要这么做呢? 如果已经有缓存了, 那岂不是直接使用而不用再去获取了? 内存缓存 memoryCache的读写都是在主线程进行的, 也没有竞争.
第二级-硬盘缓存
封装在 ImageManagerJob 类内部, 读/写硬盘缓存得到的是一个resize过的bitmap
- 读 ImageManager#getFromDiskCache() 读的过程有resize过程
- 写 ImageManager#putInDiskCache() 直接通过 Bitmap.compress 写入
其中, 内存缓存是对bitmap的直接缓存, 用引用计数的方法来控制资源释放, 而硬盘缓存写入的是 Stream流, 不是Bitmap, 可以看出上述的写硬盘缓存是 load(InputStream) -> resize(Bitmap) -> putDiskCache(Bitmap) -> writeToCache(Stream) 是这样一个过程, 这里硬盘缓存缓存的是resize后的Stream
resize & downsampler
读写缓存文件涉及到的resizer, 主要的类是 ImageResizer, Downsampler, 从InputStream到Bitmap的转换是通过 ImageResizer#load() 来做的, 根据目标尺寸(View大小) 和 图片尺寸进行resize, 先计算 inSampleSize, Downsampler类里实现了 NONE(默认尺寸), AT_LEAST(取宽高比值较小), AT_MOST(取宽高比值最大), inSampleSize的意义见 my.oschina.net/rengwuxian/…, 根据inSampleSize 参数可以有效减少不必要的内存占用.
Transformation
默认实现了三个变换操作 NONE, CENTER_CROP, FIT_CENTER, 内部的操作其实是从ImageView源码中拷贝出来的, 也开放了 Glide#transform() 进行自定义的变换操作.
Bitmap 复用
Bitmap缓存是根据Bitmap的宽高来存储的, 相同宽高的Bitmap可以复用, 这在列表里相同大小的图片加载时能节省不少临时创建内存的操作. 但缓存和复用是两个概念, bitmap缓存是针对于Key来说, 对于相同的key, 如果有缓存, 直接使用, 而对于一个一个bitmap来说, 当使用完毕后, 没有立即进行recycle, 而是通过acquire/release来控制引用计数, 当计数为0时标明没有任何地方在使用这个Bitmap了, 然后放到BitmapPool里面, 最终会被LRU算法淘汰掉, 如果在淘汰前又被复用了, 则增加引用计数, 重复使用这个Bitmap. 具体的类是LruBitmapPool#get, put
UML(类之间关系)
读源码的过程中遇到的问题和疑问记录
每一个调用栈都涉及到一个callback, 4.0的源码里明显少了许多, 不知道是怎么处理这样的回调的, 后面应该会学习到.
imagePresenter 与 Glide 与 Target 是什么关系, 一对多? 那cache是怎么对应的(:cache 只有一份, 与Glide实例数量一致)
FileLoader 的Context 为什么不会内存泄漏 (:一个Loader对应有一个Presenter, Presenter能够正常回收/复用 不会泄露)
4.0网络获取时设置宽高以减少内存消耗是怎么做的?
优缺点
源码注释少
回调层级过多
List滑动时卡顿问题
resume/stop 请求队列的暂停与恢复
(持续更新本文)