Glide(四):強力なピクチャキャッシュプールと多重化メカニズム

56827 ワード

Glideには強力なGlideキャッシュポリシーとGlide多重化メカニズムがあり、前に失敗したことがあるので、整理してまとめます.
文書ディレクトリ
  • Glideキャッシュポリシー
  • Glide多重化機構
  • 1. キャッシュと多重化メカニズムの違いと役割
  • 2. 原理は何ですか.
  • (1)メモリ消費量
  • (2)inMutable
  • (3)Bitmap多重使用条件
  • (4)inMutable、inBitmap使用
  • (5)Lruアルゴリズム
  • 3.ソース分析
  • DownSampler
  • LruBitmapPool
  • SizeConfigStrategy
  • はいつ多重プールに加入しますか?
  • はいつ多重プールから取り出しますか?
  • はいつ多重プールから削除されますか?
  • まとめ
  • 参照
  • Glideキャッシュポリシー
    Glideは、弱参照キャッシュ、LruCacheキャッシュ、DiskLruCacheキャッシュ、およびネットワークロードの3つのキャッシュポリシーを使用しています.
    デフォルトでは、Glideは新しいピクチャリクエストを開始する前に、次のマルチレベルのキャッシュをチェックします:アクティブリソース(Active Resources)-このピクチャを表示している別のViewがありますか?メモリキャッシュ(Memory cache)-この画像は最近ロードされ、メモリに存在しますか?≪リソース・タイプ|Resource Type|Eas≫-画像が復号化され、変換され、ディスク・キャッシュに書き込まれたことがありますか?データ・ソース(Data)-画像を構築するリソースは、ファイル・キャッシュに書き込まれたことがありますか?最初の2つのステップでは、画像がメモリにあるかどうかを確認し、そうであれば直接画像を返します.次の2つのステップでは、画像がディスク上にあるかどうかを確認し、画像を迅速に非同期で返すことができます.4つのステップで画像が見つからない場合、Glideは元のリソースに戻ってデータ(元のファイル、Uri、Urlなど)を取り戻します.
    Glide多重化機構
    Glideのキャッシュメカニズム以外にも強力な多重化メカニズムがあります
    1.キャッシュと多重化メカニズムの違いと役割
    簡単に言えば、キャッシュはデータを格納することであり、次回必要に応じてデータを再ロードする必要はなく、直接持ってきてすぐに使用し、ロード速度を速め、同じデータの占有空間を避け、メモリの占有量を低減する役割を果たす.
    多重化の意味は再利用で、すでに使用する必要のないデータ空間を再利用することであり、彼の役割は頻繁にメモリを申請することを避けることであり、OOMを避けることであり、短時間でメモリの解放を迅速に申請するため、GCがタイムリーではないため、短時間で十分な空間を回収することができず、OOMを招く可能性がある.したがって、多重化の役割はメモリのジッタを減らすことです.
    2.原理は何ですか
    (1)メモリ使用量
    glideがbitmapを多重化する方法を説明する前に、bitmapのメモリ使用量の問題を理解します.
    Android 3.0より前にBitmapのメモリ割り当ては2つの部分に分けられ,一部はDalvikのVMスタックに割り当てられ,画素データのメモリはNativeスタックに割り当てられた.
    Android 3に着きました0以降、BitmapのメモリはVMスタックにすべて割り当てられます.bitmapメモリを手動で解放する必要がないことを意味し、gcメモリ管理メカニズムが管理を支援します.
    (2)inMutable
    inMutableはglideが多重化できる基石であり、bitmapFactoryが提供するパラメータであり、このbitmapが可変であり、多重化をサポートしていることを示す.
    Bitmapはandroidの中で最もOOMをもたらしやすい元凶の一つであり,Bitmapの解析パラメータではBitmapFactory.Optionsには、inMutable、inBitmapの2つのプロパティがあります.
    詳細は前の記事「bitmap疑惑」を参考に
    Bitmapの多重化を行う場合は、inMutableをtrue、inBitmapを既存のBitmapに設定する必要があります.
    多重化とは、recyleを用意しないbitmapを廃棄し、再び持ってきて使うことを意味します.
    inBitmapパラメータを使用する前に、Bitmapオブジェクトを作成するたびにメモリが割り当てられますが、inBitmapを使用すると、Bitmapのメモリは再利用されます.
    (3)Bitmap多重使用条件
    Android 4.4以前は、同じサイズのbitmapのみがサポートされており、inSampleSizeは1であり、jpegまたはpng形式を採用する必要があります.
    Android 4.4以降は、多重化されたbitmapサイズが新しいbitmapよりも大きく、簡単には大きな図が小さな図に多重化できるという制限しかありません.
    (4)inMutable、inBitmap使用
    BitmapFactory.Options largeOption = new BitmapFactory.Options();
    largeOption.inMutable = true; //   inMutable
    Bitmap largeBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.large, largeOption)
    
    BitmapFactory.Options smallOption = new BitmapFactory.Options();
    smallOption.inBitmap = largeBitmap; //   inBitmap    Bitmap
    Bitmap smallBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.small, smallOption);
    

    (5)Lruアルゴリズム
    使用されなくなったbitmapを保存すると、頻繁にメモリを申請することを回避し、メモリのジッタを減らすことができるが、使用されなくなったbitmapをメモリで記憶することもできるため、GlideはLRUアルゴリズムを使用し、LruCacheのように最大の空間制限がある.
    3.ソース分析
    本当に解析したのはDownSamplerクラスです
    DownSampler
    // DownSampler
    public Resource<Bitmap> decode(InputStream is, int requestedWidth, int requestedHeight,
          Options options, DecodeCallbacks callbacks) throws IOException {
    
        byte[] bytesForOptions = byteArrayPool.get(ArrayPool.STANDARD_BUFFER_SIZE_BYTES, byte[].class);
        BitmapFactory.Options bitmapFactoryOptions = getDefaultOptions();
        bitmapFactoryOptions.inTempStorage = bytesForOptions;
    
        DecodeFormat decodeFormat = options.get(DECODE_FORMAT);
        DownsampleStrategy downsampleStrategy = options.get(DOWNSAMPLE_STRATEGY);
        boolean fixBitmapToRequestedDimensions = options.get(FIX_BITMAP_SIZE_TO_REQUESTED_DIMENSIONS);
        boolean isHardwareConfigAllowed =
          options.get(ALLOW_HARDWARE_CONFIG) != null && options.get(ALLOW_HARDWARE_CONFIG);
        if (decodeFormat == DecodeFormat.PREFER_ARGB_8888_DISALLOW_HARDWARE) {
          isHardwareConfigAllowed = false;
        }
    
        try {
          Bitmap result = decodeFromWrappedStreams(is, bitmapFactoryOptions,
              downsampleStrategy, decodeFormat, isHardwareConfigAllowed, requestedWidth,
              requestedHeight, fixBitmapToRequestedDimensions, callbacks);
          return BitmapResource.obtain(result, bitmapPool);
        } finally {
          releaseOptions(bitmapFactoryOptions);
          byteArrayPool.put(bytesForOptions);
        }
      }
    

    DownSamplerは実際の解析ロード画像、過負荷プロセスを担当しています.
  • inTempStorage

  • inTempStorageはbitmap解析のパラメータで、bufferを持ち込み、一時ファイルを作成し、画像を格納する中間キャッシュ空間です.
     BitmapFactory.Options bitmapFactoryOptions = getDefaultOptions();
    
     bitmapFactoryOptions.inTempStorage = bytesForOptions;
       
     byteArrayPool.put(bytesForOptions);
    
  • getDefaultOptions()

  • GlideはどこでinMutableをtrueに設定し、getDefaultOptions()に承諾します.
    デフォルトのbitmap解析パラメータを取得すると、デフォルトの設定でinMutableがtrueに設定されていることがわかります.
      private static synchronized BitmapFactory.Options getDefaultOptions() {
        BitmapFactory.Options decodeBitmapOptions;
        synchronized (OPTIONS_QUEUE) {
          decodeBitmapOptions = OPTIONS_QUEUE.poll();
        }
        if (decodeBitmapOptions == null) {
          decodeBitmapOptions = new BitmapFactory.Options();
          resetOptions(decodeBitmapOptions);
        }
    
        return decodeBitmapOptions;
      }
      
      private static void resetOptions(BitmapFactory.Options decodeBitmapOptions) {
        decodeBitmapOptions.inTempStorage = null;
        decodeBitmapOptions.inDither = false;
        decodeBitmapOptions.inScaled = false;
        decodeBitmapOptions.inSampleSize = 1;
        decodeBitmapOptions.inPreferredConfig = null;
        decodeBitmapOptions.inJustDecodeBounds = false;
        decodeBitmapOptions.inDensity = 0;
        decodeBitmapOptions.inTargetDensity = 0;
        decodeBitmapOptions.outWidth = 0;
        decodeBitmapOptions.outHeight = 0;
        decodeBitmapOptions.outMimeType = null;
        decodeBitmapOptions.inBitmap = null;
        decodeBitmapOptions.inMutable = true;
      }
    

    LruBitmapPool
    Glideはbitmapを多重化し、廃棄されたbitmapをキャッシュする場所が必要ですが、LruBitmapPoolはGlideがBitmapキャッシュ多重化を提供するプールです.
    以下のコードロジックは比較的に簡単で、内部も実はLruCacheの思想で、本当の実現クラスはLruPoolStrategyです
    
    public class LruBitmapPool implements BitmapPool {
    
    private final LruPoolStrategy strategy;
    
    public synchronized void put(Bitmap bitmap) {
        if (bitmap == null) {
          throw new NullPointerException("Bitmap must not be null");
        }
        if (bitmap.isRecycled()) {
          throw new IllegalStateException("Cannot pool recycled bitmap");
        }
        if (!bitmap.isMutable() || strategy.getSize(bitmap) > maxSize
            || !allowedConfigs.contains(bitmap.getConfig())) {
          bitmap.recycle();
          return;
        }
    
        final int size = strategy.getSize(bitmap);
        strategy.put(bitmap);
        tracker.add(bitmap);
    
        puts++;
        currentSize += size;
        dump();
        evict();
      }
    
      public Bitmap get(int width, int height, Bitmap.Config config) {
        Bitmap result = getDirtyOrNull(width, height, config);
        if (result != null) {
          result.eraseColor(Color.TRANSPARENT);
        } else {
          result = createBitmap(width, height, config);
        }
    
        return result;
        }
    }
    

    SizeConfigStrategy
    SizeConfigStrategyの内部コードロジックも比較的簡単で、本当にbitmapへのアクセスを実現するためのポリシークラスです.
    public class SizeConfigStrategy implements LruPoolStrategy {
      private static final int MAX_SIZE_MULTIPLE = 8;
      private final KeyPool keyPool = new KeyPool();
      private final GroupedLinkedMap<Key, Bitmap> groupedMap = new GroupedLinkedMap<>();
      private final Map<Bitmap.Config, NavigableMap<Integer, Integer>> sortedSizes = new HashMap<>();
      
      //   
      public void put(Bitmap bitmap) {
        int size = Util.getBitmapByteSize(bitmap);
        Key key = keyPool.get(size, bitmap.getConfig());
    
        groupedMap.put(key, bitmap);
    
        NavigableMap<Integer, Integer> sizes = getSizesForConfig(bitmap.getConfig());
        Integer current = sizes.get(key.size);
        sizes.put(key.size, current == null ? 1 : current + 1);
      }
    
      //   
      public Bitmap get(int width, int height, Bitmap.Config config) {
        int size = Util.getBitmapByteSize(width, height, config);
        //        bitmap
        Key bestKey = findBestKey(size, config);
        //   
        Bitmap result = groupedMap.get(bestKey);
        if (result != null) {
          // Decrement must be called before reconfigure.
          decrementBitmapOfSize(bestKey.size, result);
          result.reconfigure(width, height,
              result.getConfig() != null ? result.getConfig() : Bitmap.Config.ARGB_8888);
        }
        return result;
      }
      
    }
    

    findBestKey()メソッドは、サイズマッチングによって最適なkeyを検索することです.
    // SizeCofingStrategy
      private Key findBestKey(int size, Bitmap.Config config) {
        Key result = keyPool.get(size, config);
        for (Bitmap.Config possibleConfig : getInConfigs(config)) {
          NavigableMap<Integer, Integer> sizesForPossibleConfig = getSizesForConfig(possibleConfig);
          Integer possibleSize = sizesForPossibleConfig.ceilingKey(size);
          if (possibleSize != null && possibleSize <= size * MAX_SIZE_MULTIPLE) {
            if (possibleSize != size
                || (possibleConfig == null ? config != null : !possibleConfig.equals(config))) {
              keyPool.offer(result);
              result = keyPool.get(possibleSize, possibleConfig);
            }
            break;
          }
        }
        return result;
      }
    

    ここで質問があります.
    キャッシュプールからBitmapを取得しましたが、カラーフォーマットやデフォルトのConfigなどのConfig設定を変更する方法はありません.ARGB_8888形式はARGB_444フォーマット?
    答えは次のとおりです.
  • 初回ロードの場合、Optionで直接指定できます.
  • ロードされたbitmapの場合、bitmapはreconfigure()メソッドを提供して設定されます.
  •     public void reconfigure(int width, int height, Config config) {
            checkRecycled("Can't call reconfigure() on a recycled bitmap");
            if (width <= 0 || height <= 0) {
                throw new IllegalArgumentException("width and height must be > 0");
            }
            if (!isMutable()) {
                throw new IllegalStateException("only mutable bitmaps may be reconfigured");
            }
    
            nativeReconfigure(mNativePtr, width, height, config.nativeInt, mRequestPremultiplied);
            mWidth = width;
            mHeight = height;
            mColorSpace = null;
        }
    

    多重プールはいつ加入しますか?
    BitmapPoolはbitmapをキャッシュする場所を提供していますが、bitmapはいつBitmapPoolに追加されますか?
    実際には、各外部リソースrecycleの場合、対応するBitmapPoolが追加されますが、BitmapPoolは構成可能です.ここではLruBitmapPoolを分析するだけです.
    // BitmapDrawableResource
      public void recycle() {
        bitmapPool.put(drawable.getBitmap());
      }
    

    多重プールからいつ取り出しますか?
    同じように、それはいつBitmapPoolから取り出して使われたのか、一例としてDownSamperのdecodeFromWrappedStreamsで、内部に論理を取り出す
    // DownSampler
      private Bitmap decodeFromWrappedStreams(InputStream is,
          BitmapFactory.Options options, DownsampleStrategy downsampleStrategy,
          DecodeFormat decodeFormat, boolean isHardwareConfigAllowed, int requestedWidth,
          int requestedHeight, boolean fixBitmapToRequestedDimensions,
          DecodeCallbacks callbacks) throws IOException {
         //      
        int[] sourceDimensions = getDimensions(is, options, callbacks, bitmapPool);
        int sourceWidth = sourceDimensions[0];
        int sourceHeight = sourceDimensions[1];
        String sourceMimeType = options.outMimeType;
        //     
        int orientation = ImageHeaderParserUtils.getOrientation(parsers, is, byteArrayPool);
        int degreesToRotate = TransformationUtils.getExifOrientationDegrees(orientation);
        boolean isExifOrientationRequired = TransformationUtils.isExifOrientationRequired(orientation);
        //       
        int targetWidth = requestedWidth == Target.SIZE_ORIGINAL ? sourceWidth : requestedWidth;
        int targetHeight = requestedHeight == Target.SIZE_ORIGINAL ? sourceHeight : requestedHeight;
        //     
        ImageType imageType = ImageHeaderParserUtils.getType(parsers, is, byteArrayPool);
        ...
        //      
        if ((options.inSampleSize == 1 || isKitKatOrGreater) && shouldUsePool(imageType)) {
          ...
          // If this isn't an image, or BitmapFactory was unable to parse the size, width and height
          // will be -1 here.
          if (expectedWidth > 0 && expectedHeight > 0) {
            setInBitmap(options, bitmapPool, expectedWidth, expectedHeight);
          }
        }
        Bitmap downsampled = decodeStream(is, options, callbacks, bitmapPool);
        callbacks.onDecodeComplete(bitmapPool, downsampled);
        ...
        return rotated;
      }
      
    private static void setInBitmap(
          BitmapFactory.Options options, BitmapPool bitmapPool, int width, int height) {
        @Nullable Bitmap.Config expectedConfig = null;
    	...
        // BitmapFactory will clear out the Bitmap before writing to it, so getDirty is safe.
        options.inBitmap = bitmapPool.getDirty(width, height, expectedConfig);
      }
    

    getDirty()メソッドは、ピクチャロードでbitmapプールが使用可能であれば、bitmapPoolのgetDirty()を呼び出し、inBitmapに最後に付与することを示す
    // LruBitmapPool
      public Bitmap getDirty(int width, int height, Bitmap.Config config) {
        //     
        Bitmap result = getDirtyOrNull(width, height, config);
        if (result == null) { //    ,    bitmap
          result = createBitmap(width, height, config);
        }
        return result;
      }
    

    多重化プールから削除するのはいつですか?
    LruBitmapPool内部ではLruアルゴリズムを利用しており,操作ごとに余分なキャッシュを削除するかどうかを自動的に検出する.
    //LruBitmapPool
      private void evict() {
        trimToSize(maxSize);
      }
    
      private synchronized void trimToSize(long size) {
        while (currentSize > size) {
          final Bitmap removed = strategy.removeLast();
          // TODO: This shouldn't ever happen, see #331.
          if (removed == null) {
            if (Log.isLoggable(TAG, Log.WARN)) {
              Log.w(TAG, "Size mismatch, resetting");
              dumpUnchecked();
            }
            currentSize = 0;
            return;
          }
          tracker.remove(removed);
          currentSize -= strategy.getSize(removed);
          evictions++;
          if (Log.isLoggable(TAG, Log.DEBUG)) {
            Log.d(TAG, "Evicting bitmap=" + strategy.logBitmap(removed));
          }
          dump();
          removed.recycle();
        }
      }
    

    まとめ
  • Glideキャッシュ:アクティブリソース(Active Resources)メモリキャッシュ(Memory cache)リソースタイプ(Resourceデータソース(Data)
  • Glide多重化:BitmapPool(LruBitmapPool)
  • リファレンス
    https://muyangmin.github.io/glide-docs-cn/doc/caching.html
    https://blog.csdn.net/u011035622/article/details/50277565