標籤:
Android 開發中,bitmap是引起記憶體流失的罪魁禍首,關於bitmap的載入,緩衝策略,官方已經給了很詳細的方法:
緩衝之Memory Cache:
緩衝的策略,是利用應用程式的分配的記憶體拿出適當的一部分利用LruCache演算法進行緩衝。關於用多少記憶體來緩衝圖片,這個要根據不同的圖片,機型和 螢幕的解析度來進行綜合考量,比如對於同一個圖片,Galaxy Nexus 就比Nexus S需要的記憶體多。
以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);}
當載入圖片的時候,如果緩衝裡面有圖片,就直接從緩衝載入,否者就在一個background thread執行載入圖片的操作:
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); }}
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; } ...}
緩衝之Disk Cache:
對於圖片比較小,圖片個數比較少的,已經對載入效率比較快的要求,用Memory Cache無疑是最好的選擇,但是當對於,圖片比較多的情境,比如,Gridview ,ListView載入大量的圖片,記憶體就吃不消了,這時候,就需要用到磁碟等外部存放裝置來進行緩衝。
實際上,如果對圖片切換比較頻繁的話,那麼 ContentProvider 絕壁是個好的選擇,比如 image gallery application.
關於使用Disk Cache的官方範例程式碼如下:
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);}
memory Cache 在UI線程中執行,Disk Cache則是在Backound thread 中執行。
當旋轉螢幕,或者其他配置改變的時候,比如電話進來,彈出對話方塊,對Activity進行銷毀,重設的時候,需要對之前緩衝進行儲存;
比如在一個Fragment中儲存緩衝對象的程式碼範例:
private LruCache<String, Bitmap> mMemoryCache;@Overrideprotected 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(); fm.beginTransaction().add(fragment, TAG).commit(); } return fragment; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setRetainInstance(true); }}
From :http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html#config-changes
Android Bitmaps緩衝