用Glide加载Gif导致的卡顿,说一下你的优化思路,高级Android开发面试解答

// 获取上一帧的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;

}

}

@VisibleForTesting

static class DelayTarget extends SimpleTarget {

//…

@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了.

具体看看FrameSequenceDrawable的双缓冲机制吧:

public class FrameSequenceDrawable extends Drawable implements Animatable,Runnable{

//…

public FrameSequenceDrawable(FrameSequence frameSequence,BitmapProvider bitmapProvider){

//…

final int width = frameSequence.getWidth();

final int height = frameSequence.getHeight();

//绘制前一帧的Bitmap

frontBitmap = acquireAndValidateBitmap(bitmapProvider,width,height);

//绘制下一帧的Bitmap

backBitmap = acquireAndValidateBitmap(bitmapProvider,

width,height);

//… 启动解码线程,用于处理后台解码Gif的人物

initializeDecodingThread();

}

}

从上述构造不难发现通过BitmapProvider创建了两个Bitmap;

1.GIF动画的绘制调度

public class FrameSequenceDrawable extends Drawable implements Animatable,Runnable{

@Override

public void start(){

if(!isRunning){

synchronized(mLock){

//…

if(mState == STATE_SCHEDULED){

return;

}

//.执行一次解码操作

scheduleDecodeLocked();

}

}

}

private void scheduleDecodeLocked(){

mState = STATE_SCHEDULED;

sDecodingThreadHandler.post(mDecodeRunnable);

}

private final Runnable mDecodeRunnable = new Runnable(){

@Override

public void run(){

//…

try{

//1.解码下一帧

invalidateTimeMs = mDecoder.getFrame(nextFrame,bitmap,lastFrame);

}catch(Exception e){

//…

}

if (invalidateTimeMs < MIN_DELAY_MS) {

invalidateTimeMs = DEFAULT_DELAY_MS;

}

boolean schedule = false;

Bitmap bitmapToRelease = null;

//加锁

synchronized(mLock){

if(mDestroyed){

bitmapToRelease = mBackBitmap;

mBackBitmap =null;

}else if (mNextFrameToDecode >=0 && mState ==STATE_DECODING){

// 当前是解码状态,并且下一帧要被解码的数据为0 说明下一帧解码完成.等待绘制

schedule = true;

// 间隔的绘制时间

mNextSwap = exceptionDuringDecode ? Long.MAX_VALUE:invalidateTimeMs+mLastSwap;

mState= STATE_WAITING_TO_SWAP;

}

}

if (schedule) {

// 2. 在mNextSwap的时候,进行绘制调度

scheduleSelf(FrameSequenceDrawable.this,mNextSwap);

}

}

@Override

public void run(){

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
img

学习路线+知识梳理

花了很长时间,就为了整理这张详细的知识路线脑图。当然由于时间有限、能力也都有限,毕竟嵌入式全体系实在太庞大了,包括我那做嵌入式的同学,也不可能什么都懂,有些东西可能没覆盖到,不足之处,还希望小伙伴们一起交流补充,一起完善进步。

这次就分享到这里吧,下篇见

提升又不知道该从何学起的朋友,同时减轻大家的负担。**
[外链图片转存中…(img-4m5ze7yF-1711881207571)]
[外链图片转存中…(img-cUWF6FWb-1711881207572)]
[外链图片转存中…(img-dGUjVUCT-1711881207572)]
[外链图片转存中…(img-G8s6jgEy-1711881207573)]
[外链图片转存中…(img-1r5VelU1-1711881207574)]
[外链图片转存中…(img-1rGfzD3w-1711881207574)]
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
[外链图片转存中…(img-W5pcDOGK-1711881207574)]

学习路线+知识梳理

花了很长时间,就为了整理这张详细的知识路线脑图。当然由于时间有限、能力也都有限,毕竟嵌入式全体系实在太庞大了,包括我那做嵌入式的同学,也不可能什么都懂,有些东西可能没覆盖到,不足之处,还希望小伙伴们一起交流补充,一起完善进步。

这次就分享到这里吧,下篇见

本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值