Glide加載Gif的卡頓優化思路分析

前言

Glide圖片框架是可以直接加載GIF圖的,但是做一個銀行合作項目的時候,由於有需要出貨頁面需要加載一個GIF圖,但是發現在使用Glide框架加載Gif圖片,明顯發現有延遲.

經過查看glide加載Gif圖片的源碼得知:Glide在加載Gif的圖片幀的時候,上一幀的渲染以及下一幀的準備是串行的,這個過程中,如果出現下一幀的準備階段時間超過了Gif間隔播放的時長,就會造成播放卡頓.而且此過程中,StandardGifDecoder只保留上一幀的數據,每次獲取當前需要繪製的幀的時候都會從BitmapPool中獲取新的Bitmap(注意,這是一個新的Bitmap對象),因此加載Gif過程中,Glide至少需要兩個Bitmap.這也就導致內存會消耗的過高.

下面就來研究一下Glide是如何加載Gif,以及如何進行卡頓的優化了:

Glide加載Gif原理初探

本文圍繞以下關鍵字來介紹

  • Glide
  • StreamGifDecoder
  • ByteBufferGifDecoder
  • StandardGifDecoder
  • GifDrawable

1)首先來介紹一下Gif相關的解碼器

Glide的構造中可以找到Gif的相關信息.


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();
    //....
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

因此第一步可以發現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);
  }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

具體細節如下:

  • 使用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);
    }
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68

小小的總結一下:

  • 首先通過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;
  }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135

看了上述代碼流程,不夠直觀,下面畫一張圖,對比一下方便分析:

]]

由上述圖可知:

  • 從上一幀的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();
    ......
  }
  
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

從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);
    }
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117

可以看到在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();
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

上述的消息處理給出一個線索:繪製當前幀和加載下一幀是串行的,也就說其中任何一個環節時間把控不準都會影響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();
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

從上述構造不難發現通過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(){
			boolean invalidate = false;
			synchronized(mLock){
				if (mNextFrameToDecode > 0  && mState == STATE_WAITING_TO_SWAP) {
					invalidate =true
					;
				}
			}
			if (invalidate) {
				//3. 繪製解碼的數據
				invalidateSelf();
			}
		}
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76

從上述代碼中可以看到start方法會觸發一次解碼操作,解碼完成之後,通過調用scheduleSelf在指定的時間內執行繪製,Glide加載Gif也是差不多這樣的.

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(mBackBitmap)賦值爲上一幀的Bitmap(mFrontBitmap)
				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);
		}

	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

在FrameSequenceDrawable的draw方法中通過mFrontBitmap 和 mBackBitmap完成替換,然後立即通知解碼線程解碼下一幀數據,這樣保證了獲取下一幀和繪製當前當前幀近似同時進行.

]

總結

通過了解和分析上述操作流程,我們可以得出以下結論:

1、使用GIFLIB+雙緩衝的實現,只會創建兩個Bitmap,並且內存消耗非常之穩定

2、相比Glide的原生加載,當加載過大的GIF圖時,超過了BitmapPool的可用大小,還是會直接創建Bitmap的.

3、使用GIFLIB是直接在native層對GIF數據進行解碼的,這一點對Glide來說,效率和內存消耗情況都比較佔優.

4、Glide構建當前幀數據和下一幀數據是串行的,而FrameSequenceDrawable則是利用了雙緩衝以及解碼子線程來實現近似同步的完成上一幀和下一幀數據的無縫銜接的.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章