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

jopen 12年前發布 | 42K 次閱讀 Android Android開發 移動開發

如何處理listview的下載圖片時候多線程并發問題,我這里參考了一些網絡的資源和項目,總結了一下。希望能對有這些方面疑惑的朋友有所幫助。(listview和gridview,viewpager同一個道理,大家舉一反三)。

               這里涉及到三個知識點:

1、通過網絡下載圖片資源。

                2、異步任務顯示在UI線程上。

                3、解決當用戶隨意滑動的時候解決多線程并發的問題(這個問題是本教程要解決的重點)

 

通過網絡下載圖片資源

這個這個很簡單,這里給出了一種解決方案:
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控件上。
public class ImageLoader {

    public void loadImage(String url, ImageView imageView) {
            BitmapDownloaderTask task = new BitmapDownloaderTask(imageView);
            task.execute(url);
        }
    }

}
這個BitmapDownloadTask類是一個AsyncTask ,他的主要工作就是去網絡下載圖片并顯示在imageview上。代碼如下:
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記住它是否正在加載(或者說是下載)網絡的圖片資源。如果正在加載,或者加載完成,那么我就不應該再建立一個任務去加載圖片了。
現在我們把修改如下:
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, imageView);
     }
        }
    }

}

首先我們先通過cancelPotentialDownload方法去判斷imageView是否有線程正在為它下載圖片資源,如果有現在正在下載,那么判斷下載的這個圖片資源(url)是否和現在的圖片資源一樣,不一樣則取消之前的線程(之前的下載線程作廢)。cancelPotentialDownload方法代碼如下:
private static boolean cancelPotentialDownload(String url, ImageView imageView) {
    BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView);

    if (bitmapDownloaderTask != null) {
        String bitmapUrl = bitmapDownloaderTask.url;
        if ((bitmapUrl == null) || (!bitmapUrl.equals(url))) {
            bitmapDownloaderTask.cancel(true);
        } else {
            // 相同的url已經在下載中.
            return false;
        }
    }
    return true;
}
當 bitmapDownloaderTask.cancel(true)被執行的時候,則BitmapDownloaderTask 就會被取消,當BitmapDownloaderTask 的執行到onPostExecute的時候,如果這個任務加載到了圖片,它也會把這個bitmap設為null了。
getBitmapDownloaderTask代碼如下:
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中,代碼如下:
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 方法:
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);
    }
}

后記

在上面中,雖然我們解決了多線程的并發問題,但是,相信你一定也能看出有很多不足的地方,比如說,沒有緩存,沒有考慮內存問題等等,不過這個已經不是本章要討論的內容了(不過不要失望,在后邊的章節中,我將會一一為你講解),通過這個簡單的入門,相信你一定知道怎么去解決listview,gridview的線程并發問題了吧。


還有問題?

聯系我吧,QQ群:130112760(老羅的Android之旅),個人網站:http://www.devChina.com ,或者csdn中給我留言。


歡迎轉載,轉載注明出處:http://blog.csdn.net/michael_yy/article/details/8012308

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!