android 緩存Bitmap - 開發文檔翻譯

由於本人英文能力實在有限,不足之初敬請諒解

本博客只要沒有註明“轉”,那麼均爲原創,轉貼請註明本博客鏈接鏈接



Loading a single bitmap into your user interface (UI) is straightforward, however things get more complicated if you need to load a larger set of images at once.
In many cases (such as with components like ListView, GridView or ViewPager),
the total number of images on-screen combined with images that might soon scroll onto the screen are essentially unlimited.
加載單一的bitmap到你的UI是很簡單的,然而如果你需要在同一時間加載大量圖片,事情將變得複雜
很多情況中(比如使用像ListView, GridView 或者 ViewPager一類的組件時),快速滾動到屏幕上的圖片合併成的屏幕圖片,這些圖片總量基本是不可計數的

Memory usage is kept down with components like this by recycling the child views as they move off-screen.
The garbage collector also frees up your loaded bitmaps, assuming you don't keep any long lived references.
This is all good and well, but in order to keep a fluid and fast-loading UI you want to avoid continually processing these images each time they come back on-screen.
A memory and disk cache can often help here, allowing components to quickly reload processed images.
內存的使用是由組件控制的,比如當子view移出屏幕的時候回收他們
假設你沒有保持任何持久的引用,垃圾回收器也會釋放你加載的圖片
這樣做很好,但是UI爲了保持流動和快速加載,當這些圖片返回到屏幕上的時候,你想避免每一次持續處理這些圖片
一個內存緩存和磁盤緩存可以幫助你,允許組件快速的重新加載處理過的圖片。

This lesson walks you through using a memory and disk bitmap cache to improve the responsiveness and fluidity of your UI when loading multiple bitmaps.
這一節帶你瀏覽使用內存和磁盤bimtap緩存來改進當加載大量bitmap時你的UI響應性和流暢度

Use a Memory Cache
使用內存緩存

A memory cache offers fast access to bitmaps at the cost of taking up valuable application memory.
The LruCache class (also available in the Support Library for use back to API Level 4) is particularly well suited to the task of caching bitmaps, keeping recently referenced objects in a strong referenced LinkedHashMap and evicting the least recently used member before the cache exceeds its designated size.
內存緩存提供快速訪問bitmap是以應用寶貴的內存爲代價的
LruCache類特別適合緩存bitmap的任務,保持最近引用的對象在一個強引用的LinkedHashMap中,在緩存擴張到指定大小之前,移除最近最少使用的成員

Note: In the past, a popular memory cache implementation was a SoftReference or WeakReference bitmap cache, however this is not recommended.
Starting from Android 2.3 (API Level 9) the garbage collector is more aggressive with collecting soft/weak references which makes them fairly ineffective.
In addition, prior to Android 3.0 (API Level 11), the backing data of a bitmap was stored in native memory which is not released in a predictable manner, potentially causing an application to briefly exceed its memory limits and crash.
注意:過去,一個流行的內存緩存實現是用一個軟引用或者弱引用bitmap緩存,但是並不推薦這麼做
從Android 2.3 (API Level 9)開始,垃圾回收器對於收集軟引用和弱引用變得更積極,這就使得他們相對無效。
另外,在Android 3.0 (API Level 11)之前,bitmap是儲存在native內存中的,他的釋放並不是一種可遇見的方式,這便存在潛在的引起應用超過它自身內存限制並且導致其崩潰的風險

In order to choose a suitable size for a LruCache, a number of factors should be taken into consideration, for example:
爲了給LruCache選擇一個合適的大小,下面一些因素應該考慮進去:

How memory intensive is the rest of your activity and/or application?
你其餘的activity內存使用情況和應用其餘部分的內存使用情況
How many images will be on-screen at once? How many need to be available ready to come on-screen?
一次同時多少圖片會顯示到屏幕上
多少圖片要準備好以便顯示隨時顯示到屏幕上
What is the screen size and density of the device?
An extra high density screen (xhdpi) device like Galaxy Nexus will need a larger cache to hold the same number of images in memory compared to a device like Nexus S (hdpi).
設備的屏幕尺寸和密度是多少
一個像Galaxy Nexus特別高屏幕密度的設備,與Nexus S (hdpi)這樣的設備相比,會需要一個大緩存在內存中來持有相同數量的圖片
What dimensions and configuration are the bitmaps and therefore how much memory will each take up?
bitmap的尺寸和結構是什麼、每一張圖片佔用的內存是多少
How frequently will the images be accessed?
Will some be accessed more frequently than others?
If so, perhaps you may want to keep certain items always in memory or even have multiple LruCache objects for different groups of bitmaps.
圖片被訪問的頻率是多少
是否一些圖片訪問頻率要比其他的大一些
如果是這樣,也許你應該一直保持一些在內存中,甚至可以使用多個LruCache對象來管理多組bitmap
Can you balance quality against quantity?
Sometimes it can be more useful to store a larger number of lower quality bitmaps, potentially loading a higher quality version in another background task.
你能在質量與數量直接保持平衡嗎
有些時候,存儲大量低質量bitmap是很有效的,而在另一個後臺進程加載一個高質量版本

There is no specific size or formula that suits all applications, it's up to you to analyze your usage and come up with a suitable solution.
A cache that is too small causes additional overhead with no benefit, a cache that is too large can once again cause java.lang.OutOfMemory exceptions and leave the rest of your app little memory to work with.
沒有特定的大小或公式適合所有的應用,這取決於你對你的用法的分析,然後提出一種適合的解決方案
一個過小的緩存不但沒有任何好處而且還會引起額外的開銷,一個過大的緩存可再次引發java.lang.OutOfMemory異常或者給你應用剩餘部分只留下很小的內存

Here’s an example of setting up a LruCache for bitmaps:
下面是一個爲bitmap設置LruCache的例子:

private LruCache<String, Bitmap> mMemoryCache;

@Override
protected 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);
}

Note: In this example, one eighth of the application memory is allocated for our cache.
On a normal/hdpi device this is a minimum of around 4MB (32/8).
A full screen GridView filled with images on a device with 800x480 resolution would use around 1.5MB (800*480*4 bytes), so this would cache a minimum of around 2.5 pages of images in memory.
注意:在這個例子中,1/8的應用內存分配給了我們的緩存
一個普通的/hdpi 的設備,這個值最少是4mb(32/8)
在一個分辨率爲800x480的設備上,一個全屏的、被圖片填滿的GridView使用大概1.5MB的內存(800*480*4 bytes),所以4mb至少緩存的2.5頁的圖片在內存中

When loading a bitmap into an ImageView, the LruCache is checked first.
If an entry is found, it is used immediately to update the ImageView, otherwise a background thread is spawned to process the image:
當加載一個bitmap到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);
    }
}

The BitmapWorkerTask also needs to be updated to add entries to the memory cache:
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;
    }
    ...
}


Use a Disk Cache
使用磁盤緩存

A memory cache is useful in speeding up access to recently viewed bitmaps, however you cannot rely on images being available in this cache.
Components like GridView with larger datasets can easily fill up a memory cache.
Your application could be interrupted by another task like a phone call, and while in the background it might be killed and the memory cache destroyed.
Once the user resumes, your application has to process each image again.
內存緩存在加速訪問最近瀏覽的bitmap是很有效的,然而你不能指望圖片在這個緩存中是有效的
像GridView一類的帶有大數據的組件,可以輕易的填滿內存緩存
一旦用戶用戶繼續瀏覽,你的應用就不得不再次處理每一張圖片

A disk cache can be used in these cases to persist processed bitmaps and help decrease loading times where images are no longer available in a memory cache.
Of course, fetching images from disk is slower than loading from memory and should be done in a background thread, as disk read times can be unpredictable.
磁盤緩存可以用於這些情況,保持處理過的bitmap,在圖片在內存緩存中失效的地方減少加載所需時間
當然,從磁盤上獲取這些圖片要比從內存中加載慢,並且由於磁盤讀取時間是不可預知的,所以也應該在後臺進程中完成

Note: A ContentProvider might be a more appropriate place to store cached images if they are accessed more frequently, for example in an image gallery application.
注意:如果圖片被訪問的非常頻繁的話,ContentProvider也許更適合存儲緩存圖片,例如在一個圖片畫廊應用中

The sample code of this class uses a DiskLruCache implementation that is pulled from the Android source.
Here’s updated example code that adds a disk cache in addition to the existing memory cache:
這個類的示例代碼使用了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; // 10MB
private static final String DISK_CACHE_SUBDIR = "thumbnails";

