在非 UI 線程來處理圖片
在 高效的加載大尺寸圖片 中介紹的 用來解析圖片的 BitmapFactory.decode*
函數,需要在非UI線程中調用。
如果是讀取網絡圖片或者磁盤圖片,在UI線程中可能會導致程序ANR;如果是解析已經在內存中的圖片,則可以在UI線程中調用這些函數。
這節內容介紹如何使用AsyncTask
來處理圖片,以及如何處理併發訪問。
使用 AsyncTask
AsyncTask
類是一個在後臺線程中處理任務,並把結果反饋給UI線程的工具類。使用該類,只需要繼承她並且重寫對應的函數即可。
下面的代碼演示瞭如何用這個類來載入一個大尺寸圖片並顯示在
ImageView
中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
class BitmapWorkerTask extends AsyncTask<INTEGER, Bitmap Void,> { private final WeakReference<IMAGEVIEW> imageViewReference; private int data = 0 ; public BitmapWorkerTask(ImageView imageView) { // Use a WeakReference to ensure the ImageView can be garbage collected imageViewReference = new WeakReference<IMAGEVIEW>(imageView); } // Decode image in background. @Override protected Bitmap doInBackground(Integer... params) { data = params[ 0 ]; return decodeSampledBitmapFromResource(getResources(), data, 100 , 100 )); } // Once complete, see if ImageView is still around and set bitmap. @Override protected void onPostExecute(Bitmap bitmap) { if (imageViewReference != null && bitmap != null ) { final ImageView imageView = imageViewReference.get(); if (imageView != null ) { imageView.setImageBitmap(bitmap); } } } } |
引用到
ImageView
的 WeakReference
確保
AsyncTask
不會直接引用 ImageView
從而導致其無法被垃圾回收。
當解析完圖片後,無法確保 ImageView
是否還存在,所以需要在 onPostExecute()
函數中檢查下,如果用戶在圖片加載期間離開了當前界面,則當圖片加載完後用來顯示圖片的 ImageView
可能已經不存在了。
只要創建該Task並執行即可開始異步加載圖片了:
1
2
3
4
|
public void loadBitmap( int resId, ImageView imageView) { BitmapWorkerTask task = new BitmapWorkerTask(imageView); task.execute(resId); } |
處理併發訪問
像 ListView
和 GridView
這種控件使用上面介紹的方式來載入圖片可能會引入新的問題。爲了提高內存的使用率,當用戶做滾動操作的時候這些控件會重複利用子控件,如果每個子控件都觸發一個AsyncTask
,當任務完成的時候 無法保證該子控件是否已經被重用了。甚至,這些任務開始的順序和完成的順序也是不一樣的。
這篇博文Multithreading for Performance 進一步
討論瞭如何處理併發操作,並且提供了一個解決方案:
ImageView
中保存了最近觸發的AsyncTask
對象,當任務完成的時候可以用來檢測該引用的對象。
使用相似的函數,在前面介紹的AsyncTask
對象可以使用相似的模式來擴展。
創建一個特殊的 Drawable
子類來保存載入圖片的Task引用。
這裏使用 BitmapDrawable
類,這樣當任務完成的時候可以直接在
ImageView
中顯示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
static class AsyncDrawable extends BitmapDrawable { private final WeakReference<BITMAPWORKERTASK> bitmapWorkerTaskReference; public AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) { super (res, bitmap); bitmapWorkerTaskReference = new WeakReference<BITMAPWORKERTASK>(bitmapWorkerTask); } public BitmapWorkerTask getBitmapWorkerTask() { return bitmapWorkerTaskReference.get(); } } |
在執行 BitmapWorkerTask
之前,您需要創建一個
AsyncDrawable
並且綁定到需要顯示該圖片的那個 ImageView
中:
1
2
3
4
5
6
7
8
9
|
public void loadBitmap( int resId, ImageView imageView) { if (cancelPotentialWork(resId, imageView)) { final BitmapWorkerTask task = new BitmapWorkerTask(imageView); final AsyncDrawable asyncDrawable = new AsyncDrawable(getResources(), mPlaceHolderBitmap, task); imageView.setImageDrawable(asyncDrawable); task.execute(resId); } } |
上面代碼中的 cancelPotentialWork
函數用來檢測是否已經有一個Task綁定到 ImageView
了。
如果已經有個Task了就嘗試取消這個Task(調用 cancel()
函數)。
在一些情況下,如果新的任務和已經存在任務的數據一樣,則不需要額外的處理。下面是 cancelPotentialWork
函數的一種實現方式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public static boolean cancelPotentialWork( int data, ImageView imageView) { final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView); if (bitmapWorkerTask != null ) { final int bitmapData = bitmapWorkerTask.data; if (bitmapData != data) { // Cancel previous task bitmapWorkerTask.cancel( true ); } else { // The same work is already in progress return false ; } } // No task associated with the ImageView, or an existing task was cancelled return true ; } |
上面用到的助手函數 getBitmapWorkerTask()
,用來獲取和 ImageView
關聯的任務:
1
2
3
4
5
6
7
8
9
10
|
private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) { if (imageView != null ) { final Drawable drawable = imageView.getDrawable(); if (drawable instanceof AsyncDrawable) { final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable; return asyncDrawable.getBitmapWorkerTask(); } } return null ; } |
最後,需要更新 BitmapWorkerTask
類的 onPostExecute()
函數。
用來檢測任務是否取消了,以及當前的任務和 ImageView
引用的任務是否爲同一個任務:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
class BitmapWorkerTask extends AsyncTask<INTEGER, Bitmap Void,> { ... @Override protected void onPostExecute(Bitmap bitmap) { <STRONG> if (isCancelled()) { bitmap = null ; }</STRONG> if (imageViewReference != null && bitmap != null ) { final ImageView imageView = imageViewReference.get(); <STRONG> final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);</STRONG> if (<STRONG> this == bitmapWorkerTask &&</STRONG> imageView != null ) { imageView.setImageBitmap(bitmap); } } } } |
這樣就可以在 ListView
和 GridView
控件中使用該圖片加載任務了。只需要在您設置
ImageView
圖片的地方調用
loadBitmap
函數即可。
例如:在 GridView
中可能需要在Adapter中的 getView()
函數中調用
loadBitmap
函數。
原文轉載自 雲在千峯: http://yunfeng.sinaapp.com/?p=394#ixzz20lLvHtnN