blog

Glide読み込み Gifラグ最適化アイデア分析

グライド画像フレームワークは、直接GIF画像をロードすることができますが、銀行協力プロジェクトを行うには、ページを出荷する必要があるため、GIF画像をロードする必要がありますが、GIF画像をロードする...

May 20, 2020 · 16 min. read
シェア

問題: Gif読み込みのためのグライド最適化のアイデア

日付: 2020-07-26 10:08

カテゴリ: NDK

タグ: グライド

: GifSampleForGlide

Glide 4 に基づいています。.9.0バージョン解析

はじめに

Glideイメージフレームワークは、直接GIFイメージをロードすることができますが、銀行協力プロジェクトを行うには、ページを出荷する必要があるため、GIFイメージをロードする必要がありますが、Glideフレームワークの使用で見つかったGIFイメージをロードするには、明らかに遅延があることがわかりました。

Gifイメージを読み込むグライドのソースコードをチェックした後に知っている:Gifイメージフレームの読み込みでグライドは、前のフレームのレンダリングと次のフレームの準備がシリアルであり、このプロセスは、次のフレームの準備がGif間隔の再生時間以上である場合、それは再生の遅れが発生します。また、このプロセスは、StandardGifDecoderは、前のフレームのデータを保持するだけで、フレームを描画するために現在の必要性を取得するたびに新しいBitmap(これは新しいBitmapオブジェクトであることに注意してください)を取得するBitmapPoolからされるので、Gifをロードするプロセスは、Glideはまた、メモリ消費量が高くなるにつながる少なくとも2つのBitmapが必要です。これはまた、高いメモリ消費につながります。

ここでは、GlideがGifを読み込む方法と、ラグの最適化方法について説明します。

GlideGif の読み込み原理

この記事は以下のキーワードを中心に書かれています。

  • Glide
  • StreamGifDecoder
  • ByteBufferGifDecoder
  • StandardGifDecoder
  • GifDrawable

1)まず、Gif関連のデコーダーを紹介します。

Gifに関する情報はGlideの構成にあります。


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

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. 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);
 }
}

詳細は以下の通り。

  • バイト[] 配列は InputStream
  • 処理後のbyte[]は、次の処理のためにByteBufferGifDecoderに渡されます。

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の背景が透明チャンネルを持つかどうかに応じて、Bitmapのタイプを決定する。
 Bitmap.Config config = options.get(GifOptions.DECODE_FORMAT) == DecodeFormat.PREFER_RGB_565
 ? Bitmap.Config.RGB_565 : Bitmap.Config.ARGB_8888;
 //3.ビットマップのサンプリングレートを計算する
 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.GifDrawableはGifフレームから構築され、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フレームを読み込み、外部用にBitmapとして描画する標準的なGifデコーダーを入手する。
 return new StandardGifDecoder(provider, header, data, sampleSize);
 }
 }

少し要約すると

  • まず、ByteBufferDecoderによってGifのヘッダー情報を抽出します。
  • ヘッダ情報に基づいて Gif の背景色を取得し、Bitmap の Config オプションを設定します。
  • まだヘッダー情報からサンプリングレートを計算しています
  • GIF デコーダ StandardGifDecoder を使用して GIF フレームを作成し、Bitmap として出力します。
  • GifDrawableの構築(Gifアニメ再生用)
  • GifDrawableResource の構築

GifイメージフレームがどのようにBitmapにデコードされるか、StandardGifDecoderを見てみましょう。


public class StandardGifDecoder implements GifDecoder {
 private static final String TAG = StandardGifDecoder.class.getSimpleName();
 //...
 // ByteBufferGifDecoderデコード・メソッドによって、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) {
 // 前のフレームのビットマップを再利用する
 bitmapProvider.release(previousImage);
 }
 previousImage = null;
 // そして、ビットマップのピクセルを黒で塗りつぶす。
 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 を取得し、それを前のフレームのイメージに格納する。.
 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に埋めます。
  • 現在のフレームのデータを dest 配列にパースし、preBitmap に保存します。
  • BitmapProvider(Bitmapの再利用を提供する)から新しいBitmapを取得し、現在のフレームの解析済みdest配列を外部用のBitmapにコピーします。

3) 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を見ることができる、それはアニメータブルDrawableですので、GifDrawableは、GIFアニメーションの再生をサポートすることができます重要なクラスがあるGifFrameLoaderは、GifDrawable GIFアニメーションの再生スケジューリングを支援するために使用されます。

