2024年Android最全Glide加载Gif的卡顿优化思路分析,2024年最新android基础面试题

最后笔者收集整理了一份Flutter高级入门进阶资料PDF

以下是资料目录和内容部分截图



里面包括详细的知识点讲解分析,带你一个星期入门Flutter。还有130个进阶学习项目实战视频教程,让你秒变大前端。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

//…
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.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; } }

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

由上述图可知:

  • 从上一帧的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;
}

最后

今天关于面试的分享就到这里,还是那句话,有些东西你不仅要懂,而且要能够很好地表达出来,能够让面试官认可你的理解,例如Handler机制,这个是面试必问之题。有些晦涩的点,或许它只活在面试当中,实际工作当中你压根不会用到它,但是你要知道它是什么东西。

最后在这里小编分享一份自己收录整理上述技术体系图相关的几十套腾讯、头条、阿里、美团等公司2021年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

还有 高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

【Android核心高级技术PDF文档,BAT大厂面试真题解析】

【算法合集】

【延伸Android必备知识点】

【Android部分高级架构视频学习资源】

Android精讲视频领取学习后更加是如虎添翼!进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

Android精讲视频领取学习后更加是如虎添翼!进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值