Bitmapsロードメモリ管理

8158 ワード

前述のBitmapキャッシュに加えて、GCとBitmapの再利用をうまく利用できることもあります.異なるAndroidバージョンに対して異なる処理を行うことで、Bitmapを効率的に使用する効果を達成することが推奨されています.
ここではまず、AndroidのBitmapメモリ管理に関する基礎知識を紹介します.
  • はAndroid 2にあります.2(API 8)以前はGCが回収を開始すると、appのすべてのスレッドが停止するため、遅延が発生し、体験に影響を及ぼす.Android2.3以降はこの問題がないためGCの同時処理が増加し,Bitmapがクリーンアップするとappの利用可能なスペースがすぐに回収されることを意味する.
  • Android2.3.3(API 10)以前、Bitmapのピクチャデータはnative memoryに保存するが、BitmapオブジェクトはDalvikのheapに保存するので、この2つが分離する、メモリの解放が遅れる潜在的なOOMをもたらす.Android 3.0(API 11)の後にこの問題は解決した.この2つはDalvikのheapに置かれているからだ.

  • 次に、異なるAndroidバージョンに基づいてBitmapのメモリを管理する方法について説明します.
    Android2.3.3以下
    このバージョンの範囲内では、Bitmapのメモリをできるだけ早く回収するrecycle()メソッドが推奨されています.
  • 注意:このBitmapが使用されなくなったと判断した場合にのみrecycle()メソッドを呼び出す.そうでなければ、recycle()を呼び出してから前のBitmapを使用しようとすると、「Canvas:trying to use a recycled bitmap」
  • という異常が投げ出されます.
    次のコードは、DemoのRecyclingBitmapDrawableの一部です.mDisplayRefCountとmCacheRefCountの2つの変数は、Bitmapの表示とキャッシュの状況を記録するために使用されます.具体的な回収条件は次のとおりです.
  • mDisplayRefCountとmCacheRefCountの値はいずれも0である.
  • Bitmapは空ではない.

  • 完全なコードは公式demoを参照してください.次のReferenceはダウンロードアドレスを持っています.
    private int mCacheRefCount = 0;
    private int mDisplayRefCount = 0;
    ...
    // Notify the drawable that the displayed state has changed.
    // Keep a count to determine when the drawable is no longer displayed.
    public void setIsDisplayed(boolean isDisplayed) {
        synchronized (this) {
            if (isDisplayed) {
                mDisplayRefCount++;
                mHasBeenDisplayed = true;
            } else {
                mDisplayRefCount--;
            }
        }
        // Check to see if recycle() can be called.
        checkState();
    }
    
    // Notify the drawable that the cache state has changed.
    // Keep a count to determine when the drawable is no longer being cached.
    public void setIsCached(boolean isCached) {
        synchronized (this) {
            if (isCached) {
                mCacheRefCount++;
            } else {
                mCacheRefCount--;
            }
        }
        // Check to see if recycle() can be called.
        checkState();
    }
    
    private synchronized void checkState() {
        // If the drawable cache and display ref counts = 0, and this drawable
        // has been displayed, then recycle.
        if (mCacheRefCount <= 0 && mDisplayRefCount <= 0 && mHasBeenDisplayed
                && hasValidBitmap()) {
            getBitmap().recycle();
        }
    }
    
    private synchronized boolean hasValidBitmap() {
        Bitmap bitmap = getBitmap();
        return bitmap != null && !bitmap.isRecycled();
    }
    

    Android 3.0以上
    Android 3.0(API 11)はBitmapFactoryを導入した.Options.inBitmap属性この属性を設定と、BitmapFactoryのOptionsパラメータを持つdecode関連メソッドが既存のBitmapを再利用しようとするので、Bitmapのメモリ領域が再利用され、パフォーマンスが向上し、メモリの割り当てと回収が減少することを意味する.しかし、inBitmapという属性を使うにはいくつかの制限があり、特にAndroid 4.4以前(API 19)は、同じサイズのBitmapのみが再利用可能である、具体的にはinBitmapドキュメントを参照することができる.
    次に、具体的な例を示します.
    1.Bitmapの保存
    以下に、LruCacheから除去するBitmapのソフトリファレンスをHashSetで保存する.
    Set> mReusableBitmaps;
    private LruCache mMemoryCache;
    
    // If you're running on Honeycomb or newer, create a
    // synchronized HashSet of references to reusable bitmaps.
    if (Utils.hasHoneycomb()) {
        mReusableBitmaps =
                Collections.synchronizedSet(new HashSet>());
    }
    
    mMemoryCache = new LruCache(mCacheParams.memCacheSize) {
    
        // Notify the removed entry that is no longer being cached.
        @Override
        protected void entryRemoved(boolean evicted, String key,
                BitmapDrawable oldValue, BitmapDrawable newValue) {
            if (RecyclingBitmapDrawable.class.isInstance(oldValue)) {
                // The removed entry is a recycling drawable, so notify it
                // that it has been removed from the memory cache.
                ((RecyclingBitmapDrawable) oldValue).setIsCached(false);
            } else {
                // The removed entry is a standard BitmapDrawable.
                if (Utils.hasHoneycomb()) {
                    // We're running on Honeycomb or later, so add the bitmap
                    // to a SoftReference set for possible use with inBitmap later.
                    mReusableBitmaps.add
                            (new SoftReference(oldValue.getBitmap()));
                }
            }
        }
    ....
    }
    

    2.Bitmapの再利用
    再利用可能なBitmapがあるかどうかを検索
    public static Bitmap decodeSampledBitmapFromFile(String filename,
            int reqWidth, int reqHeight, ImageCache cache) {
    
        final BitmapFactory.Options options = new BitmapFactory.Options();
        ...
        BitmapFactory.decodeFile(filename, options);
        ...
    
        // If we're running on Honeycomb or newer, try to use inBitmap.
        if (Utils.hasHoneycomb()) {
            addInBitmapOptions(options, cache);
        }
        ...
        return BitmapFactory.decodeFile(filename, options);
    }
    

    利用可能なものが見つかったらinBitmapを設定します
    private static void addInBitmapOptions(BitmapFactory.Options options,
            ImageCache cache) {
        // inBitmap only works with mutable bitmaps, so force the decoder to
        // return mutable bitmaps.
        options.inMutable = true;
    
        if (cache != null) {
            // Try to find a bitmap to use for inBitmap.
            Bitmap inBitmap = cache.getBitmapFromReusableSet(options);
    
            if (inBitmap != null) {
                // If a suitable bitmap has been found, set it as the value of
                // inBitmap.
                options.inBitmap = inBitmap;
            }
        }
    }
    
    // This method iterates through the reusable bitmaps, looking for one 
    // to use for inBitmap:
    protected Bitmap getBitmapFromReusableSet(BitmapFactory.Options options) {
            Bitmap bitmap = null;
    
        if (mReusableBitmaps != null && !mReusableBitmaps.isEmpty()) {
            synchronized (mReusableBitmaps) {
                final Iterator> iterator
                        = mReusableBitmaps.iterator();
                Bitmap item;
    
                while (iterator.hasNext()) {
                    item = iterator.next().get();
    
                    if (null != item && item.isMutable()) {
                        // Check to see it the item can be used for inBitmap.
                        if (canUseForInBitmap(item, options)) {
                            bitmap = item;
    
                            // Remove from reusable set so it can't be used again.
                            iterator.remove();
                            break;
                        }
                    } else {
                        // Remove from the set if the reference has been cleared.
                        iterator.remove();
                    }
                }
            }
        }
        return bitmap;
    }
    

    検索時の具体的な一致条件は次のとおりです.
    static boolean canUseForInBitmap(
            Bitmap candidate, BitmapFactory.Options targetOptions) {
    
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            // From Android 4.4 (KitKat) onward we can re-use if the byte size of
            // the new bitmap is smaller than the reusable bitmap candidate
            // allocation byte count.
            int width = targetOptions.outWidth / targetOptions.inSampleSize;
            int height = targetOptions.outHeight / targetOptions.inSampleSize;
            int byteCount = width * height * getBytesPerPixel(candidate.getConfig());
            return byteCount <= candidate.getAllocationByteCount();
        }
    
        // On earlier versions, the dimensions must match exactly and the inSampleSize must be 1
        return candidate.getWidth() == targetOptions.outWidth
                && candidate.getHeight() == targetOptions.outHeight
                && targetOptions.inSampleSize == 1;
    }
    
    /**
     * A helper function to return the byte usage per pixel of a bitmap based on its configuration.
     */
    static int getBytesPerPixel(Config config) {
        if (config == Config.ARGB_8888) {
            return 4;
        } else if (config == Config.RGB_565) {
            return 2;
        } else if (config == Config.ARGB_4444) {
            return 2;
        } else if (config == Config.ALPHA_8) {
            return 1;
        }
        return 1;
    }
    

    Reference
  • Managing Bitmap Memory
  • 公式DisplayingBitmaps Demo