识别gif

能够先粗看一下调用链,这个对咱们剖析问题很有协助

Glide 加载 Gif 动画的一些细节剖析

能够看出来 是在DecodePath中的 decodeResourceWithList 办法来做 gif识别的

Glide 加载 Gif 动画的一些细节剖析

这儿要注意的是 decoders 默许只要这几种,如果咱们想动态扩展decoder 那显然也是终究要添加到这个decoders中

终究判别是否支撑 解析 gif格式的 就在ByteBufferGifDecoder了

Glide 加载 Gif 动画的一些细节剖析

持续跟

Glide 加载 Gif 动画的一些细节剖析

终究会回调到DefaultImageHeaderParser这个类中的getType办法, 能够看出来 这儿是通过读文件头 来判别 是那种类型的

Glide 加载 Gif 动画的一些细节剖析

一起这儿咱们也能感知到,如果今后要新增 一种图片类型的展现 需求 改glide源码吗?显然是不需求的哈

把ImageHeaderParser 做一下咱们自己的完结就能够了

Glide 加载 Gif 动画的一些细节剖析

Glide decode Gif 文件的坑

在咱们对 文件识别到 是Gif今后,就能够 对文件做decode了

这儿一定要注意,decode这个办法 第一个参数是一个ByteBuffer ,坑点就在这儿, 能够简单了解为ByteBuffer 是对文件的一个笼统, 如果文件30多mb,那么bytebuffer 巨细便是这么多

Glide 加载 Gif 动画的一些细节剖析

持续跟:

Glide 加载 Gif 动画的一些细节剖析

这儿首先能够承认的是 在decode gif的时分 也是依据你 imageview的宽高来进行采样的 这点和一般的 图片解析没有任何差异

差异就在于

GifDecoder gifDecoder = gifDecoderFactory.build(provider, header, byteBuffer, sampleSize);

这行代码 生成了一个GifDecoder对象,

Glide 加载 Gif 动画的一些细节剖析

本质上是这个StandardGifDecoder

Glide 加载 Gif 动画的一些细节剖析

坑点来了

Glide 加载 Gif 动画的一些细节剖析

前面提到的ByteBuffer,原封不动的放到这个Decoder里,也便是说 你每加载一个gif文件,这个gif的文件巨细也都是常驻在内存中的

这个和加载一般图片文件 有很大的不同,一般文件 解析出来一个bitmap今后 图片文件是不会常驻在内存中的

这个便是为什么Glide 加载 gif图片时 很耗内存的 一个重要原因

Glide 加载 Gif 动画的一些细节剖析

这儿面最重要的一个办法便是这个getNextFrame办法, 这个办法便是读这个ByteBuffer区域,然后依据gif的编解码规矩,把 每一帧 转成 一个个Bitmap

Gif 怎样 播映

在这儿咱们能够看出来 decode 今后 咱们会创立出来一个要害的东西叫GifDrawable

Glide 加载 Gif 动画的一些细节剖析

这个GifDrawable 的几个要害参数, gifDecoder, imageview的 宽高 以及 还有一个 gif的第一帧

尽管主要干事的是GifDrawable 但是在回来的时分 回来的是这个GifDrawableResource

Glide 加载 Gif 动画的一些细节剖析

当咱们resource 准备好今后 就会触发 Gif的播映逻辑了

Glide 加载 Gif 动画的一些细节剖析

详细的跟一下代码

Glide 加载 Gif 动画的一些细节剖析

Glide 加载 Gif 动画的一些细节剖析

Glide 加载 Gif 动画的一些细节剖析

终究能够看出来,是在ImageViewTarget中 判别 咱们的resource 到底是不是动画类型 如果是的话 就直接调用start办法开端播映动画

咱们详细来看下GifDrawable 到底是个啥?

这东西首先是 完结了2个接口

Glide 加载 Gif 动画的一些细节剖析

Animatable的效果 就不说了, 这儿最要害的是这个FrameCallback的 接口,咱们后边再说

前面提到过 Imageviewtarget 判别如果是 动画类型 则会直接调用start

Glide 加载 Gif 动画的一些细节剖析

Glide 加载 Gif 动画的一些细节剖析

这儿会调用invalidate ,这个办法 会触发draw办法

Glide 加载 Gif 动画的一些细节剖析

恩 咱们能够看到draw办法 便是直接去decoder 那里去当前帧,然后直接drawbitmap的

那剩下的问题便是终究一个了,一个gif 有很多帧, 这儿是怎样做到 一帧一帧不停的播映的呢?

再看下代码 其实要害的就在于 state.frameLoader.subscribe这儿了

Glide 加载 Gif 动画的一些细节剖析

这个subscribe 的效果是啥?

便是把callback回调 放到这个loader里边来,这个callback 是啥? callback 便是咱们的GifDrawable

Glide 加载 Gif 动画的一些细节剖析

Glide 加载 Gif 动画的一些细节剖析

要害的就在于loadNexfFrame办法了,这儿是 连绵不断制作新的bitmap 新的一帧的要害

