眾所周知Android應用開發中不能在UI線程中做耗時的操作,否則就會彈出煩人的ANR視窗。
應用開發中如果需要載入來自網路、磁碟或其他非記憶體中圖片資源時,因載入時間會受到其他因素(如磁碟、網路、圖片大小、CPU等等)的影響,很容易產生耗時操作。所以在進行類似操作時要避免在UI線程中進行。今天就和大家分享一下如何通過AsyncTask非同步載入圖片和怎麼處理多線程並發問題。
如何使用 AsyncTask載入圖片?
通過AysncTask可以很容易的在啟動後台線程載入資源,然後將結果返回到UI線程中。使用它時,需要建立它的子類並實現相應的方法,如下是一個通過AysncTask和decodeSampledBitmapFromResource()方法載入一張大圖片到ImageView中的例子:
1 class BitmapWorkerTask extends AsyncTask { 2 private final WeakReference imageViewReference; 3 private int data = 0; 4 5 public BitmapWorkerTask(ImageView imageView) { 6 // Use a WeakReference to ensure the ImageView can be garbage collected 7 imageViewReference = new WeakReference(imageView); 8 } 9 10 // Decode image in background.11 @Override12 protected Bitmap doInBackground(Integer... params) {13 data = params[0];14 return decodeSampledBitmapFromResource(getResources(), data, 100, 100));15 }16 17 // Once complete, see if ImageView is still around and set bitmap.18 @Override19 protected void onPostExecute(Bitmap bitmap) {20 if (imageViewReference != null && bitmap != null) {21 final ImageView imageView = imageViewReference.get();22 if (imageView != null) {23 imageView.setImageBitmap(bitmap);24 }25 }26 }27 }
使用WeakReference 儲存ImageView的原因,是為了在記憶體資源緊張時確保AsyncTask 不會阻止對其進行資源回收,因此當task結束時不能保證Imageview還存在,所以你應該在onPostExecute中對它進行驗證(本例中在Task結束前如果使用者關閉Activity,或系統設定改變時,ImageView可能會被回收)。
通過以下方式我們就可以非同步載入圖片:
1 public void loadBitmap(int resId, ImageView imageView) {2 BitmapWorkerTask task = new BitmapWorkerTask(imageView);3 task.execute(resId);4 }
如何處理並行作業?
常用的View組件中 像ListView、GridView等 為了高效實用記憶體,使用者在進行View滾動操作時系統會對不再使用子View進行資源回收,,採用上面的方式進行圖片載入時會引入另外一個問題。如果在每個子View中開啟AsyncTask,不能保證在任務完成時,相關的View是否已經被回收。此外,也不能保證他們載入完成的順序
我們可以通過將AsyncTask的引用儲存ImageView關聯Drawable中,任務完成時檢查引用是否存在.
建立一個專用的Drawable子類,儲存工作任務線程的引用。這樣在任務完成時即可將圖片設定在ImageView中
1 static class AsyncDrawable extends BitmapDrawable { 2 private final WeakReference bitmapWorkerTaskReference; 3 public AsyncDrawable(Resources res, Bitmap bitmap, 4 BitmapWorkerTask bitmapWorkerTask) { 5 super(res, bitmap); 6 bitmapWorkerTaskReference = 7 new WeakReference(bitmapWorkerTask); 8 } 9 10 public BitmapWorkerTask getBitmapWorkerTask() {11 return bitmapWorkerTaskReference.get();12 }13 }
在執行BitmapTask前,你可以建立AsyncDrawable並將其綁定到ImageView中
1 public void loadBitmap(int resId, ImageView imageView) {2 if (cancelPotentialWork(resId, imageView)) {3 final BitmapWorkerTask task = new BitmapWorkerTask(imageView);4 final AsyncDrawable asyncDrawable =5 new AsyncDrawable(getResources(), mPlaceHolderBitmap, task);6 imageView.setImageDrawable(asyncDrawable);7 task.execute(resId);8 }9 }
上面代碼中通過cancelPotentialWork判斷是否已經存在正在啟動並執行任務綁定在ImageView中,若有,通過執行任務cancel方法取消它,當然這種情況不常發生,
下面是cancelPotentialWork的實現:
1 public static boolean cancelPotentialWork(int data, ImageView imageView) { 2 final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView); 3 4 if (bitmapWorkerTask != null) { 5 final int bitmapData = bitmapWorkerTask.data; 6 if (bitmapData != data) { 7 // Cancel previous task 8 bitmapWorkerTask.cancel(true); 9 } else {10 // The same work is already in progress11 return false;12 }13 }14 // No task associated with the ImageView, or an existing task was cancelled15 return true;16 }
下面是一個輔助方法,通過ImageView尋找與其關聯的非同步任務;
1 private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) { 2 if (imageView != null) { 3 final Drawable drawable = imageView.getDrawable(); 4 if (drawable instanceof AsyncDrawable) { 5 final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable; 6 return asyncDrawable.getBitmapWorkerTask(); 7 } 8 } 9 return null;10 }
下一步需要在BitmapWorkerTask中的onPostExecute中執行更新操作,
首先檢查任務是否取消,如後更行與其關聯的ImageView:
1 class BitmapWorkerTask extends AsyncTask { 2 ... 3 @Override 4 protected void onPostExecute(Bitmap bitmap) { 5 if (isCancelled()) { 6 bitmap = null; 7 } 8 if (imageViewReference != null && bitmap != null) { 9 final ImageView imageView = imageViewReference.get();10 final BitmapWorkerTask bitmapWorkerTask =11 getBitmapWorkerTask(imageView);12 if (this == bitmapWorkerTask && imageView != null) {13 imageView.setImageBitmap(bitmap);14 }15 }16 }17 }
通過以上方法,你就可以在ListView、GridView或者其他具有子view回收處理的組件中使用,通過調用
loadBitmap你可以很簡單的添加圖片到ImageView中,如:在GirdView的 Adapter中的getView方法中調用。