GifDrawableのstartメソッドはアニメーション開始のエントリーポイントで、このメソッドではGifFrameLoaderに登録されたオブザーバーとしてのGifDrawableが描画のトリガーを引くと、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);
 // nullではなく、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);
 // GlideFrameSequenceDrawableの読み込み処理 ....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.sendMessageAtTime(msg, 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. 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の読み込みの遅れに影響します。

GlideGif の読み込み遅延の最適化

GIFをデコードするためにネイティブレイヤーにGIFLIBを導入することで、メモリ消費量とCPU使用率を大幅に削減し、改善することができます。第二に、GIFアニメーションを描画するためのFrameSequenceDrawableダブルバッファ機構により、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();
 //前のフレームのビットマップを描画する
 frontBitmap = acquireAndValidateBitmap(bitmapProvider,width,height);
 //次のフレームのビットマップを描画する
 backBitmap = acquireAndValidateBitmap(bitmapProvider,
 width,height);
 //.. デコード・スレッドを開始し、バックグラウンドでGif文字のデコードを処理する。
 initializeDecodingThread();
 }
}

上記の構造から、BitmapProviderを通して2つの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(){
 boolean invalidate = false;
 synchronized(mLock){
 if (mNextFrameToDecode > 0 && mState == STATE_WAITING_TO_SWAP) {
 invalidate =true
 ;
 }
 }
 if (invalidate) {
 //3. デコードされたデータを描画する
 invalidateSelf();
 }
 }
 }
}

上記のコードから、startメソッドがデコード操作をトリガーしてデコードが完了し、指定された時間にscheduleSelfを呼び出して描画を実行することがわかります。

2. GIF描画とダブルバッファリングの役割


public class FrameSequenceDrawable extends Drawable implements Animatable , Runnable{
 @Override
 public void draw(@NonNull Canvas canvas){
 synchronized(mLock){
 checkDestroyLocked();
 if (mState == STATE_WAITING_TO_SWAP) {
 if (mNextSwap - SystemClock.uptimeMillis()<=0) {
 mState = STATE_READY_TO_SWAP;
 }
 
 }
 if (isRunning() && mState == STATE_READY_TO_SWAP) {
 //1.次のフレームのBitmapを前のフレームのBitmapに割り当てる。
 Bitmap temp = mBackBitmap;
 mBackBitmap = mFrontBitmap;
 mFrontBitmap = temp;
 //2. 上記のステップが完了すると、デコード・スレッドに次のデコード処理を続行するよう通知される。
 if (continueLooping) {
 scheduleDecodeLocked();
 }else{
 scheduleSelf(mFinishedCallbackRunnable,0);
 }
 }
 }
 if (mCircleMaskEnabled) {
 //...
 }else{
 //3.現在のフレームを描画する
 mPaint.setShader(null);
 canvas.drawBitmap(mFrontBitmap,mSrcRect,getBounds(),mPaint);
 }
 }
}

FrameSequenceDrawableのdrawメソッドでは、mFrontBitmapとmBackBitmapを使って置換を完了させ、すぐに次のフレームをデコードするようにデコードスレッドに通知することで、次のフレームの取得と現在のフレームの描画がほぼ同時に行われるようにしています。

概要

上記の業務プロセスを理解・分析することで、以下のような結論が導き出されます。

1、GIFLIB +ダブルバッファ実装の使用は、2つのビットマップを作成するだけで、メモリ消費量は非常に安定しています。

2、Glideのネイティブロードに比べて、あまりにも大きなGIFイメージをロードする場合、BitmapPoolの利用可能なサイズ以上、または直接Bitmapを作成します。

3、GIFLIBの使用は、直接GIFデータのデコードのネイティブ層では、Glideのこの点は、効率とメモリ消費量がより有利です。

4、Glideは、現在のフレームデータと次のフレームデータをシリアルに構築しますが、FrameSequenceDrawableは、ダブルバッファとデコードサブスレッドを使用して、前のフレームと次のフレームデータのシームレスなほぼ同期完了を実現します。

Read next

JVMの構造と各部の機能を詳しく解説する

JDKは、JavaプログラマーがJavaプログラムのコンパイルやデバッグを行うために一般的に使用する開発パッケージです。 JREとはJava Runtime Environment(Java実行環境)のことで、つまり、書かれたプログラムを実行するためにはJREが必要です。 JVMはJava Virtual Machineの略語で、バイトコードを特定のマシンコードに解釈して実行する役割を担っています。

May 20, 2020 · 3 min read