注:本文翻譯自Google官方的Android Developers Training文檔,譯者技術一般,由于喜愛安卓而產生了翻譯的念頭,純屬個人興趣愛好。
原文鏈接: http://developer.android.com/training/displaying-bitmaps/process-bitmap.html
在上節課(博客鏈接: http://www.cnblogs.com/jdneo/p/3514060.html )中所討論的 BitmapFactory.decode* 方法,在數據源是從閃存盤或者網絡(任何非手機存儲的數據來源)讀取的,那么就不能再主UI線程中執行。因為加載這個數據所花費的時間是不可估計的,并且它依賴于虛度因素(從網絡讀取的速度,圖片尺寸,CPU的處理能力,等等)。如果其中一個任務阻塞的UI線程,那么系統將會把你的應用標記為未響應,之后用戶就可以選擇強制關閉它(可以閱讀: Designing for Responsiveness )。
這節課會教你如何在一個后臺線程,使用
AsyncTask
處理圖像,并告訴你如何處理并發問題。
一). 使用一個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 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 也許已經不存在了,就比如說,用戶轉到了其他的activity,或者一個配置的變更在任務完成之前發生了。
要開始異步地加載這個位圖,簡單地創建一個任務的實例并執行它:
public
void
loadBitmap(
int
resId, ImageView imageView) {
BitmapWorkerTask task
=
new
BitmapWorkerTask(imageView);
task.execute(resId);
}
二). 處理并發
當一些普通的View組件,如: ListView 和 GridView 等和 AsyncTask 配合使用時,會引入另一個之前章節講過的問題。為了讓存儲使用更高效,這些組件會在用戶滾動窗口時回收自己的子View。如果沒一個子View都激活一個 AsyncTask ,那么當執行完畢后,相關聯的view是否會因為另一個子view也引用同樣的對象而不被回收,這一方面是沒有保證的。另外,異步任務結束的順序是否和開始的順序保持一致,這一點也未必。
在這篇博客: Multithreading for Performance 中進一步討論了處理并發的問題,并提供了一種解決方案,這個方案能讓 ImageView 存儲一個最新的 AsyncTask 引用,同時在任務執行完畢后可以對其進行檢查。還是像之前章節那樣類似的方法,對 AsyncTask 進行一些擴展。
創建一個專用的
Drawable
子類,用來存儲“WorkerTask”的引用。在這個例子中,一個
BitmapDrawable
被使用到,這樣的話一個“占位符式的”圖片就能在任務完成之前被顯示:
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 !=
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 :
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>
{
...
@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);
}
}
}
}
現在這個實現對于 ListView 和 GridView 和其它需要回收子view的組件來說,就變的更加合適了。只需要調用 loadBitmap()就可以對你的 ImageView 設置圖片。例如,在一個 GridView 的實現中,是在其對應適配器的 getView() 方法中執行。
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061
微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元

