Android官方开发文档Training系列课程中文版:高效显示位图之在非UI线程中处理图片
原文地址:http://android.xsoftlab.net/training/displaying-bitmaps/process-bitmap.html
我們在上節課Load Large Bitmaps Efficiently中討論了BitmapFactory.decode*方法,說到了不應該在UI線程中執行讀取數據的過程,尤其是從磁盤或者網絡上讀取數據(或者其它讀取速度次于內存的地方)。讀取數據的時間是不可預料的,這取決于各種各樣的因素(從磁盤或者網絡讀取的速度、圖片的大小、CPU的功率,etc.)。如果這其中的一個因素阻塞了UI線程,那么系統會標志程序為無響應標志,并會給用戶提供一個關閉的選項(請查看Designing for Responsiveness獲取更多信息)。
這節課討論了通過使用AsyncTask在非UI線程中處理位圖以及展示如何處理并發問題。
使用AsyncTask
類AsyncTask提供了一種簡要的方式來處理后臺進程的工作,并會將處理后的結果推送到UI線程中。如果要使用這個類,需要創建該類的子類,然后重寫所提供的方法。這里有個例子,展示了如何使用AsyncTask及decodeSampledBitmapFromResource()來加載一張大圖到ImageView上:
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {private final WeakReference<ImageView> imageViewReference;private int data = 0;public BitmapWorkerTask(ImageView imageView) {// Use a WeakReference to ensure the ImageView can be garbage collectedimageViewReference = new WeakReference<ImageView>(imageView);}// Decode image in background.@Overrideprotected Bitmap doInBackground(Integer... params) {data = params[0];return decodeSampledBitmapFromResource(getResources(), data, 100, 100));}// Once complete, see if ImageView is still around and set bitmap.@Overrideprotected 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可能已經不存在了,比如說吧,當用戶離開了activity或者在任務結束的時候一些配置發生了變化。
為了啟動異步任務來加載圖片,需要簡單的創建一個新任務并執行它:
public void loadBitmap(int resId, ImageView imageView) {BitmapWorkerTask task = new BitmapWorkerTask(imageView);task.execute(resId); }處理并發
一些普通的View控件比如ListView和GridView會涉及到另一個問題,就是當與AsyncTask結合使用的時候會出現并發問題。為了能有效的使用內存,這些控件會隨著用戶的滑動來回收子View。如果每一個子View都會觸發一個AsyncTask,那么就不能保障在任務完成的時候,與之相關聯的View沒有被回收利用。此外,對于順序啟動的任務也不能保障可以按順序完成。
博客Multithreading for Performance進一步的討論了如何處理并發,它提供了一個解決方案:在ImageView中存儲了最近的AsyncTask的引用,這個引用可以在任務完成的時候對最近的AsyncTask進行檢查。通過類似的辦法,那么上面章節的AsyncTask可以被擴展成類似的模式。
創建一個專用的Drawable子類來存儲工作任務的引用。在這種情況下,BitmapDrawable就會被用到,所以在任務完成之前可以有一個占位圖顯示在ImageView上:
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上:
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()方法用來檢查是否有另外在進行中的任務已經與ImageView關聯上了。如果是這樣的話,它會通過cancel()嘗試取消原來的任務。在少數情況下,新建的任務數據可能會與已經存在的任務相匹配,所以就不要有進一步的動作。下面是cancelPotentialWork()方法的實現:
public static boolean cancelPotentialWork(int data, ImageView imageView) {final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);if (bitmapWorkerTask != null) {final int bitmapData = bitmapWorkerTask.data;// If bitmapData is not yet set or it differs from the new dataif (bitmapData == 0 || bitmapData != data) {// Cancel previous taskbitmapWorkerTask.cancel(true);} else {// The same work is already in progressreturn false;}}// No task associated with the ImageView, or an existing task was cancelledreturn true; }有個輔助方法:getBitmapWorkerTask(),它被用來接收與指定ImageView相關聯的任務:
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相匹配:
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {...@Overrideprotected void onPostExecute(Bitmap bitmap) {if (isCancelled()) {bitmap = null;}if (imageViewReference != null && bitmap != null) {final ImageView imageView = imageViewReference.get();final BitmapWorkerTask bitmapWorkerTask =getBitmapWorkerTask(imageView);if (this == bitmapWorkerTask && imageView != null) {imageView.setImageBitmap(bitmap);}}} }現在這個實現就適合用到類似ListView和GridView這種會回收它們子View的組件上了,簡單的調用loadBitmap()就可以正常給ImageView設置圖片了。比如,在一個GridView的實現中,這個方法就可以在相應適配器的getView()方法中使用。
總結
以上是生活随笔為你收集整理的Android官方开发文档Training系列课程中文版:高效显示位图之在非UI线程中处理图片的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: HTTPS请求实现框架
- 下一篇: Android官方开发文档Trainin