Glide自我解惑(一)

参考文章:

自己学习的步骤

步骤一: 郭霖大神专栏 步骤二: Glide详解之架构分析 步骤三:绘制Glide流程图 步骤四:分析Glide的缓存机制 步骤五:绘制Glide的缓存流程图 步骤六:深入查看缓存的代码,了解原理(比如:LruCacheStrategty,BitmapPool等) 步骤七:查看Picasso的相关源码分析的文章,对比Glide的优势与不足 本人渣渣一枚,学习方式不一定是对的,仅供参考。

目录

以下分析以Glide 3.7版本为主。

1. Glide加载流程图

Glide.with方法

最终返回 RequestManager对象;

load方法

前面说过,经过with方法后返回的是 ReqeustManager,所以load方法,实际上调用的是 ReqeustManagerload方法。

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获取缓存的源文件。这里要注意两点:

  1. 获取源文件用的Key是key.getOriginalKey(),但是获取处理后的文件用的key是EngineKey,包含各种width,height,signature等成员变量,非常复杂。 如图:
    但是,如果是EngineKey的话,看equals和hashCode方法,就可以知道,如果你在外面override(width,height),也会导致Key发生变化。
  2. 什么叫处理后的图片(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

LruBitmapPool内部采用的缓存策略是 LruPoolStrategy;
它是个接口,在KITKAT(API 19 )以上,它采用实现类 SizeConfigStrategy,否则采用实现类 AttributeStrategy
AttributeStrategy 复用的图片需要图片的尺寸和 Bitmap.Config 完全一致 SizeConfigStrategy 复用要求相对宽松,复用的图片需要 Bitmap.Config 一致,但复用图片的所需内存比原图小即可。确保 Bitmap.Config 一致后,如果有内存大小一致的图片则直接复用,没有则选取内存稍大一点的图片。显然采取 SizeConfigStrategy 的复用率更高。之所以有这两者的区分,跟 Bitmap 的 reconfigure() 方法有关,BitmapPool 能够复用旧图片的内存得益于这个方法

简单来说,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源码还没看完,先占坑~~~,之后对比两者。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值