android中listview,gridview加載圖片的線程並發解決方案

            在QQ群裡和論壇裡,有人問如何處理listview的下載圖片時候多線程並發問題,我這裡參考瞭一些網絡的資源和項目,總結瞭一下。希望能對有這些方面疑惑的朋友有所幫助。(listview和gridview,viewpager同一個道理,大傢舉一反三)。
               這裡涉及到三個知識點:
1、通過網絡下載圖片資源。
                2、異步任務顯示在UI線程上。
                3、解決當用戶隨意滑動的時候解決多線程並發的問題(這個問題是本教程要解決的重點)

通過網絡下載圖片資源
這個這個很簡單,這裡給出瞭一種解決方案:
[java] 
static Bitmap downloadBitmap(String url) { 
    final AndroidHttpClient client = AndroidHttpClient.newInstance("Android"); 
    final HttpGet getRequest = new HttpGet(url); 
 
    try { 
        HttpResponse response = client.execute(getRequest); 
        final int statusCode = response.getStatusLine().getStatusCode(); 
        if (statusCode != HttpStatus.SC_OK) {  
            Log.w("ImageDownloader", "Error " + statusCode + " while retrieving bitmap from " + url);  
            return null; 
        } 
         
        final HttpEntity entity = response.getEntity(); 
        if (entity != null) { 
            InputStream inputStream = null; 
            try { 
                inputStream = entity.getContent();  
                final Bitmap bitmap = BitmapFactory.decodeStream(inputStream); 
                return bitmap; 
            } finally { 
                if (inputStream != null) { 
                    inputStream.close();   
                } 
                entity.consumeContent(); 
            } 
        } 
    } catch (Exception e) { 
        // Could provide a more explicit error message for IOException or IllegalStateException 
        getRequest.abort(); 
        Log.w("ImageDownloader", "Error while retrieving bitmap from " + url, e.toString()); 
    } finally { 
        if (client != null) { 
            client.close(); 
        } 
    } 
    return null; 

這個通過http去網絡下載圖片的功能很簡單,我是直接從別的文章裡復制過來的,不懂的可以給我留言。

在異步任務把圖片顯示在主線程上
在上面中,我們已經實現瞭從網絡下載一張圖片,接下來,我們要在異步任務中把圖片顯示在UI主線程上。在android系統中,android給我們提供瞭一個異步任務類:AsyncTask ,它提供瞭一個簡單的方法然給我們的子線程和主線程進行交互。
現在我們來建立一個ImageLoader類,這個類有一個loadImage方法來加載網絡圖片,並顯示在android的Imageview控件上。
[java] 
public class ImageLoader { 
 
    public void loadImage(String url, ImageView imageView) { 
            BitmapDownloaderTask task = new BitmapDownloaderTask(imageView); 
            task.execute(url); 
        } 
    } 
 

這個BitmapDownloadTask類是一個AsyncTask ,他的主要工作就是去網絡下載圖片並顯示在imageview上。代碼如下:
[java] 
class BitmapDownloaderTask extends AsyncTask<String, Void, Bitmap> { 
    private String url; 
    private final WeakReference<ImageView> imageViewReference; 
 
    public BitmapDownloaderTask(ImageView imageView) { 
        imageViewReference = new WeakReference<ImageView>(imageView); 
    } 
 
    @Override 
    // Actual download method, run in the task thread 
    protected Bitmap doInBackground(String… params) { 
         // params comes from the execute() call: params[0] is the url. 
         return downloadBitmap(params[0]); 
    } 
 
    @Override 
    // Once the image is downloaded, associates it to the imageView 
    protected void onPostExecute(Bitmap bitmap) { 
        if (isCancelled()) { 
            bitmap = null; 
        } 
 
        if (imageViewReference != null) { 
            ImageView imageView = imageViewReference.get(); 
            if (imageView != null) { 
                imageView.setImageBitmap(bitmap); 
            } 
        } 
    } 

這個BitmapDownloaderTask 裡面的doInBackground方法是在子線程運行,而onPostExecute是在主線程運行,doInBackground執行的結果返回給onPostExecute。關於更多的AsyncTask 相關技術和參考android的幫助文檔(這個技術點不是本章要討論的內容)。
到目前為止,我們已經可以實現瞭通過異步任務去網絡下載圖片,並顯示在imageview上的功能瞭。

多線程並發處理
在上面中雖然我們實現瞭子線程下載圖片並顯示在imageview的功能,但是在listview等容器中,當用戶隨意滑動的時候,將會產生N個線程去下載圖片,這個是我們不想看到的。我們希望的是一個圖片隻有一個線程去下載就行瞭。
為瞭解決這個問題,我們應該做的是讓這個imageview記住它是否正在加載(或者說是下載)網絡的圖片資源。如果正在加載,或者加載完成,那麼我就不應該再建立一個任務去加載圖片瞭。
現在我們把修改如下:
[java] 
public class ImageLoader { 
 
    public void loadImage(String url, ImageView imageView) { 
            if (cancelPotentialDownload(url, imageView)) { 
         BitmapDownloaderTask task = new BitmapDownloaderTask(imageView); 
         DownloadedDrawable downloadedDrawable = new DownloadedDrawable(task); 
         imageView.setImageDrawable(downloadedDrawable); 
         task.execute(url, cookie); 
     } 
        } 
    } 
 

首先我們先通過cancelPotentialDownload方法去判斷imageView是否有線程正在為它下載圖片資源,如果有現在正在下載,那麼判斷下載的這個圖片資源(url)是否和現在的圖片資源一樣,不一樣則取消之前的線程(之前的下載線程作廢)。cancelPotentialDownload方法代碼如下:
[java]
private static boolean cancelPotentialDownload(String url, ImageView imageView) { 
    BitmapDownloaderTask bitmapDownloaderTask = <span style="color:#cc0000;">getBitmapDownloaderTask(imageView);</span> 
 
    if (bitmapDownloaderTask != null) { 
        String bitmapUrl = bitmapDownloaderTask.url; 
        if ((bitmapUrl == null) || (!bitmapUrl.equals(url))) { 
           <span style="color:#ff6666;"> bitmapDownloaderTask.cancel(true);</span> 
        } else { 
            <span style="color:#ff0000;">// 相同的url已經在下載中. 
            return false;</span> 
        } 
    } 
    return true; 

當 bitmapDownloaderTask.cancel(true)被執行的時候,則BitmapDownloaderTask 就會被取消,當BitmapDownloaderTask 的執行到onPostExecute的時候,如果這個任務加載到瞭圖片,它也會把這個bitmap設為null瞭。
getBitmapDownloaderTask代碼如下:
[java] 
private static BitmapDownloaderTask getBitmapDownloaderTask(ImageView imageView) { 
    if (imageView != null) { 
        Drawable drawable = imageView.getDrawable(); 
        if (drawable instanceof DownloadedDrawable) { 
            DownloadedDrawable downloadedDrawable = (DownloadedDrawable)drawable; 
            return downloadedDrawable.getBitmapDownloaderTask(); 
        } 
    } 
    return null; 

DownloadedDrawable是我們自定義的一個類,它的主要功能是記錄瞭下載的任務,並被設置到imageview中,代碼如下:
[java] 
static class DownloadedDrawable extends ColorDrawable { 
    private final WeakReference<BitmapDownloaderTask> bitmapDownloaderTaskReference; 
 
    public DownloadedDrawable(BitmapDownloaderTask bitmapDownloaderTask) { 
        super(Color.BLACK); 
        bitmapDownloaderTaskReference = 
            new WeakReference<BitmapDownloaderTask>(bitmapDownloaderTask); 
    } 
 
    public BitmapDownloaderTask getBitmapDownloaderTask() { 
        return bitmapDownloaderTaskReference.get(); 
    } 

最後, 我們回來修改BitmapDownloaderTask 的onPostExecute 方法:
[java] 
if (imageViewReference != null) { 
    ImageView imageView = imageViewReference.get(); 
    BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView); 
    // Change bitmap only if this process is still associated with it 
    if (this == bitmapDownloaderTask) { 
        imageView.setImageBitmap(bitmap); 
    } 

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *