緩存圖片

將一個圖片加載到用戶界面很簡單,若是須要將一組圖片加載到UI,事件就變得複雜了。在
不少狀況下,隨着圖像在屏幕上快速滾動,須要加載的圖像是無窮無盡的。

經過將不在屏幕上的子view進行回收使內存總量保持穩定。假如你不保持對任何圖片的長時
間引用,垃圾回收機制將會釋放加載的圖片。但爲了保持一個流暢,快速加載的UI,要避免
每次來回在屏幕上處理這些圖像。內存和磁盤緩存一般能夠幫助處理這些問題,使得組件能
夠快速從新加載處理過的圖像。

使用內存緩存

內存緩存以佔據寶貴的內存資源爲代價提供了對圖像的快速訪問。LruCache類對於處理緩存
圖像的任務是很是好合適的,將最近被引用的對象保存在一個強引用的LinkedHashMap中,
並在內存超過度配給它的大小以前是否最近最少使用的成員對象。

要爲LruCache選擇一個合適的大小,應該考慮下面一些因素:
1.
2.一次在屏幕上將要顯示多少張圖片?多少張須要準備好顯示到屏幕上?
3.屏幕的大小和密度是怎麼樣的?一個高分辨率的設備須要準備更多的內存來顯示在低分辨率
設備上相同數量的圖片。
4.圖片的尺寸和配置是怎麼樣的,每張圖片須要佔據多大內存空間?
5.圖片被訪問的頻率有多高?是否有一些比其餘的訪問頻率要高?若是是這樣,也行你可能需
要老是在內存中保持必定的項目,甚至爲不一樣圖片組建立不一樣的LruCache。
6.你能在質量和數量之間取得平衡嗎?有時候能夠存儲較大數量低質量的圖片,在一個後臺任
務中加載高質量的圖片。

並無對全部應用程序都適合的方案,應該由你分析本身應用的狀況,並拿出一個合理的解決
方案。緩存過小致使額外的開銷,緩存太大會致使內存溢出,並給程序的其餘部分留下不多的
內存。
web

代碼片斷,雙擊複製
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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);
}


當把一個圖片加載到一個ImageView中時,LruCache將會先進行檢查。若是發現一個入口,它
被當即用來更新ImageView,不然一個後臺線程會進行圖像處理:
緩存

代碼片斷,雙擊複製
01
02
03
04
05
06
07
08
09
10
11
12
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);
    }
}


代碼片斷,雙擊複製
01
02
03
04
05
06
07
08
09
10
11
12
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的組件會很快佔據整個緩存。你的應用程序可能會被另外一個任務,例如來電而打
斷,而在後臺,它將會被殺死,高速緩存也會被摧毀。一旦用戶恢復操做,你的應用程序須要
從新處理每一個圖像。

在這種狀況下,可使用磁盤高速緩存,當圖片再也不內存中時能夠減小加載圖像的時間。固然
從磁盤中讀取圖像比從內存中讀取要慢,這個操做應該在一個後臺線程中進行,由於磁盤的讀
取時間是不可預知的。
app

代碼片斷,雙擊複製
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
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);
}


內存緩存在UI線程中進行檢查,而磁盤緩存在後臺線程中進行檢查。磁盤操做不該該在UI線
程上發生。當圖片的處理過程結束後,最終的圖片被添加到內存和磁盤緩存,以便之後使用。

處理配置變化

運行時的配置變化,例如屏幕方向的變化,會使得Android銷燬並重寫啓動正在運行的activity。
幸運的是,你有一個很好的內存緩存的位圖,緩存可以經過使用由調用setRetainInstance保存
的Fragment傳遞給新的activity。當activity被建立後,這個保留的Fragment被重寫鏈接,你也
得到了訪問現有緩存的機會,可使得圖像被快速取出並重寫填充到ImageView對象。
ide

代碼片斷,雙擊複製
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
private LruCache<String, Bitmap> mMemoryCache;
 
@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    RetainFragment mRetainFragment =
             RetainFragment.findOrCreateRetainFragment(getFragmentManager());
    mMemoryCache = RetainFragment.mRetainedCache;
    if (mMemoryCache == null ) {
         mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
             ... // Initialize cache here as usual
         }
         mRetainFragment.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 );
    }
}
相關文章
相關標籤/搜索