注:本文翻譯自Google官方的Android Developers Training文檔,譯者技術(shù)一般,由于喜愛安卓而產(chǎn)生了翻譯的念頭,純屬個人興趣愛好。
原文鏈接: http://developer.android.com/training/displaying-bitmaps/process-bitmap.html
在上節(jié)課(博客鏈接: http://www.cnblogs.com/jdneo/p/3514060.html )中所討論的 BitmapFactory.decode* 方法,在數(shù)據(jù)源是從閃存盤或者網(wǎng)絡(luò)(任何非手機(jī)存儲的數(shù)據(jù)來源)讀取的,那么就不能再主UI線程中執(zhí)行。因?yàn)榧虞d這個數(shù)據(jù)所花費(fèi)的時(shí)間是不可估計(jì)的,并且它依賴于虛度因素(從網(wǎng)絡(luò)讀取的速度,圖片尺寸,CPU的處理能力,等等)。如果其中一個任務(wù)阻塞的UI線程,那么系統(tǒng)將會把你的應(yīng)用標(biāo)記為未響應(yīng),之后用戶就可以選擇強(qiáng)制關(guān)閉它(可以閱讀: Designing for Responsiveness )。
這節(jié)課會教你如何在一個后臺線程,使用
AsyncTask
處理圖像,并告訴你如何處理并發(fā)問題。
一). 使用一個AsyncTask
AsyncTask
類提供一個簡單地方法在后臺線程執(zhí)行一些任務(wù),并將結(jié)果反饋給UI線程。要使用它,可以創(chuàng)建一個子類,并覆寫一些函數(shù)。下面是一個使用
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 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 和任何它引用的對象被垃圾回收器回收。這樣的話,在任務(wù)結(jié)束后, ImageView 是否仍然存在就沒有保證了,所以你必須在 onPostExecute() 中檢查一下引用是否存在。這個 ImageView 也許已經(jīng)不存在了,就比如說,用戶轉(zhuǎn)到了其他的activity,或者一個配置的變更在任務(wù)完成之前發(fā)生了。
要開始異步地加載這個位圖,簡單地創(chuàng)建一個任務(wù)的實(shí)例并執(zhí)行它:
public void loadBitmap( int resId, ImageView imageView) { BitmapWorkerTask task = new BitmapWorkerTask(imageView); task.execute(resId); }
二). 處理并發(fā)
當(dāng)一些普通的View組件,如: ListView 和 GridView 等和 AsyncTask 配合使用時(shí),會引入另一個之前章節(jié)講過的問題。為了讓存儲使用更高效,這些組件會在用戶滾動窗口時(shí)回收自己的子View。如果沒一個子View都激活一個 AsyncTask ,那么當(dāng)執(zhí)行完畢后,相關(guān)聯(lián)的view是否會因?yàn)榱硪粋€子view也引用同樣的對象而不被回收,這一方面是沒有保證的。另外,異步任務(wù)結(jié)束的順序是否和開始的順序保持一致,這一點(diǎn)也未必。
在這篇博客: Multithreading for Performance 中進(jìn)一步討論了處理并發(fā)的問題,并提供了一種解決方案,這個方案能讓 ImageView 存儲一個最新的 AsyncTask 引用,同時(shí)在任務(wù)執(zhí)行完畢后可以對其進(jìn)行檢查。還是像之前章節(jié)那樣類似的方法,對 AsyncTask 進(jìn)行一些擴(kuò)展。
創(chuàng)建一個專用的
Drawable
子類,用來存儲“WorkerTask”的引用。在這個例子中,一個
BitmapDrawable
被使用到,這樣的話一個“占位符式的”圖片就能在任務(wù)完成之前被顯示:
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
之前,創(chuàng)建一個
AsyncDrawable
并將它和目標(biāo)
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 方法用來檢查是否另一個正在運(yùn)行的任務(wù)已經(jīng)關(guān)聯(lián)了這個 ImageView 。如果是的話,它嘗試通過調(diào)用 cancel() 方法取消之前的任務(wù)。在一些個別情況中,新的任務(wù)數(shù)據(jù)會和已經(jīng)存在的任務(wù)相符合,那么就沒有其他的事情取藥發(fā)生。下面的代碼是 cancelPotentialWork 方法的實(shí)現(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 != 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(),被用來獲取與任務(wù)相關(guān)聯(lián)的一個特定 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()方法,這樣它就能檢查任務(wù)是否取消了以及當(dāng)前的任務(wù)是否和
ImageView
所關(guān)聯(lián)的數(shù)據(jù)相匹配:
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> { ... @Override protected 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)在這個實(shí)現(xiàn)對于 ListView 和 GridView 和其它需要回收子view的組件來說,就變的更加合適了。只需要調(diào)用 loadBitmap()就可以對你的 ImageView 設(shè)置圖片。例如,在一個 GridView 的實(shí)現(xiàn)中,是在其對應(yīng)適配器的 getView() 方法中執(zhí)行。
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長非常感激您!手機(jī)微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
