本文分三个模块进行,分别是Registry(注册器), Engine(请求),Cache(缓存)。

Registry模块

Registry的职责是注册Glide的所有组件,是Glide扩展的基石,目前在Registry中注册的组件有ModelLoaderFactory,ResourceDecoder, DataRewinder, Encoder。

我们看下个组件职责:

ModelLoaderFactory与ModelLoader

用于构造ModelLoader,ModelLoader会指定两种类型。第一个类型是Model,即要加载的数据类型,第二个类型是请求到的数据类型,比如常见的Http请求,那么它的数据类型是InputStream,下面看下定义。

我们看下一个常见的实现,处理Http请求,并获取InputStream。

这个是Glide发起Http请求的ModelLoader,类似的请求还有很多,可以看到,这个ModelLoader需要处理的Model是GlideUrl,而我们一般传入的数据是String,这个时候Glide会通过一个MultiModelLoader进行过滤,最终将Model由String转成GlideUrl,使用这个ModelLoader进行处理。

如果要是好替换Glide的网络请求实现,就是通过替换这个ModelLoader实现的,具体可以看下Glide的其他集成库(intergration中的okhttp)。

ResourceDecoder

ResourceDecoder的作用是将ModelLoader加载出来的数据,进行解码,解码成Bitmap,或者BitmapDrawable之类的。接口定义如下:

Glide中常用的Decoder有两个,其他都是将这两个Decoder进行包装,它们分别是ByteBufferBitmapDecoder和StreamBitmapDecoder。 我们看下StreamBitmapDecoder的实现: 将InputStream转成Bitmap。

解码过程中也有一些细节问题需要注意:

  • 如果要使用ARGB_565进行解码图片,最好使用GlideModule中进行配置默认的RequestOptions。但是就是配置了使用ARGB_565进行加载,Glide根据文件头判断这个图片是否含有alpha通道,如果该图片是png的,那么仍然会使用ARGB_8888进行解码,所以为了获取最小的内存占用,需要和后端尽量返回jpeg的图片。
  • Android O以后的设备会默认使用HARDWRAE这个格式进行解码,这种格式

DataRewinder

Rewinder担任的是ModelLoader到ResourceDecoder的桥梁的角色,DecodeJob将ModelLoader获得的数据,构造出DataRewinder,然后使用Rewinder将数据传给ResourceDecoder进行解码。 下面看下DataRewinder接口定义。

Encoder

Encoder的作用是将数据转换成文件,用来配合Glide硬盘缓存。

最常用的实现是StreamEncoder,使用StreamEncoder将InputSteam转换成文件,完成文件缓存。

StreamEncoder中使用ArrayPool将字节缓冲区进行缓存,避免了每次都创建字节数组(缓冲区)。

RequetBuilder#into()

Glide的加载流程起源于RequestBuilder的into()方法,下面逐层对into()方法进行分析。

指定加载的Target,开始执行请求

首先清除这个Target上的原有请求(如果请求相同,并且可以使用内存缓存除外),然后使用RequestManager记录此请求引用并启动。 下面分析加载的起点into()。

buildRequest的过程

buildRequest 中会创键多个请求,比如如果设置了请求错误的Request,或者缩略图Request会一起创键。

此时如果当前的状态不是Pause的话,那么直接调用Request#begin()

Request的实现是SingleRequest类,我们看下SingleRequest的begin()如何执行

SizeDeterminer获取控件尺寸

这里的逻辑是如果Model不为null,那么就去将状态改成WATIINT_FOR_SIZE,然后使用ViewTarget的getSize()方法去获取尺寸。

这里获取尺寸的方法逻辑如下,如果该View的LayoutParams宽高都不是MATCH_PARENT或者WRAP_CONTENT(必须已经发生LayoutRequested),而是具体的数值,那么直接使用这个尺寸,否则,会使用ViewTreeObserver中的onPreDraw()进行监听尺寸。

从onPreDraw中获取到尺寸后, 会开始Glide真正的图片加载流程。 Glide一系列加载流程起源于Engine类,Engine类是个单例,存在于GlideContext中,我们看下Engine的初始化。

这里有个技巧,如果一个ImageView的大小是WRAP_CONTENT,并且没有发生layoutRquested,即没有通过addView()之类的方法添加,那么会使用屏幕的宽或高(最大的)进行采样,而不是控件的大小。解决办法是强制使用waitForLayout()

或者使用override()指定宽高。

开始加载图片

Engine加载流程(核心)

Engine的核心是load方法,这个方法也是Glide的核心,下面我们重点分析下这个方法。

  1. 从当前的的弱引用中进行查找
  2. 从内存缓存中进行查找
  3. 从已经开始的网络请求中进行查找
  4. 创建一个新的网络请求

参考 http://frodoking.github.io/2015/10/10/android-glide

在方法前面会检测是否能够使用内存缓存,Glide的内存缓存分为两种,一种是ActiveResources使用WeakReference存储,一种是MemoryCache使用LRU策略进行存储。

如果能直接取出来缓存的话,是不需要进行解码的,直接回调onResourceReady(),如果缓存没命中,这时Glide会检测是否有正在进行的请求,如果有,直接添加个回调然后返回。

如果以上都没有,那么会直接启动新的加载流程,代码如下:

EngineJob的start()方法,会开始一个新的解码流程。 会先在磁盘线程池加载磁盘缓存,如果没有,那么使用网络线程池加载。

DecodeJob的run()如下:

核心逻辑是getNextStage()getNextGenerator(),这两个方法描述的是当前加载过程的状态转换。

getNextGenerator()方法会根据当前状态创建对应的Generator。

