轉載聲明:Ryan的博客文章歡迎您的轉載,但在轉載的同時,請註明文章的來源出處,不勝感激! :-) html
http://my.oschina.net/ryanhoo/blog/88344
java
譯者:Ryan Hoo android
來源:https://developer.android.com/develop/index.html
網絡
譯者按: 在Google最新的文檔中,提供了一系列含金量至關高的教程。由於種種緣由而不爲人知,真是惋惜!Ryan將會細心整理,將之翻譯成中文,但願對開發者有所幫助。 併發
本系列是Google關於展現大Bitmap(位圖)的官方演示,能夠有效的解決內存限制,更加有效的加載並顯示圖片,同時避免讓人頭疼的OOM(Out Of Memory)。
異步
------------------------------------------------------------------------------------- async
譯文: ide
在高效地加載Bitmap中,咱們討論了BitmapFactory.decode*系列方法,若是源數據來自硬盤或者網絡(或者除內存以外的來源),是不該該在主UI線程執行的。這是由於讀取這樣的數據所需的加載時間是不肯定的,它依賴於多種因素(從硬盤或網絡的讀取速度、圖片的大小、CPU的功率等等)。若是這些任務裏面任何一個阻塞了UI線程,系統會將你的應用標記爲未響應,而且用戶能夠選擇關閉應用(更多信息,請參閱Designing for Responsiveness)。 this
這節課將教會你使用AsyncTask在後臺線程處理Bitmap並向你展現如何處理併發問題。 spa
使用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或者應用配置發生了變化(橫屏)。
爲了異步加載Bitmap,咱們建立一個簡單的異步任務而且執行它:
public void loadBitmap(int resId, ImageView imageView) { BitmapWorkerTask task = new BitmapWorkerTask(imageView); task.execute(resId); }
處理併發
常見的View(視圖)組件如ListView和GridView在於AsyncTask配合使用的時候引出了另一個問題,這個咱們在上一節中提到過。爲了提高內存效率,當用戶滾動這些組件的時候進行子視圖的回收(主要是回收不可見的視圖)。若是每一個子視圖都觸發了一個AsyncTask,沒法保證在任務完成的時候,關聯視圖尚未被回收而被用來顯示另外一個子視圖。此外,也沒法保證異步任務結束的循序與它們開始的順序一致。
Multithreading for Performance這篇文章深刻討論瞭如何處理併發問題,而且給出瞭如何在任務結束的時候檢測ImageView存儲最近使用的AsyncTask引用的解決方案。使用類似的方法,能夠遵循相似的模式來擴展前面的AsyncTask。
建立一個專用的Drawable之類,用來存儲worker task的引用。在這種狀況下,任務結束的時候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有關聯。若是有關聯,它將經過調用canceel()方法試圖取消以前的任務。在少數狀況下,新的任務中的數據與現有的任務相匹配,所以不須要作什麼。下面是calcelPotentialWork的具體實現:
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以及其餘任何須要回收子視圖的組件中。當你只須要爲ImageView設置圖片,調用loadBitmap就能夠了。例如,在GridView中實現的方式是在Adapter的getView()方法中。