@Override
protected 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);
}

Note: Even initializing the disk cache requires disk operations and therefore should not take place on the main thread.
However, this does mean there's a chance the cache is accessed before initialization.
To address this, in the above implementation, a lock object ensures that the app does not read from the disk cache until the cache has been initialized.
注意:初始化磁盤緩存需要磁盤操作,所以不應該把這個工作放到主線程中
然而,這確實是意味着,在緩存初始化之前,緩存就有被訪問的可能
爲了解決這個問題,在上面的實現中,一個鎖對象保證了應用在磁盤緩存初始化完畢之前不會訪問磁盤緩存

While the memory cache is checked in the UI thread, the disk cache is checked in the background thread.
Disk operations should never take place on the UI thread.
When image processing is complete, the final bitmap is added to both the memory and disk cache for future use.
然而內存緩存是在UI線程中檢查,磁盤緩存是在後臺線程中檢查
磁盤操作永遠不該在UI線程中發生
當圖片處理完成時,最終的bitmap爲了之後的使用而被添加到內存緩存和磁盤緩存中

Handle Configuration Changes
處理配置改變
Runtime configuration changes, such as a screen orientation change, cause Android to destroy and restart the running activity with the new configuration (For more information about this behavior, see Handling Runtime Changes).
You want to avoid having to process all your images again so the user has a smooth and fast experience when a configuration change occurs.
運行時配置改變,比如屏幕方向改變,導致Android銷燬並使用新的配置項重啓運行中的activity(這種行爲的更多信息參見Handling Runtime Changes)
當配置改變發生時,你想避免重新處理一遍你所有的圖片,這樣用戶纔會有流暢和快速的體驗

Luckily, you have a nice memory cache of bitmaps that you built in the Use a Memory Cache section.
This cache can be passed through to the new activity instance using a Fragment which is preserved by calling setRetainInstance(true)).
After the activity has been recreated, this retained Fragment is reattached and you gain access to the existing cache object, allowing images to be quickly fetched and re-populated into the ImageView objects.
幸運的是,在Use a Memory Cache章節中,你有一個不錯的存儲bitmap的內存緩存
使用Fragment通過調用setRetainInstance(true),這個緩存可以傳遞到新的activity實例中
在activity重新建立了之後,這個保留下來的Fragment會重新附着在activity上,你可以訪問現有的緩存對象,允許圖片被快速的獲取並重新填充到ImageView對象中

Here’s an example of retaining a LruCache object across configuration changes using a Fragment:
下面是在配置改變時,使用Fragment保持一個LruCache對象

private LruCache<String, Bitmap> mMemoryCache;

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    RetainFragment retainFragment =
            RetainFragment.findOrCreateRetainFragment(getFragmentManager());
    mMemoryCache = retainFragment.mRetainedCache;
    if (mMemoryCache == null) {
        mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
            ... // Initialize cache here as usual
        }
        retainFragment.mRetainedCache = mMemoryCache;
    }
    ...
}

class RetainFragment extends Fragment {
    private static final String TAG = "RetainFragment";
    public LruCache<String, Bitmap> mRetainedCache;

    public RetainFragment() {}

    public static RetainFragment findOrCreateRetainFragment(FragmentManager fm) {
        RetainFragment fragment = (RetainFragment) fm.findFragmentByTag(TAG);
        if (fragment == null) {
            fragment = new RetainFragment();
        }
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRetainInstance(true);
    }
}

To test this out, try rotating a device both with and without retaining the Fragment.
You should notice little to no lag as the images populate the activity almost instantly from memory when you retain the cache.
Any images not found in the memory cache are hopefully available in the disk cache, if not, they are processed as usual.
爲了進行徹底檢驗,分別嘗試旋轉帶有此Fragment和不帶此Fragment的設備
你會發現機會沒有延時,因爲當你保持緩存的時候,圖片立即從內存填充到activity
任何在內存緩存中找不到的圖片希望能在磁盤緩存中找到,不然就要像平時一樣處理他們


原文地址如下,英文水平實在有限,希望拍磚同時能給予指正。

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

 

 

轉貼請保留以下鏈接

本人blog地址

http://su1216.iteye.com/

http://blog.csdn.net/su1216/

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