参考文章:
-
郭霖大神专栏(必看)
-
Glide源码解析06-Glide流程图(流程图超赞,打call)
-
Glide 框架简单粗暴的流程图(简略版流程图,虽然省略了很多,但是整体流程还是很清晰的,而且类之间的关系也也很清晰)
-
Glide功能模块设计分析之强大的缓存模块(我看过的所有缓存分析中最详细易懂的)
自己学习的步骤
步骤一: 郭霖大神专栏 步骤二: Glide详解之架构分析 步骤三:绘制Glide流程图 步骤四:分析Glide的缓存机制 步骤五:绘制Glide的缓存流程图 步骤六:深入查看缓存的代码,了解原理(比如:LruCacheStrategty,BitmapPool等) 步骤七:查看Picasso
的相关源码分析的文章,对比Glide
的优势与不足 本人渣渣一枚,学习方式不一定是对的,仅供参考。
目录
以下分析以Glide 3.7版本为主。
1. Glide加载流程图
Glide.with方法
RequestManager
对象;
load方法
前面说过,经过with
方法后返回的是 ReqeustManager
,所以load
方法,实际上调用的是 ReqeustManager
的load
方法。
into方法
接下来是最最复杂的into方法。 在Load方法中,返回了DrawableTypeRequest
,所以into
调用的是DrawableTypeRequest
的into方法。
图片中有一处标绿,这里的buildRequest
存在一些逻辑判断,如下:
2. Glide核心类
- Glide:初始化系统,负责管理系统其它模块,如:数据加载器、网络栈等;
- RequestManager:创建、初始化以及管理系统所有的Request,能跟Activity以及Fragment进行生命周期的绑定进行相应的请求操作;
- Engine:将资源加载请求放入执行线程池,加载完后返回给主线程
- ExecutorService:线程池执行服务,负责分发调度
EngineRunnable
可执行对象; - DataFetcher:看名字就可以知道,这个类是负责从磁盘或者网络端加载数据;
3. Glide 缓存机制
Glide的缓存类型分为两大类,一类是Resource缓存,一类是Bitmap缓存。
Resource缓存
Glide在缓存Resource使用三层缓存,包括: 一级缓存:缓存被回收的资源,使用LRU算法(Least Frequently Used,最近最少使用算法)。当需要再次使用到被回收的资源,直接从内存返回。 二级缓存:使用弱引用缓存正在使用的资源。当系统执行gc操作时,会回收没有强引用的资源。使用弱引用缓存资源,既可以缓存正在使用的强引用资源,也不阻碍系统需要回收无引用资源。 三级缓存:磁盘缓存。网络图片下载成功后将以文件的形式缓存到磁盘中。
Bitmap缓存
移动设备中对内存的使用管理要求很高,而图片资源往往是比较占内存的,这就要求我们在加载图片时尽量合理的使用内存,虽然有了图片内存缓存程序在一般情况都都能工作得很好,不会出现大的问题,但是内存缓存的容量总是有限度的(一般为LRU策略),不能无限制地增长,所以程序还是会周期性的申请和释放内存。特别是在一个长列表快速滑动时,会造成大量的图片申请和释放,导致 GC 频繁,进而产生卡顿 所以这就要求我们对图片显示实体对象有一个复用策略,避免大量的图片申请和释放 Android 中图片的显示实体是 Bitmap 对象,每次图片显示时会将图片资源构建成一个 Bitmap 对象,创建 Bitmap 和销毁 Bitmap 是比较消耗系统资源的,所以能让 Bitmap 可复用,可有效降低 Bitmap的创建和销毁,和频繁的内存申请和释放操作,从而减少卡顿提高应用性能,在3.0以前 Bitmap 的数据是存在 native 区域,3.0以后存在 Dalvik 内存区域,API11 后 系统提供了 Bitmap 复用的 API Managing Bitmap Memory Glide 中为了复用 Bitmap 构建了一个 BitmapPool,图片的 Bitmap 的申请和释放都需要通过它来处理。需要加载新的图片时,先从 BitmapPool 中查找有没有相应大小或者稍大一点的 Bitmap,有则直接使用,没有再创建新的 Bitmap。一个长列表中的图片往往是大小相同的,所以这个复用率还是相当可观的。
3.1.1 内存缓存读取流程分析
问: 为什么要加入activeResouce呢?它有什么好处?
答: activeResource 缓存存放的是正在使用的图片资源对象,数据结构为 HashMap<Key, WeakReference<EngineResource<?>>> ,不同于内存缓存用 Lru 算法策略的是,该缓存是无容量大小限制的,内部用引用计数来确定是否被外界所正在使用,当引用为0时会从该缓存中移除,加入到内存缓存或者 BitmapPool 中。该缓存的作用是保护正在使用的资源并复用,试想一下如果没有这级缓存,只有 LruMemoryCache 那么当 LruMemoryCache 缓存达到容量上限时有可能移除掉正在使用的图片资源,当应用中另外一个地方需要同时显示同样的图片资源时Glide将在内存中找不到这个资源对象则又会重建一个新的资源对象。
3.1.2 内存缓存写入流程分析
Tips:EngineResource是用一个acquired变量用来记录图片被引用的次数,调用acquire()方法会让变量加1,调用release()方法会让变量减1。
Tips: ResourceListener 接口中只有 onResourceReleased() 一个方法,当 EngineResource 需要被释放时会回调该方法来通知监听者。这里的监听者正是 EngineJob,在 EngineJob 的 onResourceReleased() 方法中,将 EngineResource 从 activeResources 中删除,并且如果没有设置跳过内存缓存,则放入 MemoryCache 中,否则回收 EngineResource。这将导致 EngineResource 中持有的 Bitmap 回收到 BitmapPool 中,等待复用。
3.1.3 磁盘缓存读取策略分析
比较简单,直接文字表达吧。 Glide的硬盘缓存策略就是调用decodeResultFromCache
从磁盘中读取处理后的文件,如果获取不到,那么调用decodeSourceFromCache
获取缓存的源文件。这里要注意两点:
- 获取源文件用的Key是key.getOriginalKey(),但是获取处理后的文件用的key是EngineKey,包含各种width,height,signature等成员变量,非常复杂。 如图:
override(width,height),
也会导致Key发生变化。 - 什么叫处理后的图片(Result)?什么叫源文件(源图片Source)? 答:Glide从网络获取到图片之后,并不会直接显示到ImageView(或者说容器上),而是经过转换和解码。转换和解码的过程在
transformEncodeAndTranscode
方法里:
那我们看transform
方法:
transform
方法的实现类:
BitmapTransformation
:
到这里就明白了,源文件要根据我们外面传递的centerCrop
或者 fitCenter
进行相应的转换。至于具体怎么转化,等下一篇继续。这里继续分析就太长了。
转换部分看完之后,我们看解码部分。主要是transcode
方法:
可以看到,最后就是返回个GlideBitmapDrawableResource
。
3.1.4 磁盘缓存写入策略分析
上面提过,当没有缓存的情况下,会从网络读取图片,此时会调用decodeSource
方法:
这一步是将源文件写入了磁盘,那么将转化后的文件写入磁盘的操作是在哪里呢? 答案是:transformEncodeAndTranscode
方法。
可以看到,它是把EngineKey作为key,不再是OriginKey了。
Tips:磁盘缓存的默认路径在 /data/data//cache/image_manager_disk_cache,默认的大小为 250MB,默认的实现类是 DiskLruCacheWrapper。
好了,到这里,磁盘的写入分析就结束了。
3.2 bitmap缓存
Bitmap的缓存用的也是Lru算法,Glide中默认的实现类是LruBitmapPool
(API 11 以上)以及BitmapPoolAdapter
(API 11 以下)
LruBitmapPool
LruPoolStrategy
;
SizeConfigStrategy
,否则采用实现类
AttributeStrategy
。
简单来说,reconfigure() 方法能够在不重新初始化 Bitmap 和不影响底层内存分配的前提下修改 Bitmap 的尺寸和类型。使用该方法能够复用旧 Bitmap 的内存从而避免给新 Bitmap 分配内存。通过复用生成的 Bitmap,新的内存大小可以通过 getByteCount() 方法获得。
由于 Bitmap 是复用的,所以它所映射的底层内存中还是原始图片的数据,所以 BitmapPool 将 Bitmap 返回给使用者前,还需要使用 Bitmap 的 eraseColor(Color.TRANSPARENT) 来擦除旧的数据。
问:内存缓存,磁盘缓存以及BitmapPool缓存三者是如何协同工作的?或者说,当什么时候,bitmapPool的get,put方法会被调用?
答:通过查看源码,可以发现,bitmapPool的put方法调用是在Resource的recycle方法中,因此可以推出:当这张图片不再被使用被使用或者被取消加载到view上的时候,会被放入缓冲池;
问:BitmapPools是不是无限大的? 答:不是的。BitmapPool的大小是由MemorySizeCalculator
这个类算出来的,具体是在getBitmapPoolSize()
方法,这个大小默认是缓存四个屏幕图片所占用的大小。如下图。
如果超过这个大小,bitmapPool就进行移除最早加入的bitmap。
public synchronized boolean put(Bitmap bitmap) {
//省略不相关代码
evict();
return true;
}
复制代码
maxSize虽然默认是四个屏幕大小,但是我们可以通过setSizeMultiplier来改变它的大小。
MemoryCategory
这个枚举类进行设定的。
总结
Glide的加载过程非常复杂,涉及的类很多。但是总的来说,各个类的职能很清晰。并且,Glide的缓存机制非常完善,不仅有Resource的三级缓存,而且还自带Bitmap缓存池,降低了创建和销毁bitmap带来的资源消耗,从而可以大大提高图片的加载效率。 Picasso源码还没看完,先占坑~~~,之后对比两者。