Android Training Caching Bitmaps 翻譯

來源:互聯網
上載者:User

標籤:des   android   style   blog   http   color   使用   io   

原文:http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html

圖片緩衝

在Android開發中,載入一個圖片到介面很容易,但如果一次載入大量圖片就複雜多了。在很多情況下(比如:ListView,GridView或ViewPager),能夠滾動的組件需要載入的圖片幾乎是無限多的。

有些組件的child view在不顯示時會回收,並迴圈使用,如果沒有任何對bitmap的持久引用的話,記憶體回收行程會釋放你載入的bitmap。這沒什麼問題,但當這些圖片再次顯示的時候,要想避免重複處理這些圖片,從而達到載入流暢的效果,就要使用記憶體緩衝和本機快取了,這些緩衝可以讓你快速載入處理過的圖片。

這些緩衝,就是本章要討論的內容。

使用記憶體緩衝

記憶體緩衝以犧牲記憶體的代價,帶來快速的圖片訪問。LruCache類(API Level 4之前可以使用Support Library)非常適合圖片緩衝任務,在一個LinkedHashMap中儲存著對Bitmap的強引用,當緩衝數量超過容器容量時,刪除最近最少使用的成員(LRU)。

注意:在過去,非常流行用SoftReference或WeakReference來實現圖片的記憶體緩衝,但現在不再推薦使用這個方法了。因為從Android 2.3 (API Level 9)之後,記憶體回收行程會更積極的回收soft/weak的引用,這將導致使用soft/weak引用的緩衝幾乎沒有緩衝效果。順帶一提,在Android3.0(API Level 11)以前,bitmap是儲存在native 記憶體中的,所以系統以不可預見的方式來釋放bitmap,這可能會導致短時間超過記憶體限制從而造成崩潰。

為了給LruCache一個合適的容量,需要考慮很多因素,比如:

  • 你其它的Activity 和/或 Application是怎樣使用記憶體的?
  • 螢幕一次顯示多少圖片?需要多少圖片為顯示到螢幕做準備?
  • 螢幕的大小(size)和密度(density)是多少?像Galaxy Nexus這樣高密度(xhdpi)的螢幕在緩衝相同數量的圖片時,就需要比低密度螢幕Nexus S(hdpi)更大的記憶體。
  • 每個圖片的尺寸多大,相關配置怎樣的,佔用多大記憶體?
  • 圖片的訪問頻率高不高?不同圖片的訪問頻率是否不一樣?如果是,你可能會把某些圖片一直緩衝在記憶體中,或需要多種不同緩衝策略的LruCache。
  • 你能平衡圖片的品質和數量嗎?有時候,緩衝多個品質低的圖片是很有用的,而品質高的圖片應該(像下載檔案一樣)在背景工作中載入。

這裡沒有適應所有應用的特定大小或公式,只能通過分析具體的使用方法,來得出合適的解決方案。緩衝太小的話沒有實際用處,還會增加額外開銷;緩衝太大的話,會再一次造成OutOfMemory異常,並給應用的其他部分留下很少的記憶體。

下面是一個圖片的LruCache的配置例子:

