// 获取上一帧的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移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
学习路线+知识梳理
花了很长时间,就为了整理这张详细的知识路线脑图。当然由于时间有限、能力也都有限,毕竟嵌入式全体系实在太庞大了,包括我那做嵌入式的同学,也不可能什么都懂,有些东西可能没覆盖到,不足之处,还希望小伙伴们一起交流补充,一起完善进步。
这次就分享到这里吧,下篇见。
提升又不知道该从何学起的朋友,同时减轻大家的负担。**
[外链图片转存中…(img-4m5ze7yF-1711881207571)]
[外链图片转存中…(img-cUWF6FWb-1711881207572)]
[外链图片转存中…(img-dGUjVUCT-1711881207572)]
[外链图片转存中…(img-G8s6jgEy-1711881207573)]
[外链图片转存中…(img-1r5VelU1-1711881207574)]
[外链图片转存中…(img-1rGfzD3w-1711881207574)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
[外链图片转存中…(img-W5pcDOGK-1711881207574)]
学习路线+知识梳理
花了很长时间,就为了整理这张详细的知识路线脑图。当然由于时间有限、能力也都有限,毕竟嵌入式全体系实在太庞大了,包括我那做嵌入式的同学,也不可能什么都懂,有些东西可能没覆盖到,不足之处,还希望小伙伴们一起交流补充,一起完善进步。
这次就分享到这里吧,下篇见。