Bitmaps加載之內(nèi)存管理

除了前面說的Bitmap緩存之外,還有一些事情我們可以做來使用好GC和Bitmap的重用. 對于不同的Android版本要做不同的處理,這樣才能達(dá)到高效使用Bitmap的效果,這是推薦的策略.

這里先介紹一些關(guān)于Android中Bitmap內(nèi)存管理的基礎(chǔ)知識鋪墊一下:

  • 在Android2.2(API 8)及以前,當(dāng)GC開始回收時,app的所有線程都將停止, 這就導(dǎo)致了延遲的產(chǎn)生,進(jìn)而影響體驗(yàn). Android2.3及之后就不會用這個問題了,因此增加了GC的并發(fā)處理,也就意味著Bitmap被清理之后app的可用空間會很快回收回來.
  • Android2.3.3(API 10)及以前,Bitmap的圖片數(shù)據(jù)是保存在native memory上, 而Bitmap對象是保存在Dalvik的heap上,這樣這兩個就分離開了,就會導(dǎo)致內(nèi)存釋放不及時從而帶來潛在的OOM. 在Android 3.0(API 11)之后這個問題就解決了,因?yàn)檫@兩個都被放在Dalvik的heap上.

下面介紹如何根據(jù)不同的Android版本來管理Bitmap的內(nèi)存.

Android2.3.3及以下

在這個版本范圍內(nèi),推薦使用recycle()方法, 該方法會盡快把Bitmap的內(nèi)存回收回來.

  • 注意: 你只有在確定這個Bitmap不再使用的情況下才去調(diào)用recycle()方法. 不然如果你調(diào)用recycle()之后又要想去使用之前的Bitmap,會拋出一個異常:"Canvas: trying to use a recycled bitmap"

下面的代碼是Demo中RecyclingBitmapDrawable的一部分,其中mDisplayRefCount和mCacheRefCount這兩個變量用來記錄該Bitmap顯示和緩存情況,具體回收條件如下:

  1. mDisplayRefCount和mCacheRefCount的值都為0.
  2. 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屬性.如果設(shè)置了該屬性, BitmapFactory帶有Options參數(shù)的decode相關(guān)方法會嘗試去重用已存在的Bitmap, 這就意味這Bitmap的內(nèi)存空間得到了重用, 就可以改善性能,減少內(nèi)存分配和回收.
但是使用inBitmap這個屬性有一些限制, 有一點(diǎn)比較特別的是在Android4.4以前(API 19),只有相同大小的Bitmap才可以重用,具體可以看inBitmap文檔.

下面看具體實(shí)例:

1. 保存Bitmap

下面是用一個HashSet來保存從LruCache中移除的Bitmap的軟引用.

Set<SoftReference<Bitmap>> mReusableBitmaps;
private LruCache<String, BitmapDrawable> 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<SoftReference<Bitmap>>());
}

mMemoryCache = new LruCache<String, BitmapDrawable>(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<Bitmap>(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);
}

如果找到可用的就設(shè)置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<SoftReference<Bitmap>> 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

  1. Managing Bitmap Memory
  2. 官方DisplayingBitmaps Demo
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末亿蒸,一起剝皮案震驚了整個濱河市古沥,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖虚倒,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件砚亭,死亡現(xiàn)場離奇詭異,居然都是意外死亡蹦渣,警方通過查閱死者的電腦和手機(jī)誓酒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進(jìn)店門樟蠕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來贮聂,“玉大人靠柑,你說我怎么就攤上這事∠判福” “怎么了歼冰?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長耻警。 經(jīng)常有香客問我隔嫡,道長,這世上最難降的妖魔是什么甘穿? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任腮恩,我火速辦了婚禮,結(jié)果婚禮上温兼,老公的妹妹穿的比我還像新娘秸滴。我一直安慰自己,他們只是感情好募判,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布荡含。 她就那樣靜靜地躺著,像睡著了一般届垫。 火紅的嫁衣襯著肌膚如雪释液。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天装处,我揣著相機(jī)與錄音误债,去河邊找鬼。 笑死妄迁,一個胖子當(dāng)著我的面吹牛寝蹈,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播判族,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼躺盛,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了形帮?” 一聲冷哼從身側(cè)響起槽惫,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤周叮,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后界斜,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體仿耽,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年各薇,在試婚紗的時候發(fā)現(xiàn)自己被綠了项贺。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡峭判,死狀恐怖开缎,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情林螃,我是刑警寧澤奕删,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站疗认,受9級特大地震影響完残,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜横漏,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一谨设、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧缎浇,春花似錦扎拣、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至亡笑,卻和暖如春侣夷,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背仑乌。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工百拓, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人晰甚。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓衙传,卻偏偏與公主長得像,于是被迫代替她去往敵國和親厕九。 傳聞我的和親對象是個殘疾皇子蓖捶,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,573評論 2 359

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,297評論 25 707
  • HereAndroid的內(nèi)存優(yōu)化是性能優(yōu)化中很重要的一部分,而避免OOM又是內(nèi)存優(yōu)化中比較核心的一點(diǎn)扁远。這是一篇關(guān)于...
    HarryXR閱讀 3,821評論 1 24
  • 本文轉(zhuǎn)載來源 http://www.csdn.net/article/2015-09-18/2825737/1 (...
    yoosir閱讀 1,099評論 0 5
  • 今天從超市買來幾棵虎皮蘭俊鱼。第一次主動嘗試養(yǎng)花刻像。把以前死掉的花除去,重新灌土并闲,澆水细睡。幾個大的虎尾蘭就種好了。放...
    劉琦_8bf2閱讀 406評論 0 0
  • 一個陰雨天,某醫(yī)院腎病科里來了一位年輕的小伙子犀填,20多歲的年齡蠢壹,但臉色晦暗,疲倦不堪宏浩,一家人全都神色凝重知残,說小伙子...
    linjingxia閱讀 285評論 0 0