高效地顯示Bitmap圖片 2 - 在UI線程之外處理Bitmaps
Processing Bitmaps Off the UI Thread [在UI Thread之外處理Bitmap]
- 在上一課中有介紹一係列的BitmapFactory.decode* 方法,當數據源是網絡或者是磁盤時(或者是任何實際源不在內存的),這些方法都不應該在main UI 線程中執行。那些情況下加載數據是不可以預知的,它依賴於許多因素(從網絡或者硬盤讀取數據的速度, 圖片的大小, CPU的速度, etc.)。如果其中任何一個任務卡住了UI thread, 係統會出現ANR的錯誤。
- 這一節課會介紹如何使用 AsyncTask 在後台線程中處理bitmap並且演示了如何處理並發(concurrency)的問題。
Use an AsyncTask [使用AsyncTask]
AsyncTask
類提供了一個簡單的方法來在後台線程執行一些操作,並且可以把後台的結果呈現到UI線程。下麵是一個加載大圖的示例:- 關於如何使用AsyncTask,我的博文裏麵有一篇文章也有簡單介紹,【Android】使用AsyncTask來處理一些簡單的需要後台處理的動作
- class BitmapWorkerTask extends AsyncTask {
- private final WeakReference imageViewReference;
- private int data = 0;
- public BitmapWorkerTask(ImageView imageView) {
- // Use a WeakReference to ensure the ImageView can be garbage collected
- imageViewReference = new WeakReference(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
所引用的資源可以被GC(garbage collected)。因為當任務結束時不能確保ImageView
仍然存在,因此你必須在onPostExecute() 裏麵去檢查引用
. 這個ImageView
也許已經不存在了,例如,在任務結束時用戶已經不在那個Activity或者是設備已經發生配置改變(旋轉屏幕等)。 - 開始異步加載位圖,隻需要創建一個新的任務並執行它即可:
- public void loadBitmap(int resId, ImageView imageView) {
- BitmapWorkerTask task = new BitmapWorkerTask(imageView);
- task.execute(resId);
- }
Handle Concurrency [處理並發問題]
- 通常類似
ListView
與GridView
等視圖組件在使用上麵演示的AsyncTask 方法時會同時帶來另外一個問題。為了更有效的處理內存,那些視圖的子組件會在用戶滑動屏幕時被循環使用(關於這個原理請參考【Android】ListView中getView的原理與解決多輪重複調用的方法). 如果每一個子視圖都觸發一個AsyncTask ,
那麼就無法確保當前視圖在結束時,分配的視圖已經進入循環隊列中給另外一個子視圖進行重用。而且, 無法確保所有的異步任務能夠按順序執行完畢。 - Multithreading for Performance 這篇博文更進一步的討論了如何處理並發並且提供了一種解決方法,當任務結束時
ImageView
保存一個最近常使用的AsyncTask引用。(暫時打不開那篇博文,下次試試看,仔細看下) 。使用類似的方法,AsyncTask
可以擴展出一個類似的模型. - 創建一個專用的
Drawable
子類來保存一個可以回到當前工作任務的引用。 在這種情況下,BitmapDrawable
被用來作為占位圖片,它可以在任務結束時顯示到ImageView中。
- static class AsyncDrawable extends BitmapDrawable {
- private final WeakReference bitmapWorkerTaskReference;
- public AsyncDrawable(Resources res, Bitmap bitmap,
- BitmapWorkerTask bitmapWorkerTask) {
- super(res, bitmap);
- bitmapWorkerTaskReference =
- new WeakReference(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() 方法來取消之前的一個任務
. 在小部分情況下, New出來的任務有可能已經存在,這樣就不需要執行這個任務了。下麵演示了如何實現一個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()
方法裏麵做更新操作:
- class BitmapWorkerTask extends AsyncTask {
- ...
- @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);
- }
- }
- }
- }
最後更新:2017-04-04 07:03:06