GifDrawable
一、整体流程概览
在Glide中播放GIF图像的核心流程如下:
- GIF资源识别 :Glide通过
GifResourceDecoder
识别GIF文件格式 - 资源解码 :
GifDrawable
和GifFrameLoader
负责GIF帧的解码管理 - 帧调度 :
GifFrameLoader
实现精准帧调度 - 动画播放 :
GifDrawable
将动画帧展示在View上 - 资源回收:结合Glide生命周期自动回收资源
二、核心源码分析
1. GifDrawable类结构
GifDrawable
继承关系如下:
java
public class GifDrawable
extends Drawable
implements Animatable, Animatable2Compat, GifFrameLoader.FrameCallback
- 实现了
Drawable
:可以在任何支持Drawable的View上显示 Animatable
接口:提供start()/stop()
等动画控制方法Animatable2Compat
:增强的生命周期感知动画FrameCallback
:接收帧准备好的回调
2. GIF帧调度机制
核心类GifFrameLoader
处理帧调度:
arduino
public class GifFrameLoader {
// 关键成员
private final FrameCallback callback; // GifDrawable实现的回调接口
private final GifDecoder gifDecoder; // GIF解码器
private final Handler handler; // 主线程Handler
private final BitmapPool bitmapPool; // Glide的Bitmap池
// 帧调度机制
public void getNextFrame() {
long targetTime = SystemClock.uptimeMillis() + currentFrame.getDelay();
handler.postAtTime(new LoadNextFrame(), targetTime);
}
}
3. GifDrawable与帧交互的关键方法
3.1. 帧准备回调
当帧准备好时,GifFrameLoader
通过回调通知GifDrawable
:
scss
// GifDrawable.java
@Override
public void onFrameReady(int frameIndex) {
// 1. 更新当前帧的Bitmap
setCurrentFrame();
// 2. 重绘自身触发View更新
invalidateSelf();
// 3. 调度下一帧
frameLoader.getNextFrame();
}
3.2. 动画控制方法
scss
// GifDrawable.java
@Override
public void start() {
if (!isRunning) {
// 注册动画生命周期
registerAnimationCallback();
// 开始第一帧调度
frameLoader.getNextFrame();
isRunning = true;
}
}
@Override
public void stop() {
if (isRunning) {
// 停止所有帧调度
frameLoader.clear();
isRunning = false;
// 取消注册
unregisterAnimationCallback();
}
}
3.3. 与View的渲染绑定
scss
// GifDrawable.java
@Override
public void draw(Canvas canvas) {
if (currentFrame != null) {
// 绘制当前帧
canvas.drawBitmap(currentFrame, srcRect, destRect, paint);
// 检查是否需应用透明优化
if (applyGravity) {
calculateDestRect();
}
}
}
4. GIF解码流程
GifDecoder
负责GIF帧数据的解码:
csharp
public interface GifDecoder {
// 获取下一帧图像
Bitmap getNextFrame();
// 获取帧延迟时间
int getDelay();
// 获取总帧数
int getFrameCount();
}
实现类StandardGifDecoder
的简化解码流程:
arduino
// StandardGifDecoder.java
public Bitmap getNextFrame() {
// 1. 读取下一帧数据
// 2. 计算解码参数
// 3. 使用BitmapPool获取Bitmap内存
bitmap = bitmapPool.get(width, height, Bitmap.Config.ARGB_8888);
// 4. 渲染到Bitmap
destPixels = bitmap.getPixels();
// ...渲染操作
return bitmap;
}
5. 资源管理机制
5.1. 生命周期绑定
GifDrawable
实现Animatable2Compat
以便绑定到View的生命周期:
csharp
// GifDrawable.java
private void registerAnimationCallback() {
// 绑定到View
if (this.callback != null && ViewCompat.isAttachedToWindow(view)) {
animatable2Compat.registerAnimationCallback(callback);
}
}
5.2. 资源回收
与Glide的生命周期自动回收系统集成:
scss
// GifDrawable.java
@Override
public void recycle() {
// 1. 清空当前帧
currentFrame = null;
// 2. 回收解码器资源
frameLoader.clear();
// 3. 释放位图引用
frameLoader.onFrameCleared();
// 4. 取消所有回调
unregisterAnimationCallback();
}
三、Glide加载GIF的完整流程
- 发起请求 :
Glide.with(context) .asGif() .load(url) .into(imageView);
- 解码识别 : -
StreamGifDecoder
识别输入流是否为GIF格式 - 创建GifResourceDecoder
- 资源封装 : - 创建
GifDrawable
实例 - 初始化GifFrameLoader
- 创建GifDecoder
(默认StandardGifDecoder
) - 视图绑定 : - 在
GifDrawable
目标View的onLoadStarted()
中调用start()
less
// GifDrawableImageViewTarget.java
@Override protected void onResourceReady(@NonNull GifDrawable resource) {
// 开始动画播放
resource.start();
}
- 帧调度流程 : -
GifFrameLoader
请求第1帧 -GifDecoder
解码帧 - 通过回调onFrameReady()
更新Drawable - 触发视图重绘 - 调度下一帧 - 生命周期结束 : - Glide调用
GifDrawable
的clear()
- 停止动画 - 回收所有帧资源 - 释放Bitmap内存
四、关键技术原理
1. 帧调度算法:精确帧间隔控制
关键代码实现:
java
// GifFrameLoader.java
private class LoadNextFrame implements Runnable {
@Override
public void run() {
// 1. 请求下一帧
loadNextFrame();
// 2. 计算下一帧时间点
long nextFrameTime = currentFrame.getDelay() + SystemClock.uptimeMillis();
// 3. 通过Handler精准调度
handler.postAtTime(this, nextFrameTime);
}
}
2. 内存优化策略
GifDrawable
采用多层优化减少内存占用:
优化点 | 实现机制 | 效果 |
---|---|---|
Bitmap池 | 通过BitmapPool 复用Bitmap |
减少对象创建/GC开销 |
帧复用 | 缓存常用帧 | 减少解码次数 |
惰性加载 | 按需解码帧 | 降低瞬时内存峰值 |
差分帧 | 仅解码变化部分 | 降低解码复杂度 |
3. 生命周期感知
集成Glide生命周期管理系统:
java
// GifDrawable.java
public void setVisible(boolean visible, boolean restart) {
super.setVisible(visible, restart);
if (visible) {
startIfNeeded();
} else {
stop();
}
}
绑定到View生命周期:
java
public void registerAnimationCallback(@NonNull AnimationCallback callback) {
if (animatable2Compat != null) {
animatable2Compat.registerAnimationCallback(callback);
}
}
五、实际使用优化建议
1. 精准控制策略
java
Glide.with(context)
.asGif()
.load(url)
.listener(new RequestListener<GifDrawable>() {
@Override
public boolean onResourceReady(GifDrawable resource, ...) {
// 1. 获取帧数
int frameCount = resource.getFrameCount();
// 2. 获取帧时长
for (int i = 0; i < frameCount; i++) {
int delay = resource.getFrameDuration(i);
Log.d("GIF", "Frame $i delay: " + delay + "ms");
}
// 3. 开始播放
resource.start();
// 4. 监听循环结束
resource.registerAnimationCallback(new AnimationCallback() {
@Override
public void onAnimationEnd(Drawable drawable) {
// 循环结束处理
}
});
return false;
}
})
.into(imageView);
2. 性能优化技巧
- 减小GIF尺寸:
java
.override(300, 300) // 限制解码尺寸
- 控制播放次数:
ini
// 自定义Decoder设置循环次数
GifDrawable drawable = resource.mutate(); drawable.setLoopCount(3);
- 内存监控:
java
// 监控内存占用
Runtime runtime = Runtime.getRuntime();
long usedMemory = runtime.totalMemory() - runtime.freeMemory();
- 暂停恢复策略:
java
// 根据App状态控制动画
@Override protected void onPause() {
gifDrawable.stop();
}
@Override protected void onResume() {
if (!gifDrawable.isRunning()) {
gifDrawable.start();
}
}