浅谈HarmonyOS Glide组件的GIF能力 原创 精华
HarmonyOS Glide组件是一款非常优秀的图片处理工具,不仅支持多种格式图片的加载,而且采用磁盘缓存和内存缓存方式实现图片的预加载,同时还能指定图片缓存大小,节省内存。本文将通过介绍Glide组件的GIF能力,来解读Glide加载资源的过程。
通过以上GIF可以看到,一张网络上的GIF图片已经被成功下载,并且展示到Image控件上了。
我们到底做了什么?实际上核心的代码就只有这一段而已:
虽说只有这简简单单的一段代码,但大家可能不知道的是,Glide在背后帮我们默默执行了成吨的工作。下面,我们将围绕着这段简单的代码,来解读Glide加载GIF的过程。
一、加载过程与数据转换
在开始解读Glide加载GIF的过程之前,先说明一下图片的加载过程以及图片加载过程中的数据转换,便于后面对整个过程的理解。如下所示,是GIF的加载过程:
如下所示,是GIF加载过程中的数据转换:
1、load状态传入的model类型
2、request状态获取的数据类型
3、原数据经过decoder和transcode之后的数据类型
4、transformation变换
5、animation加载动画实现
二、Glide.With()
with()方法是Glide类中的一组静态方法,用于获取RequestManager对象。Glide.with(Context)流程如下所示:
1.通过Glide.get(context)初始化Glide
2.通过GlideBuilder初始化各项配置
3.返回requestManagerRetriever对象
4.调用RequestManagerRetriever中的get方法,通过RequestManagerFactory中的build()方法创建并返回了RequestManager,用于管理Glide的请求。
三、Glide.asGif()
通过asGif()方法,规定了最后资源转化类型为 GifDrawable。如果加载的资源不是GIF,则将操作失败。
这里需要注意的是如果加载的是GIF文件,即使没有使用asGif()方法,但只要配合DraweeView使用,最终解析还是会走GIF流程。如果用户希望解析的GIF显示为一张单帧图片,那么一定要在asBitmap ()方法中声明需求,让Glide知道需要的仅仅是一张单帧图片而非GIF。
四、Glide.load()
load()方法用于创建一个目标为Drawable的图片加载请求,传入需要加载的资源(String,URL,URI等)。由于with()方法返回的是一个RequestManager对象,那么很容易就能想到,load()方法是在RequestManager类当中。通过调用asDrawable()方法,创建一个目标为Drawable的图片加载请求RequestBuilder。
load方法比较简单,流程也比较清晰,主要是保存用户传入的参数,包括load传入的model和RequestOption构建的参数都会被记录保存,用于后续构建Request使用。如下所示:
五、Glide.into()
如果说前面都是在准备开胃小菜的话,那么现在终于要进入主菜了,因为into()方法是整个Glide图片加载流程中逻辑最复杂的地方,into()方法的作用是在子线程中网络请求解析图片,并回到主线程中绘制图片。由于into()过程非常复杂,所以我们将这部分拆分为三个小节进行讲解。
1.资源加载
Into()方法从load()创建的图片加载请求RequestBuilder开始。资源加载过程中,通过onSizeReady()函数获取image控件的宽和高。如果已知控件宽、高则直接进入onSizeReady函数执行后续任务。如果控件宽、高未知,则会在ViewTarget中进行监听回调,待控件拥有宽高之后再执行onSizeReady函数和后续任务。
进入engine.load函数后。首先通过loadFromMemory()函数,加载activeResource中的缓存资源,如果activeResource没有找到资源,则会通过loadFromLruCache()方法,到LruCache缓存中寻找资源。
如果通过以上方法都没有找到缓存资源,则会开启新的任务进行加载。在waitForExistingOrStartNewJob()方法中创建EngineJob和DecodeJob,然后通过EngineJob执行DecodeJob,解析任务。如下图所示:
2.资源解析
完成资源加载之后,Glide会进入资源解析,通过decodeResourceWithList()方法获取对应的解析器。代码如下所示
然后通过DataType、ResourceType来寻找具体实现类,发现byteBufferGifDecoder的decode才是真正的执行者。
下面是ByteBufferGifDecoder的资源解析过程,解析完成后会生成一个GifDrawable回调资源。
如果成功获取resource就执行回调通知,onResourceReady()用于将图片显示到DraweeView上。
如果resource继承了Animatable,就会触发animatable.start()进行GIF的加载和绘制。
3.GIF加载和绘制
GIF的加载和绘制就是通过将GIF解析成一张张的单帧图片,然后再将单帧图片循环不停地绘制到canvas上,从而实现动画效果。
GIF加载和绘制的序列图如下:
3.1GIF加载
Glide 加载 GIF 的原理就是将GIF 解码成多张图片进行无限轮播,每帧切换都是一次图片加载请求,当加载到新的一帧数据时会对旧的一帧数据进行清除,然后再继续下一帧数据的加载请求,以此类推。
在GIF加载和绘制的序列图中可以看到,ImageViewTarget中的onResourceReady触发onStart() =>realStart()=>startRunning()。当GIF为单张图片的时候就直接绘制。当GIF为多张图片就先加载第一张,然后注册frameLoader的回调。
到这里,就是整个GIF加载的关键了,通过loadNextFrame加载GIF的下一帧。
然后进入DelayTarget类中执行onSourceReady()方法,使用EventHandler将PixelMap的resource传到主线程上,用于定时发送解析好的资源。
FrameLoaderCallback是EventHandler的实现类,用于接收EventHandler发送过来的任务,并触发onFrameReady函数。
当上一帧加载完成后, GifFrameLoader类中的onFrameReady(target)方法触发绘制的回调操作,然后进入加载GIF的下一帧。同时,会通过FrameLoaderCallback.MSG_CLEAR对旧的一帧数据进行清除。清除完后再次通过loadNextFrame()加载下一帧,实现了GIF循环不停去加载下一帧的这个流程,直到加载完整个GIF。
3.2GIF绘制
GIF绘制,就是将解析后的图片通过invalidateSelf()方法通知DraweeView进行重绘。
在绘制过程中invalideDraweeView通过调用GifDrawable的drawToCanvas()方法将图片绘制到Canvas上。
GifDrawable类中的onFrameReady()调用的invalidateSelf()函数用于执行绘制任务
通过调用setImageElement(((RootShapeElement) resource))方法,实现Callback接口。
最后通过drawToCanvas()方法生成空白PixelMap交给GifDrawable绘制,并根据scaleMode()方法重新设置最后生成图像的位置。
至此,整个GIF的流程就走了一遍。
六、课题延伸
因为GIF加载过程其实是无限循环加载单张图片的过程,其实对系统的性能消耗还是非常大的。所以在使用GIF的时候,一定要坚持用完之后及时释放资源。在这里因为HarmonyOS的生命周期和Android有所不同,所以在DraweeView开放了stopGif()方法,当你的GIF不打算用之后,请务必先调用stopGif(),防止内存泄露。
重要提示:
1、目前必须配合DraweeView使用GIF。
2、如果Glide使用了生命周期较长的上下文,例如applicationContext,则在GIF页面结束时调用绘制视图的stopGif方法停止Glide,以减少资源浪费。
3.如果您想使用Glid的GIF能力,但原生Image不支持此功能,因为Image和Element是独立的,不能使用Element重绘。要支持GIF,您需要自定义Image。具体可以参考DraweeView的实现
源码地址在哪?
https://gitee.com/openharmony-tpc/glide
感谢,已更新