Android中高效的顯示圖片 - 非UI線程加載

AsyncTask

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中高效的顯示圖片》專題中的第二篇

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末桐智,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子烟馅,更是在濱河造成了極大的恐慌说庭,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件郑趁,死亡現(xiàn)場離奇詭異刊驴,居然都是意外死亡,警方通過查閱死者的電腦和手機穿撮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來痪欲,“玉大人悦穿,你說我怎么就攤上這事∫堤撸” “怎么了栗柒?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長知举。 經(jīng)常有香客問我瞬沦,道長,這世上最難降的妖魔是什么雇锡? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任逛钻,我火速辦了婚禮,結果婚禮上锰提,老公的妹妹穿的比我還像新娘曙痘。我一直安慰自己,他們只是感情好立肘,可當我...
    茶點故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布边坤。 她就那樣靜靜地躺著,像睡著了一般谅年。 火紅的嫁衣襯著肌膚如雪茧痒。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天融蹂,我揣著相機與錄音旺订,去河邊找鬼弄企。 笑死,一個胖子當著我的面吹牛耸峭,可吹牛的內(nèi)容都是我干的桩蓉。 我是一名探鬼主播,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼劳闹,長吁一口氣:“原來是場噩夢啊……” “哼院究!你這毒婦竟也來了?” 一聲冷哼從身側響起本涕,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤业汰,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后菩颖,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體样漆,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年晦闰,在試婚紗的時候發(fā)現(xiàn)自己被綠了放祟。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡呻右,死狀恐怖跪妥,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情声滥,我是刑警寧澤眉撵,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站落塑,受9級特大地震影響纽疟,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜憾赁,卻給世界環(huán)境...
    茶點故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一污朽、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧龙考,春花似錦膘壶、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至柬赐,卻和暖如春亡问,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工州藕, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留束世,地道東北人。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓床玻,卻偏偏與公主長得像毁涉,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子锈死,可洞房花燭夜當晚...
    茶點故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內(nèi)容