在Android中高效的顯示圖片 - 加載大圖一文中講到了BitmapFactory.decode*方法的使用删窒,但使用時需要注意不應該在UI線程中調(diào)用它們來從硬盤裂垦、網(wǎng)絡或者其他非內(nèi)存的地方加載圖片。因為加載圖片所需要的時間是不可預測的肌索,它跟很多因素有關蕉拢,比如網(wǎng)絡狀況、硬盤讀寫速度诚亚、圖片的大小晕换、CPU的速度等。如果我們阻塞UI線程來加載圖片有可能會導致ANR站宗。
(本文出處:http://www.reibang.com/p/adf6c5cf4fbd)
使用AsyncTask加載圖片
解決方法就是開啟一個后臺線程來異步加載圖片届巩。使用Android API提供的AsyncTask類可以很方便的在后臺線程中完成圖片加載然后將結果返回給UI線程。下面就是一個使用AsyncTask來異步加載圖片的例子份乒。
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恕汇。例如腕唧,在任務完成之前用戶退出了這個Activity,那么這個ImageView是應該被釋放的瘾英。由于異步的關系我們無法保證任務執(zhí)行完成后ImageView仍然存在枣接,所以需要在onPostExecute方法里檢查ImageView的引用。
并發(fā)
例如ListView缺谴、GridView這類視圖組件結合上面的AsyncTask又會引入另一個問題但惶。為了高效的使用內(nèi)存,這類組件在滑動的時候會復用子view湿蛔,如果一個view觸發(fā)一個AsyncTask膀曾,那我們無法保證任務執(zhí)行完成后view沒有被復用。如果view被復用從而觸發(fā)兩次AsyncTask阳啥,我們也無法保證異步任務的執(zhí)行順序添谊,很有可能先觸發(fā)的任務后執(zhí)行完成,這就會導致結果錯誤察迟。
這里提供的解決方案是在ImageView中綁定最后觸發(fā)的AsyncTask的引用斩狱,當任務執(zhí)行完成后返回結果時再比較返回結果的任務是不是ImageView綁定的任務,這樣就可以保證Imageview顯示的結果就是它最后觸發(fā)的AsyncTask的結果扎瓶。
ImageView是系統(tǒng)的一個類所踊,他并沒有給我們預設一個屬性讓我們來記錄AsyncTask,那么我們?nèi)绾尾拍軐syncTask綁定到ImageView中去呢概荷?當然可以繼承ImageView來自定義一個包含AsyncTask字段的AsyncImageView秕岛,但是這樣可能會影響到布局文件。這里使用了另外一種實現(xiàn)方式误证。大家都知道ImageView有一個setImageDrawable(BitmapDrawable b)的方法瓣蛀,這就說明ImageView可以保存一個BitmapDrawable變量,如果我們能將AsyncTask放到BitmapDrawable雷厂,那么實際上AsyncTask也就放到ImageView里了惋增。所以我們只需要繼承BitmapDrawable實現(xiàn)一個AsyncDrawable,把這個AsyncDrawable設置給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();
}
}
執(zhí)行BitmapWorkerTask之前先創(chuàng)建一個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上是不是有正在運行的異步任務像棘,如果有且上一個任務與當前請求的任務是同一個任務就直接返回false避免重復請求稽亏,如果有且任務不一樣就取消上一個任務。
public static boolean cancelPotentialWork(int data, ImageView imageView) {
final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
if (bitmapWorkerTask != null) {
final int bitmapData = bitmapWorkerTask.data;
// If bitmapData is not yet set or it differs from the new data
if (bitmapData == 0 || 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里獲取綁定的AsyncTask的方法缕题。
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()方法截歉,檢查任務是否已經(jīng)取消,是否是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);
}
}
}
}
現(xiàn)在BitmapWorkerTask可用在ListView瘪松、GradView或者別的復用子view的組件上完美運行了咸作。只需要在你想要給ImageView設置圖片的地方調(diào)用loadBitmap即可。例如宵睦,在GridView的適配器的getView方法里調(diào)用loadBitmap給子view設置圖片內(nèi)容记罚。
總結
本文實現(xiàn)了后臺線程加載圖片的功能。并對因多線程引入的并發(fā)問題給出了解決方案壳嚎。
本文是《Android中高效的顯示圖片》專題中的第二篇
- Android中高效的顯示圖片 - 加載大圖
- Android中高效的顯示圖片 - 非UI線程加載
- Android中高效的顯示圖片 - 圖片緩存
- Android中高效的顯示圖片 - Bitmap的內(nèi)存模型