private void loadNextFrame() {
if (!isRunning || isLoadPending) {  
return;  
}  
if (startFromFirstFrame) {  
Preconditions.checkArgument(  
pendingTarget == null, "Pending target must be null when starting from the first frame");  
gifDecoder.resetFrameIndex();  
startFromFirstFrame = false;  
}  
// 是否存在未制作的帧数据?  
if (pendingTarget != null) {  
DelayTarget temp = pendingTarget;  
pendingTarget = null;  
onFrameReady(temp);  
return;  
}  
isLoadPending = true;  
// Get the delay before incrementing the pointer because the delay indicates the amount of time  
// we want to spend on the current frame.  
int delay = gifDecoder.getNextDelay();  
long targetTime = SystemClock.uptimeMillis() + delay;  
// 移动到下一帧  
gifDecoder.advance();  
// 创立下一个DelayTarget  
next = new DelayTarget(handler, gifDecoder.getCurrentFrameIndex(), targetTime);  
// 这儿终究仍是会调用到 Engine那里的  
requestBuilder.apply(signatureOf(getFrameSignature())).load(gifDecoder).into(next);  
}

怎样连绵不断制作新的一帧

最要害的便是下面这2个办法

Glide 加载 Gif 动画的一些细节剖析

咱们先看终究一句,这个代码很好了解,其实便是和你 直接调用glide 加载图片是相同的。 连调用方式都相同。

咱们首先要重视的是这个requestBuild是哪里来的

能够看下调用链

Glide 加载 Gif 动画的一些细节剖析

终究能够看到是通过GifFrameLoader的办法去结构出来的

Glide 加载 Gif 动画的一些细节剖析

这儿要注意的是, gif 解析出来的bitmap 是不运用任何缓存的,既不会在内存中缓存,也不会在磁盘中缓存

有人觉得古怪,啊?磁盘缓存都不必吗?对的,对于gif的原始图片来说,当然是默许缓存到磁盘中的,但是对于gif的每一帧的bitmap来说 不会运用任何缓存。

至于这儿的 useAnimationPool 其实这儿 便是用别的一个线程池去做编解码 不会和glide 的 网络恳求线程池 混用

搞清楚这个终究便是看下 这个target了,搞清楚target gif的 播映就差不多了

当glide把资源准备好今后 就回去回调这个target的 ready办法,能够看出来这个办法 里啥都没做, 便是发了一个handler的消息

Glide 加载 Gif 动画的一些细节剖析

这个handler 哪里来的?当然是自己创立的

Glide 加载 Gif 动画的一些细节剖析

看下消息处理

Glide 加载 Gif 动画的一些细节剖析

终究仍是走到了Loader的 onFrameReady办法中


@VisibleForTesting  
void onFrameReady(DelayTarget delayTarget) {  
if (onEveryFrameListener != null) {  
onEveryFrameListener.onFrameReady();  
}  
isLoadPending = false;  
if (isCleared) {  
handler.obtainMessage(FrameLoaderCallback.MSG_CLEAR, delayTarget).sendToTarget();  
return;  
}  
// If we're not running, notifying here will recycle the frame that we might currently be  
// showing, which breaks things (see #2526). We also can't discard this frame because we've  
// already incremented the frame pointer and can't decode the same frame again. Instead we'll  
// just hang on to this next frame until start() or clear() are called.  
if (!isRunning) {  
if (startFromFirstFrame) {  
handler.obtainMessage(FrameLoaderCallback.MSG_CLEAR, delayTarget).sendToTarget();  
} else {  
pendingTarget = delayTarget;  
}  
return;  
}  
if (delayTarget.getResource() != null) {  
recycleFirstFrame();  
DelayTarget previous = current;  
current = delayTarget;  
// The callbacks may unregister when onFrameReady is called, so iterate in reverse to avoid  
// concurrent modifications.  
for (int i = callbacks.size() - 1; i >= 0; i--) {  
FrameCallback cb = callbacks.get(i);  
// 要点便是在这儿 这个callback 其实便是咱们的GifDrawble  
cb.onFrameReady();  
}  
if (previous != null) {  
handler.obtainMessage(FrameLoaderCallback.MSG_CLEAR, previous).sendToTarget();  
}  
}  
loadNextFrame();  
}

这个办法 终究会回调到 咱们的GifDrawable中的ready办法 终究也是在这儿完结的制作

Glide 加载 Gif 动画的一些细节剖析

至此 整个glide 制作gif 动画的流程就完毕了

一些细节

有人可能会注意到 咱们第一次加载gif的时分 load 传的便是一个url ,然后解析的时分 是自己找适宜的解码器去解析

真正开端gif加载的时分load 传的是一个 decoder类型,这个是怎样解析的呢?

Glide 加载 Gif 动画的一些细节剖析

原来是用的 GifFrameResourceDecoder

那为什么glide 知道是用这个东西的?

肯定是有当地注册过了啊,

专门注册了Gif 帧解析的 流程

Glide 加载 Gif 动画的一些细节剖析

Glide 加载 Gif 动画的一些细节剖析

后续

通过前面的源码剖析,咱们知道了glide 加载gif的 大部分细节,也知道一些缺陷,后边还会介绍 更优异的gif播映方案, 终究会将两者结合起来~~~