在Google最新的文件中,提供了一系列含金量相當高的教程。因為種種原因而鮮為人知,真是可惜!Ryan將會細心整理,將之翻譯成中文,希望對開發者有所幫助。
本系列是Google關於展示大Bitmap(點陣圖)的官方演示,可以有效的解決記憶體限制,更加有效的載入並顯示圖片,同時避免讓人頭疼的OOM(Out Of Memory)。
-------------------------------------------------------------------------------------
譯文:
在高效地載入Bitmap中,我們討論了BitmapFactory.decode*系列方法,如果源資料來自硬碟或者網路(或者除記憶體之外的來源),是不應該在主UI執行緒執行的。這是因為讀取這樣的資料所需的載入時間是不確定的,它依賴於多種因素(從硬碟或網路的讀取速度、圖片的大小、CPU的功率等等)。如果這些任務裡面任何一個阻塞了UI執行緒,系統會將你的應用標記為未響應,並且使用者可以選擇關閉應用(更多資訊,請參閱Designing
for Responsiveness)。
這節課將教會你使用AsyncTask在後臺執行緒處理Bitmap並向你展示如何處理併發問題。
使用AsyncTask(非同步任務)
AsyncTask類提供了一種簡單的方法,可以在後來執行緒處理一些事情,並將結果返回到UI執行緒。要使用它,需要建立一個繼承於它的子類,並且覆寫它提供的方法。這裡有一個使用AsyncTask和decodeSampledBitmapFromResource()載入大圖片到ImageView中的例子:
01 |
class BitmapWorkerTask extends AsyncTask<Integer,
Void, Bitmap> { |
02 |
private final WeakReference<ImageView>
imageViewReference; |
05 |
public BitmapWorkerTask(ImageView
imageView) { |
07 |
imageViewReference
= new WeakReference<ImageView>(imageView); |
12 |
protected Bitmap
doInBackground(Integer... params) { |
14 |
return decodeSampledBitmapFromResource(getResources(),
data, 100 , 100 )); |
19 |
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); |
ImageView的WeakReference(弱引用)可以確保AsyncTask不會阻止ImageView和它的任何引用被垃圾回收器回收。不能保證在非同步任務完成後ImageView依然存在,因此你必須在onPostExecute()方法中檢查引用。ImageView可能已經不存在了,比如說,使用者在任務完成前退出了當前Activity或者應用配置發生了變化(橫屏)。
為了非同步載入Bitmap,我們建立一個簡單的非同步任務並且執行它:
1 |
public void loadBitmap( int resId,
ImageView imageView) { |
2 |
BitmapWorkerTask
task = new BitmapWorkerTask(imageView); |
處理併發
常見的View(檢視)元件如ListView和GridView在於AsyncTask配合使用的時候引出了另外一個問題,這個我們在上一節中提到過。為了提升記憶體效率,當使用者滾動這些元件的時候進行子檢視的回收(主要是回收不可見的檢視)。如果每個子檢視都觸發了一個AsyncTask,無法保證在任務完成的時候,關聯檢視還沒有被回收而被用來顯示另一個子檢視。此外,也無法保證非同步任務結束的循序與它們開始的順序一致。
Multithreading
for Performance這篇文章深入討論瞭如何處理併發問題,並且給出瞭如何在任務結束的時候檢測ImageView儲存最近使用的AsyncTask引用的解決方案。使用相似的方法,可以遵循類似的模式來擴充套件前面的AsyncTask。
建立一個專用的Drawable之類,用來儲存worker task的引用。在這種情況下,任務結束的時候BitmapDrawable可以取代影象佔位符顯示在ImageView中。
01 |
static class AsyncDrawable extends BitmapDrawable
{ |
02 |
private final WeakReference<BitmapWorkerTask>
bitmapWorkerTaskReference; |
04 |
public AsyncDrawable(Resources
res, Bitmap bitmap, |
05 |
BitmapWorkerTask
bitmapWorkerTask) { |
07 |
bitmapWorkerTaskReference
= |
08 |
new WeakReference<BitmapWorkerTask>(bitmapWorkerTask); |
11 |
public BitmapWorkerTask
getBitmapWorkerTask() { |
12 |
return bitmapWorkerTaskReference.get(); |
在執行BitmapWorkerTask前,你需要建立一個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); |
在上面的程式碼示例中引用的cancelPotentialWork方法可以檢測一個執行中的任務是否與ImageView有關聯。如果有關聯,它將通過呼叫canceel()方法試圖取消之前的任務。在少數情況下,新的任務中的資料與現有的任務相匹配,因此不需要做什麼。下面是calcelPotentialWork的具體實現:
01 |
public static boolean cancelPotentialWork( int data,
ImageView imageView) { |
02 |
final BitmapWorkerTask
bitmapWorkerTask = getBitmapWorkerTask(imageView); |
04 |
if (bitmapWorkerTask
!= null )
{ |
05 |
final int bitmapData
= bitmapWorkerTask.data; |
06 |
if (bitmapData
!= data) { |
08 |
bitmapWorkerTask.cancel( true ); |
一個助手方法,getBitmapWorkerTask(),在上面用來檢索和指定ImageView相關的任務
01 |
private static BitmapWorkerTask
getBitmapWorkerTask(ImageView imageView) { |
02 |
if (imageView
!= null )
{ |
03 |
final Drawable
drawable = imageView.getDrawable(); |
04 |
if (drawable instanceof AsyncDrawable)
{ |
05 |
final AsyncDrawable
asyncDrawable = (AsyncDrawable) drawable; |
06 |
return asyncDrawable.getBitmapWorkerTask(); |
最後一步是更新BitmapWorkerTask中的onPostExecute()方法,以便檢測與ImageView關聯的任務是否被取消或者與當前任務相匹配。
01 |
class BitmapWorkerTask extends AsyncTask<Integer,
Void, Bitmap> { |
05 |
protected void onPostExecute(Bitmap
bitmap) { |
10 |
if (imageViewReference
!= null &&
bitmap != null )
{ |
11 |
final ImageView
imageView = imageViewReference.get(); |
12 |
final BitmapWorkerTask
bitmapWorkerTask = |
13 |
getBitmapWorkerTask(imageView); |
14 |
if ( this ==
bitmapWorkerTask && imageView != null )
{ |
15 |
imageView.setImageBitmap(bitmap); |
這裡的方法也適合用在ListView、GridView以及其他任何需要回收子檢視的元件中。當你只需要為ImageView設定圖片,呼叫loadBitmap就可以了。例如,在GridView中實現的方式是在Adapter的getView()方法中。