Android官方开发文档Training系列课程中文版:高效显示位图之在非UI线程中处理图片
原文地址:http://android.xsoftlab.net/training/displaying-bitmaps/process-bitmap.html
我們在上節(jié)課Load Large Bitmaps Efficiently中討論了BitmapFactory.decode*方法,說到了不應(yīng)該在UI線程中執(zhí)行讀取數(shù)據(jù)的過程,尤其是從磁盤或者網(wǎng)絡(luò)上讀取數(shù)據(jù)(或者其它讀取速度次于內(nèi)存的地方)。讀取數(shù)據(jù)的時間是不可預(yù)料的,這取決于各種各樣的因素(從磁盤或者網(wǎng)絡(luò)讀取的速度、圖片的大小、CPU的功率,etc.)。如果這其中的一個因素阻塞了UI線程,那么系統(tǒng)會標志程序為無響應(yīng)標志,并會給用戶提供一個關(guān)閉的選項(請查看Designing for Responsiveness獲取更多信息)。
這節(jié)課討論了通過使用AsyncTask在非UI線程中處理位圖以及展示如何處理并發(fā)問題。
使用AsyncTask
類AsyncTask提供了一種簡要的方式來處理后臺進程的工作,并會將處理后的結(jié)果推送到UI線程中。如果要使用這個類,需要創(chuàng)建該類的子類,然后重寫所提供的方法。這里有個例子,展示了如何使用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及它所引用的事務(wù)被垃圾回收器回收。這不能保證在任務(wù)執(zhí)行完畢的時候ImageView還依然存在,所以你還必須在onPostExecute()方法中檢查一下它的引用。ImageView可能已經(jīng)不存在了,比如說吧,當用戶離開了activity或者在任務(wù)結(jié)束的時候一些配置發(fā)生了變化。
為了啟動異步任務(wù)來加載圖片,需要簡單的創(chuàng)建一個新任務(wù)并執(zhí)行它:
public void loadBitmap(int resId, ImageView imageView) {BitmapWorkerTask task = new BitmapWorkerTask(imageView);task.execute(resId); }處理并發(fā)
一些普通的View控件比如ListView和GridView會涉及到另一個問題,就是當與AsyncTask結(jié)合使用的時候會出現(xiàn)并發(fā)問題。為了能有效的使用內(nèi)存,這些控件會隨著用戶的滑動來回收子View。如果每一個子View都會觸發(fā)一個AsyncTask,那么就不能保障在任務(wù)完成的時候,與之相關(guān)聯(lián)的View沒有被回收利用。此外,對于順序啟動的任務(wù)也不能保障可以按順序完成。
博客Multithreading for Performance進一步的討論了如何處理并發(fā),它提供了一個解決方案:在ImageView中存儲了最近的AsyncTask的引用,這個引用可以在任務(wù)完成的時候?qū)ψ罱腁syncTask進行檢查。通過類似的辦法,那么上面章節(jié)的AsyncTask可以被擴展成類似的模式。
創(chuàng)建一個專用的Drawable子類來存儲工作任務(wù)的引用。在這種情況下,BitmapDrawable就會被用到,所以在任務(wù)完成之前可以有一個占位圖顯示在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();} }在執(zhí)行BitmapWorkerTask任務(wù)之前,你可以創(chuàng)建一個AsyncDrawable并將這個任務(wù)綁定到目標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()方法用來檢查是否有另外在進行中的任務(wù)已經(jīng)與ImageView關(guān)聯(lián)上了。如果是這樣的話,它會通過cancel()嘗試取消原來的任務(wù)。在少數(shù)情況下,新建的任務(wù)數(shù)據(jù)可能會與已經(jīng)存在的任務(wù)相匹配,所以就不要有進一步的動作。下面是cancelPotentialWork()方法的實現(xiàn):
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相關(guān)聯(lián)的任務(wù):
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(),所以它會檢查任務(wù)是否已經(jīng)被取消和檢查當前的任務(wù)是否與與之相關(guān)聯(lián)的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);}}} }現(xiàn)在這個實現(xiàn)就適合用到類似ListView和GridView這種會回收它們子View的組件上了,簡單的調(diào)用loadBitmap()就可以正常給ImageView設(shè)置圖片了。比如,在一個GridView的實現(xiàn)中,這個方法就可以在相應(yīng)適配器的getView()方法中使用。
總結(jié)
以上是生活随笔為你收集整理的Android官方开发文档Training系列课程中文版:高效显示位图之在非UI线程中处理图片的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: HTTPS请求实现框架
- 下一篇: Android官方开发文档Trainin