private LruCache<String, Bitmap> mMemoryCache;@Overrideprotected void onCreate(Bundle savedInstanceState) {    ...    // Get max available VM memory, exceeding this amount will throw an    // OutOfMemory exception. Stored in kilobytes as LruCache takes an    // int in its constructor.轉換單位    final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);    // Use 1/8th of the available memory for this memory cache.    final int cacheSize = maxMemory / 8;    mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {        @Override        protected int sizeOf(String key, Bitmap bitmap) {            // The cache size will be measured in kilobytes rather than            // number of items.轉換單位            return bitmap.getByteCount() / 1024;        }    };    ...}public void addBitmapToMemoryCache(String key, Bitmap bitmap) {    if (getBitmapFromMemCache(key) == null) {        mMemoryCache.put(key, bitmap);    }}public Bitmap getBitmapFromMemCache(String key) {    return mMemoryCache.get(key);}

注意:在這個例子中,將應用最大可使用記憶體的八分之一分配給了緩衝,在一個普通的或者hdpi裝置上,這個值大約至少是4MB(32/8)。在一個800x480解析度的裝置上全螢幕顯示一個填滿圖片的GridView大約要1.5MB(800*480*4 bytes),因此,這個LruCache至少能夠緩衝2.5頁的圖片。

當往ImageView中載入圖片時,先檢查以下LruCache中是否已經緩衝。如果已經緩衝,則直接取出並更新ImageView,否則要啟動個背景工作來處理圖片:

public void loadBitmap(int resId, ImageView imageView) {    final String imageKey = String.valueOf(resId);    final Bitmap bitmap = getBitmapFromMemCache(imageKey);    if (bitmap != null) {        mImageView.setImageBitmap(bitmap);    } else {        mImageView.setImageResource(R.drawable.image_placeholder);        BitmapWorkerTask task = new BitmapWorkerTask(mImageView);        task.execute(resId);    }}

這個BitmapWorkerTask也需要將新處理完的圖片添加進緩衝:

class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {    ...    // Decode image in background.    @Override    protected Bitmap doInBackground(Integer... params) {        final Bitmap bitmap = decodeSampledBitmapFromResource(                getResources(), params[0], 100, 100));        addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);        return bitmap;    }    ...}

 

使用磁碟緩衝

記憶體緩衝能夠加快對最近顯示過的圖片的訪問速度,然而你不能認為緩衝中的圖片全是有效。像GridView這樣需要大量資料的組件是很容易填滿記憶體緩衝的。你的應用可能會被別的任務打斷(比如一個來電),它可能會在後台被殺掉,其記憶體緩衝當然也被銷毀了。當使用者恢複你的應用時,應用將重新處理之前緩衝的每一張圖片。

在這個情形中,使用磁碟緩衝可以持久的儲存處理過的圖片,並且縮短載入記憶體緩衝中無效的圖片的時間。當然從磁碟載入圖片比從記憶體中載入圖片要慢的多,並且由於磁碟讀取的時間是不確定的,所以要在後台線程進行磁碟載入。

注意:如果以更高的頻率訪問圖片,比片牆應用,使用ContentProvider可能更適合儲存圖片緩衝。

下面這個例子除了之前的記憶體緩衝,還添加了一個磁碟緩衝,這個磁碟緩衝實現自Android源碼中的DiskLruCache:

private DiskLruCache mDiskLruCache;private final Object mDiskCacheLock = new Object();private boolean mDiskCacheStarting = true;private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MBprivate static final String DISK_CACHE_SUBDIR = "thumbnails";@Overrideprotected void onCreate(Bundle savedInstanceState) {    ...    // Initialize memory cache    ...    // Initialize disk cache on background thread    File cacheDir = getDiskCacheDir(this, DISK_CACHE_SUBDIR);    new InitDiskCacheTask().execute(cacheDir);    ...}class InitDiskCacheTask extends AsyncTask<File, Void, Void> {    @Override    protected Void doInBackground(File... params) {        synchronized (mDiskCacheLock) {            File cacheDir = params[0];            mDiskLruCache = DiskLruCache.open(cacheDir, DISK_CACHE_SIZE);            mDiskCacheStarting = false; // Finished initialization            mDiskCacheLock.notifyAll(); // Wake any waiting threads        }        return null;    }}class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {    ...    // Decode image in background.    @Override    protected Bitmap doInBackground(Integer... params) {        final String imageKey = String.valueOf(params[0]);        // Check disk cache in background thread        Bitmap bitmap = getBitmapFromDiskCache(imageKey);        if (bitmap == null) { // Not found in disk cache            // Process as normal            final Bitmap bitmap = decodeSampledBitmapFromResource(                    getResources(), params[0], 100, 100));        }        // Add final bitmap to caches        addBitmapToCache(imageKey, bitmap);        return bitmap;    }    ...}public void addBitmapToCache(String key, Bitmap bitmap) {    // Add to memory cache as before    if (getBitmapFromMemCache(key) == null) {        mMemoryCache.put(key, bitmap);    }    // Also add to disk cache    synchronized (mDiskCacheLock) {        if (mDiskLruCache != null && mDiskLruCache.get(key) == null) {            mDiskLruCache.put(key, bitmap);        }    }}public Bitmap getBitmapFromDiskCache(String key) {    synchronized (mDiskCacheLock) {        // Wait while disk cache is started from background thread        while (mDiskCacheStarting) {            try {                mDiskCacheLock.wait();            } catch (InterruptedException e) {}        }        if (mDiskLruCache != null) {            return mDiskLruCache.get(key);        }    }    return null;}// Creates a unique subdirectory of the designated app cache directory. Tries to use external// but if not mounted, falls back on internal storage.public static File getDiskCacheDir(Context context, String uniqueName) {    // Check if media is mounted or storage is built-in, if so, try and use external cache dir    // otherwise use internal cache dir    final String cachePath =            Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) ||                    !isExternalStorageRemovable() ? getExternalCacheDir(context).getPath() :                            context.getCacheDir().getPath();    return new File(cachePath + File.separator + uniqueName);}

注意:磁碟緩衝的初始化會涉及磁碟操作,因此不應該在UI線程做初始化操作。然而,這樣做可能會出現初始化完成前,就訪問緩衝的情況。為瞭解決這個問題,上面的例子使用了一個鎖對象,確保在磁碟緩衝初始化完成前,不會有其他對象使用它。

檢查記憶體緩衝可以在UI線程,檢查磁碟緩衝最好在後台線程。永遠不要在UI線程做磁碟操作。當圖片處理完成,應該將其添加進記憶體緩衝和磁碟緩衝中,以備將來不時之需。

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.