我们看下这个Stage的获取,这个可以理解为Glide的核心了。

状态切换: 带尺寸缓存->缓存->网络->完成状态 每个状态均使用DiskCacheStragey进行判断,如果DiskCacheStragey不支持直接返回下一个状态。

先看下DataFetcherGenerator的定义。

从上面生成DataFetcherGenerator的方法可以看出,DataFetcherGenerator的作用就是发起加载流程,结束之后使用回调返回结果,回调定义如下:

下面看下SourceGenerator的实现,其他的实现大多类似,都是获取LoadData,然后使用LoadData的DataFetcher进行加载图片。

SourceGenerator的逻辑大概是这样,先通过Registgry获取DataFetcher,然后使用DataFetcher(这里是HttpUrlFetcher)加载, 如果加载成功会调用onDataReady(),我们看下SourceGenerator的 onDataReady调用。

rechedule()调用之后,会继续调用startNext(),会调用到cacheData()

回调到DecodeJob的onDataFetcherReady()

看下这个decodeResourceWithList()

中间会回调到DecodeJob中的onResourceDecoded(),我们看下这个方法的实现,这个方法的作用是调用RequestOptions中的Transform处理图片,然后将ResourceCache的Key和Encode准备好(放在deferEncoderManager中),稍后进行写入缓存。

接下来的逻辑是从Registry中寻找能够解码的Decoder,然后从Rewinder中获取数据,逐个尝试使用Decoder进行解码。 这里的Decoder实际上是StreamDecoder,实际解码类是Downsample。 一会在分析decoder实现,现在看下整体流程。

解码完成后,调用notifyEncodeAndRelease()通知主线程处理图片,如果还需要处理encode的话,进行处理。

主线程收到回调,会回调下面的方法。

cb.onResourceReady()完成后,Glide加载图片的工作就大部分完成了。剩下的就是通知Traget的回调了。

Glide 缓存

Glide缓存分成三个部分,分别是ArrayPool,BitmapPool,LruResourceCache。

LruResourceCache

Glide 中最直接的内存缓存,我们看下Key的实现。

这是Key的所有字段,可以看出,出了与Model有关,还有Transform之类的因素,这也是Glide的高效之处,直接可以将处理过的图片进行缓存。 LruResourceCache在Engine#load()中进行获取,即如果图片存在LruResourceCache中,会直接返回,不会创建DecodeJob进行加载。

LruResourceCache是Glide自实现的Lru容器,存放的数据是资源的包装类Resource<?>。

BitmapPool

BitmapPool是Glide内存缓存的重要成员。这个BitmapPool的作用是提供一个空的Bitmap供Glide解码使用(Config.inBitmap属性)。 同时,也可以作为提供Bitmap的容器,如Transformation中的使用。

Bitmap的获取通过一个LruPoolStrage接口实现,用于区分Android版本实现。

Strategy的创建策略如下

如果api大于19,那么使用SizeConfigStrategy,这些版本只要满足被回收的bitmap内存占用大于要解码的bitmap即可。 SizeConfigStrategy查找合适的Bitmap逻辑如下

如果api小于等于19,那么只有在bitmap尺寸(width,height)和Config都相同的时候才能够被复用,所以该BitmapPool的实现较为简单,只是一个KeyPool和GroupLinkedMap。

BitmapPool再Downsample中有频繁的使用,Downsample类是Decoder的核心。 解码前专门设置inBitmap属性。

ArrayPool

ArrayPool也是Glide的内存缓存,它的实现类是LruArrayPool,它的作用是在Glide解码图片和解析文件时提供缓冲区(byte[])的复用逻辑,这个Volley的代码里也有,意思就是我们不需要在每次读取文件的时候手动new byte[]了。

java中默认的缓冲区是4kb,如果频繁的申请这4kb内存,势必会造成内存抖动,从而影响流畅性。

我们先看下ArrayPool的使用,在Downsample类中。

其中byte[] bytesForOptions = byteArrayPool.get(ArrayPool.STANDARD_BUFFER_SIZE_BYTES, byte[].class);就是从缓存中获取缓冲区。

ArrayPool的实现与BitmapPool类似,只不过变成了获取byte[]或者int[],这里不多分析了。

总结

通过阅读Glide源码,收获还是很多的,下面记录下Glide的一些使用技巧,帮助我们更好地使用Glide。

  • Glide推荐使用ARGB_565进行解码,需要在DefaultRequestOptions中进行配置,但这不是绝对的,如果你的图片是png或者其他含有alpha的图片,那么仍然会使用ARGB_8888进行解码,所以如果想省点内存的话,最好限制下图片格式。

  • Android O以后,会优先使用HARDWARE格式进行解码,但这些图不能对像素进行操作,需要小心。

  • 加载图片的大小最好指明尺寸,如果使用WRAP_CONTENT的ImageView加载图片,那么有可能图片会按照屏幕的最大尺寸进行采样,从而造成内存浪费。解决办法就是使用具体尺寸或者MATCH_PARENT,或者使用override()指明图片大小。

  • 如果项目代码中要初始化Bitmap,不妨试试从BitmapPool中进行获取Glide.get(context).getBitmapPool().get(...),如果有缓存的Bitmap,那么就不用创建新的了。

  • 使用Glide加载的图片,如果没有设置skipMemory(),那么不要调用Bitmap的recycler()(该图片已在内存缓存中),否则其他模块使用会造成Crash。

参考

Glide 探究随记
https://juejin.im/entry/5aa11f826fb9a028d663bfe0 http://www.voidcn.com/article/p-thodukjp-brq.html

发表评论

电子邮件地址不会被公开。 必填项已用*标注