IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Glide加载Gif的卡顿优化思路分析,美团架构师深入讲解Android开发 -> 正文阅读

[移动开发]Glide加载Gif的卡顿优化思路分析,美团架构师深入讲解Android开发

Glide( @NonNull Context context, /*.....*/) { //... List<ImageHeaderParser> imageHeaderParsers = registry.getImageHeaderParsers(); //.. GifDrawableBytesTranscoder gifDrawableBytesTranscoder = new GifDrawableBytesTranscoder(); //... registry //... /* GIFs */ .append( Registry.BUCKET_GIF, InputStream.class, GifDrawable.class, new StreamGifDecoder(imageHeaderParsers, byteBufferGifDecoder, arrayPool)) .append(Registry.BUCKET_GIF, ByteBuffer.class, GifDrawable.class, byteBufferGifDecoder) .append(GifDrawable.class, new GifDrawableEncoder()) /* GIF Frames */ // Compilation with Gradle requires the type to be specified for UnitModelLoader here. .append( GifDecoder.class, GifDecoder.class, UnitModelLoader.Factory.<GifDecoder>getInstance()) .append( Registry.BUCKET_BITMAP, GifDecoder.class, Bitmap.class, new GifFrameResourceDecoder(bitmapPool)) //... .register(GifDrawable.class, byte[].class, gifDrawableBytesTranscoder); ImageViewTargetFactory imageViewTargetFactory = new ImageViewTargetFactory(); //.... }


因此第一步可以发现Glide是通过创建StreamGifDecoder来解码Gif的InputStream流.

public class StreamGifDecoder implements ResourceDecoder<InputStream, GifDrawable> { @Override public Resource<GifDrawable> decode(@NonNull InputStream source, int width, int height, @NonNull Options options) throws IOException { // 1. 用一个byte数组来接收InputStream流 byte[] data = inputStreamToBytes(source); if (data == null) { return null; } // 2.使用ByteBuffer包装处理原始的数据流, //思考为什么用ByteBuffer呢? /** @link StandardGifDecoder#setData(); // Initialize the raw data buffer. rawData = buffer.asReadOnlyBuffer(); rawData.position(0); rawData.order(ByteOrder.LITTLE_ENDIAN); // 小端对齐.从低位到高位排序 */ ByteBuffer byteBuffer = ByteBuffer.wrap(data); return byteBufferDecoder.decode(byteBuffer, width, height, options); } }


具体细节如下:

*   使用byte\[\] 数组接收InputStream
*   然后在通过处理之后的byte\[\]交给ByteBufferGifDecoder进行下一阶段的处理工作(完善对InputStream的解码工作);

public class ByteBufferGifDecoder implements ResourceDecoder<ByteBuffer, GifDrawable> { //... @Override public GifDrawableResource decode(@NonNull ByteBuffer source, int width, int height, @NonNull Options options) { final GifHeaderParser parser = parserPool.obtain(source); try { return decode(source, width, height, parser, options); } finally { parserPool.release(parser); } } @Nullable private GifDrawableResource decode( ByteBuffer byteBuffer, int width, int height, GifHeaderParser parser, Options options) { long startTime = LogTime.getLogTime(); try { // 1.获取GIF头部信息 final GifHeader header = parser.parseHeader(); if (header.getNumFrames() <= 0 || header.getStatus() != GifDecoder.STATUS_OK) { // If we couldn't decode the GIF, we will end up with a frame count of 0. return null; } //2. 根据GIF的背景是否有透明通道(Alpha)来确定Bitmap的类型 Bitmap.Config config = options.get(GifOptions.DECODE_FORMAT) == DecodeFormat.PREFER_RGB_565 ? Bitmap.Config.RGB_565 : Bitmap.Config.ARGB_8888; //3.计算Bitmap的采样率 int sampleSize = getSampleSize(header, width, height); //4. 获取Gif数据的StandardGifDecoder====> 由静态内部类GifDecoderFactory GifDecoder gifDecoder = gifDecoderFactory.build(provider, header, byteBuffer, sampleSize); gifDecoder.setDefaultBitmapConfig(config); gifDecoder.advance(); //5.获取Gif数据的下一帧 Bitmap firstFrame = gifDecoder.getNextFrame(); if (firstFrame == null) { return null; } Transformation<Bitmap> unitTransformation = UnitTransformation.get(); //6.由Gif数据帧构建一个GifDrawable用来播放GIF帧的动画 GifDrawable gifDrawable = new GifDrawable(context, gifDecoder, unitTransformation, width, height, firstFrame); //7. 将GifDrawable包装成GifDrawableResource,用于维护GifDrawable的回收,以及播放动画的停止. return new GifDrawableResource(gifDrawable); } finally { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "Decoded GIF from stream in " + LogTime.getElapsedMillis(startTime)); } } } } @VisibleForTesting static class GifDecoderFactory { GifDecoder build(GifDecoder.BitmapProvider provider, GifHeader header, ByteBuffer data, int sampleSize) { //获取一个标准的Gif解码器,用于读取Gif帧并且将其绘制为Bitmap,供外界使用 return new StandardGifDecoder(provider, header, data, sampleSize); } }


小小的总结一下:

*   首先通过ByteBufferDecoder提取Gif的头部信息
*   根据Gif的头部信息获取其背景颜色,好设置Bitmap的Config选项
*   依然还是根据头信息计算出采样率
*   获取GIF的解码器StandardGifDecoder用于构建GIF帧输出为Bitmap供外界使用
*   构建GifDrawable(用于播放Gif动画)
*   构建GifDrawableResource(用于管理GifDrawable)

2)其次看Gif图像帧获取以及如何将图像帧注入到Bitmap中
-------------------------------

下面来看看Gif图像帧是如何被解码到Bitmap中的,请看StandardGifDecoder

public class StandardGifDecoder implements GifDecoder { private static final String TAG = StandardGifDecoder.class.getSimpleName(); //... // 由ByteBufferGifDecoder的decode方法可知,通过StandardGifDecoder获取Gif的下一帧数据,用于转换为Bitmap. @Nullable @Override public synchronized Bitmap getNextFrame() { //... // 根据Gif的头信息获取GIF当前帧的帧数据 GifFrame currentFrame = header.frames.get(framePointer); GifFrame previousFrame = null; int previousIndex = framePointer - 1; if (previousIndex >= 0) { previousFrame = header.frames.get(previousIndex); } // Set the appropriate color table. // 设置色表:用于设置像素透明度 lct == local color table ; gct == global color table;这里告诉我们的就是先局部后全局 act = currentFrame.lct != null ? currentFrame.lct : header.gct; if (act == null) { if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "No valid color table found for frame #" + framePointer); } // No color table defined. status = STATUS_FORMAT_ERROR; return null; } // Reset the transparent pixel in the color table // 重置色表中的像素的透明度 if (currentFrame.transparency) { // Prepare local copy of color table ("pct = act"), see #1068 System.arraycopy(act, 0, pct, 0, act.length); // Forget about act reference from shared header object, use copied version act = pct; // Set transparent color if specified. // 这里默认为黑色透明度 act[currentFrame.transIndex] = COLOR_TRANSPARENT_BLACK; } // Transfer pixel data to image. // 将像素数据转换为图像 return setPixels(currentFrame, previousFrame); } //... private Bitmap setPixels(GifFrame currentFrame, GifFrame previousFrame) { // Final location of blended pixels. // 存储上一帧的Bitmap像素数据 final int[] dest = mainScratch; // clear all pixels when meet first frame and drop prev image from last loop if (previousFrame == null) { if (previousImage != null) { // 回收上一帧的Bitmap bitmapProvider.release(previousImage); } previousImage = null; // 并且将Bitmap的像素填充黑色 Arrays.fill(dest, COLOR_TRANSPARENT_BLACK); } if (previousFrame != null && previousFrame.dispose == DISPOSAL_PREVIOUS && previousImage == null) { //上一帧数据为被废弃了,清空 Arrays.fill(dest, COLOR_TRANSPARENT_BLACK); } // fill in starting image contents based on last image's dispose code //1. 将上一帧的 数据注入到dest数组中 if (previousFrame != null && previousFrame.dispose > DISPOSAL_UNSPECIFIED) { if (previousFrame.dispose == DISPOSAL_BACKGROUND) { // Start with a canvas filled with the background color @ColorInt int c = COLOR_TRANSPARENT_BLACK; if (!currentFrame.transparency) { c = header.bgColor; if (currentFrame.lct != null && header.bgIndex == currentFrame.transIndex) { c = COLOR_TRANSPARENT_BLACK; } } else if (framePointer == 0) { isFirstFrameTransparent = true; } // The area used by the graphic must be restored to the background color. int downsampledIH = previousFrame.ih / sampleSize; int downsampledIY = previousFrame.iy / sampleSize; int downsampledIW = previousFrame.iw / sampleSize; int downsampledIX = previousFrame.ix / sampleSize; int topLeft = downsampledIY * downsampledWidth + downsampledIX; int bottomLeft = topLeft + downsampledIH * downsampledWidth; for (int left = topLeft; left < bottomLeft; left += downsampledWidth) { int right = left + downsampledIW; for (int pointer = left; pointer < right; pointer++) { dest[pointer] = c; } } } else if (previousFrame.dispose == DISPOSAL_PREVIOUS && previousImage != null) { // Start with the previous frame // 获取上一帧的Bitmap中的数据,并且将数据更新到dest中. previousImage.getPixels(dest, 0, downsampledWidth, 0, 0, downsampledWidth, downsampledHeight); } } // Decode pixels for this frame into the global pixels[] scratch. // 2. 解析当前帧的数据到dest中 decodeBitmapData(currentFrame); if (currentFrame.interlace || sampleSize != 1) { copyCopyIntoScratchRobust(currentFrame); } else { copyIntoScratchFast(currentFrame); } // Copy pixels into previous image //3.获取当前帧的数据dest,并且将数据存储到上一帧的image(Bitmap)中存储. if (savePrevious && (currentFrame.dispose == DISPOSAL_UNSPECIFIED || currentFrame.dispose == DISPOSAL_NONE)) { if (previousImage == null) { previousImage = getNextBitmap(); } previousImage.setPixels(dest, 0, downsampledWidth, 0, 0, downsampledWidth, downsampledHeight); } // Set pixels for current image. // 4.获取新的Bitmap,将dest中的数据拷贝到Bitmap,提供给GifDrawable使用. Bitmap result = getNextBitmap(); result.setPixels(dest, 0, downsampledWidth, 0, 0, downsampledWidth, downsampledHeight); return result; } }


看了上述代码流程,不够直观,下面画一张图,对比一下方便分析:

![](https://i.loli.net/2020/07/26/OAIjK5XrgJEcNti.png)

由上述图可知:

*   从上一帧的Bitmap中获取帧数据然后填充到dest数组
*   然后从这个数组获取帧数数据,填充到Bitmap中(第一次将Gif帧数据转换为preBitmap)
*   解析当前帧的数据到dest数组中,并且在将该数据保存在preBitmap中
*   从BitmapProvider(提供Bitmap的复用)中获取新的Bitmap,并且将当前帧解析的dest数组拷贝到Bitmap中,供外界使用

3)Glide借助GifDrawable来播放GIF动画
----------------------------

public class GifDrawable extends Drawable implements GifFrameLoader.FrameCallback, Animatable, Animatable2Compat { @Override public void start() { isStarted = true; resetLoopCount(); if (isVisible) { startRunning(); } } private void startRunning() { ...... if (state.frameLoader.getFrameCount() == 1) { invalidateSelf(); } else if (!isRunning) { isRunning = true; // 1. 调用了 GifFrameLoader 的 subscribe 方法 state.frameLoader.subscribe(this); invalidateSelf(); } } @Override public void onFrameReady() { ...... // 2. 执行绘制 invalidateSelf(); ...... } }


从GifDrawable实现的接口可以看出,其是一个Animatable的Drawable,因此GifDrawable可以支持播放GIF动画,还有一个重要的类就是GifFrameLoader,用来帮助GifDrawable实现GIF动画播放的调度.

GifDrawable的start方法是动画开始的入口,在该方法中将GifDrawable作为一个观察者注册到GifFrameLoader中,一旦GifFrameLoader触发了绘制,就会调用onFrameReady方法,然后通过调用invalidateSelf执行此次绘制.

来具体看看GifFrameLoader是如何执行动画的调度

class GifFrameLoader { //.. public interface FrameCallback { void onFrameReady(); } //.. void subscribe(FrameCallback frameCallback) { if (isCleared) { throw new IllegalStateException("Cannot subscribe to a cleared frame loader"); } if (callbacks.contains(frameCallback)) { throw new IllegalStateException("Cannot subscribe twice in a row"); } //判断观察者队列是否为空 boolean start = callbacks.isEmpty(); // 添加观察者 callbacks.add(frameCallback); // 不为空,执行GIF的绘制 if (start) { start(); } } private void start(){ if(isRunning){ return; } isRunning =true; isCleared=false; loadNextFrame(); } void unsubscribe(FrameCallback frameCallback) { callbacks.remove(frameCallback); if (callbacks.isEmpty()) { stop(); } } private void loadNextFrame() { //.. // 当前有没有被绘制的帧数据 if (pendingTarget != null) { DelayTarget temp = pendingTarget; pendingTarget = null; //直接调用onFrameReady 通知观察者绘制当前帧. onFrameReady(temp); return; } isLoadPending = true; //获取下一帧需要绘制的间隔时长 int delay = gifDecoder.getNextDelay(); long targetTime = SystemClock.uptimeMillis() + delay; // 将下一帧放置在最前,方便进行绘制.(位置) gifDecoder.advance(); //通过DelayTarget中的Handler创建一个延迟消息. next = new DelayTarget(handler, gifDecoder.getCurrentFrameIndex(), targetTime); // Glide的加载流程 ....with().load().into(); 在targetTime时,获取数据帧然后进行绘制. requestBuilder.apply(signatureOf(getFrameSignature())).load(gifDecoder).into(next); } @VisibleForTesting void onFrameReady(DelayTarget delayTarget) { //.... if (delayTarget.getResource() != null) { recycleFirstFrame(); DelayTarget previous = current; current = delayTarget; // 1. 回调给观察者,执行当前帧的绘制 for (int i = callbacks.size() - 1; i >= 0; i--) { FrameCallback cb = callbacks.get(i); cb.onFrameReady(); } if (previous != null) { handler.obtainMessage(FrameLoaderCallback.MSG_CLEAR, previous).sendToTarget(); } } //2. 继续加载GIF的下一帧 loadNextFrame(); } private class FrameLoaderCallback implements Handler.Callback { //.. @Override public boolean handleMessage(Message msg) { if (msg.what == MSG_DELAY) { GifFrameLoader.DelayTarget target = (DelayTarget) msg.obj; onFrameReady(target); return true; } else if (msg.what == MSG_CLEAR) { GifFrameLoader.DelayTarget target = (DelayTarget) msg.obj; requestManager.clear(target); } return false; } } @VisibleForTesting static class DelayTarget extends SimpleTarget<Bitmap> { //... @Override public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition<? super Bitmap> transition) { this.resource = resource; Message msg = handler.obtainMessage(FrameLoaderCallback.MSG_DELAY, this); //通过Handler发送延迟消息,将下一帧的绘制工作消息发送出去. handler.sendMessageAtTime(msg, targetTime); } } }


可以看到在onResourceReady方法中,通过Handler将FrameLoaderCallback.MSG\_DELAY消息在延迟了targetTime时候,投递到主线程的消息队列中执行.

class GifFrameLoader{ private class FrameLoaderCallback implements Handler.Callback { static final int MSG_DELAY = 1; static final int MSG_CLEAR = 2; @Synthetic FrameLoaderCallback() { } @Override public boolean handleMessage(Message msg) { if (msg.what == MSG_DELAY) { // 回调了 onFrameReady 通知 GifDrawable 绘制 GifFrameLoader.DelayTarget target = (DelayTarget) msg.obj; onFrameReady(target); return true; } else if (msg.what == MSG_CLEAR) { ...... } return false; } } @VisibleForTesting void onFrameReady(DelayTarget delayTarget){ //.... if (delayTarget.getResource() != null) { recycleFirstFrame(); DelayTarget previous = current; current = delayTarget; // 1. 回调观察者集合(GifDrawable), 执行 GIF 当前帧的绘制 for (int i = callbacks.size() - 1; i >= 0; i--) { FrameCallback cb = callbacks.get(i); cb.onFrameReady(); } if (previous != null) { handler.obtainMessage(FrameLoaderCallback.MSG_CLEAR, previous).sendToTarget(); } } // 2. 继续加载 GIF 的下一帧 loadNextFrame(); } }


上述的消息处理给出一个线索:绘制当前帧和加载下一帧是串行的,也就说其中任何一个环节时间把控不准都会影响Gif加载的卡顿问题.

Glide加载Gif卡顿的优化
===============

通过引入GIFLIB在native层解码GIF,这样一来内存消耗以及CPU的使用率都可以得到明显的降低和提升.其次通过FrameSequenceDrawable的双缓冲机制进行绘制GIF动画,这样就不需要在Java层的BitmapPool中创建多个Bitmap了.
 
  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-09-04 17:39:38  更:2021-09-04 17:39:57 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 13:24:41-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码