如何处理listview的下载图片时候多线程并发问题,我这里参考了一些网络的资源和项目,总结了一下。希望能对有这些方面疑惑的朋友有所帮助。(listview和gridview,viewpager同一个道理,大家举一反三)。
这个通过http去网络下载图片的功能很简单,我是直接从别的文章里复制过来的,不懂的可以给我留言。
这个BitmapDownloadTask类是一个AsyncTask ,他的主要工作就是去网络下载图片并显示在imageview上。代码如下:
这个BitmapDownloaderTask 里面的doInBackground方法是在子线程运行,而onPostExecute是在主线程运行,doInBackground执行的结果返回给onPostExecute。关于更多的AsyncTask 相关技术和参考android的帮助文档(这个技术点不是本章要讨论的内容)。
到目前为止,我们已经可以实现了通过异步任务去网络下载图片,并显示在imageview上的功能了。
首先我们先通过cancelPotentialDownload方法去判断imageView是否有线程正在为它下载图片资源,如果有现在正在下载,那么判断下载的这个图片资源(url)是否和现在的图片资源一样,不一样则取消之前的线程(之前的下载线程作废)。cancelPotentialDownload方法代码如下:
当 bitmapDownloaderTask.cancel(true)被执行的时候,则BitmapDownloaderTask 就会被取消,当BitmapDownloaderTask 的执行到onPostExecute的时候,如果这个任务加载到了图片,它也会把这个bitmap设为null了。
DownloadedDrawable是我们自定义的一个类,它的主要功能是记录了下载的任务,并被设置到imageview中,代码如下:
最后, 我们回来修改BitmapDownloaderTask 的onPostExecute 方法:
这里涉及到三个知识点:
1、通过网络下载图片资源。
2、异步任务显示在UI线程上。
3、解决当用户随意滑动的时候解决多线程并发的问题(这个问题是本教程要解决的重点)
通过网络下载图片资源
这个这个很简单,这里给出了一种解决方案:
01 |
static Bitmap
downloadBitmap(String url) { |
02 |
final AndroidHttpClient
client = AndroidHttpClient.newInstance( "Android" ); |
03 |
final HttpGet
getRequest = new HttpGet(url); |
04 |
05 |
try { |
06 |
HttpResponse
response = client.execute(getRequest); |
07 |
final int statusCode
= response.getStatusLine().getStatusCode(); |
08 |
if (statusCode
!= HttpStatus.SC_OK) { |
09 |
Log.w( "ImageDownloader" , "Error
" +
statusCode + "
while retrieving bitmap from " +
url); |
10 |
return null ; |
11 |
} |
12 |
|
13 |
final HttpEntity
entity = response.getEntity(); |
14 |
if (entity
!= null )
{ |
15 |
InputStream
inputStream = null ; |
16 |
try { |
17 |
inputStream
= entity.getContent(); |
18 |
final Bitmap
bitmap = BitmapFactory.decodeStream(inputStream); |
19 |
return bitmap; |
20 |
} finally { |
21 |
if (inputStream
!= null )
{ |
22 |
inputStream.close(); |
23 |
} |
24 |
entity.consumeContent(); |
25 |
} |
26 |
} |
27 |
} catch (Exception
e) { |
28 |
//
Could provide a more explicit error message for IOException or IllegalStateException |
29 |
getRequest.abort(); |
30 |
Log.w( "ImageDownloader" , "Error
while retrieving bitmap from " +
url, e.toString()); |
31 |
} finally { |
32 |
if (client
!= null )
{ |
33 |
client.close(); |
34 |
} |
35 |
} |
36 |
return null ; |
37 |
} |
在异步任务把图片显示在主线程上
在上面中,我们已经实现了从网络下载一张图片,接下来,我们要在异步任务中把图片显示在UI主线程上。在android系统中,android给我们提供了一个异步任务类:AsyncTask ,它提供了一个简单的方法然给我们的子线程和主线程进行交互。
现在我们来建立一个ImageLoader类,这个类有一个loadImage方法来加载网络图片,并显示在android的Imageview控件上。
1 |
public class ImageLoader
{ |
2 |
3 |
public void loadImage(String
url, ImageView imageView) { |
4 |
BitmapDownloaderTask
task = new BitmapDownloaderTask(imageView); |
5 |
task.execute(url); |
6 |
} |
7 |
} |
8 |
9 |
} |
01 |
class BitmapDownloaderTask extends AsyncTask<String,
Void, Bitmap> { |
02 |
private String
url; |
03 |
private final WeakReference<ImageView>
imageViewReference; |
04 |
05 |
public BitmapDownloaderTask(ImageView
imageView) { |
06 |
imageViewReference
= new WeakReference<ImageView>(imageView); |
07 |
} |
08 |
09 |
@Override |
10 |
//
Actual download method, run in the task thread |
11 |
protected Bitmap
doInBackground(String... params) { |
12 |
//
params comes from the execute() call: params[0] is the url. |
13 |
return downloadBitmap(params[ 0 ]); |
14 |
} |
15 |
16 |
@Override |
17 |
//
Once the image is downloaded, associates it to the imageView |
18 |
protected void onPostExecute(Bitmap
bitmap) { |
19 |
if (isCancelled())
{ |
20 |
bitmap
= null ; |
21 |
} |
22 |
23 |
if (imageViewReference
!= null )
{ |
24 |
ImageView
imageView = imageViewReference.get(); |
25 |
if (imageView
!= null )
{ |
26 |
imageView.setImageBitmap(bitmap); |
27 |
} |
28 |
} |
29 |
} |
30 |
} |
到目前为止,我们已经可以实现了通过异步任务去网络下载图片,并显示在imageview上的功能了。
多线程并发处理
在上面中虽然我们实现了子线程下载图片并显示在imageview的功能,但是在listview等容器中,当用户随意滑动的时候,将会产生N个线程去下载图片,这个是我们不想看到的。我们希望的是一个图片只有一个线程去下载就行了。
为了解决这个问题,我们应该做的是让这个imageview记住它是否正在加载(或者说是下载)网络的图片资源。如果正在加载,或者加载完成,那么我就不应该再建立一个任务去加载图片了。
现在我们把修改如下:
01 |
public class ImageLoader
{ |
02 |
03 |
public void loadImage(String
url, ImageView imageView) { |
04 |
if (cancelPotentialDownload(url,
imageView)) { |
05 |
BitmapDownloaderTask
task = new BitmapDownloaderTask(imageView); |
06 |
DownloadedDrawable
downloadedDrawable = new DownloadedDrawable(task); |
07 |
imageView.setImageDrawable(downloadedDrawable); |
08 |
task.execute(url,
imageView); |
09 |
} |
10 |
} |
11 |
} |
12 |
13 |
} |
首先我们先通过cancelPotentialDownload方法去判断imageView是否有线程正在为它下载图片资源,如果有现在正在下载,那么判断下载的这个图片资源(url)是否和现在的图片资源一样,不一样则取消之前的线程(之前的下载线程作废)。cancelPotentialDownload方法代码如下:
01 |
private static boolean cancelPotentialDownload(String
url, ImageView imageView) { |
02 |
BitmapDownloaderTask
bitmapDownloaderTask = getBitmapDownloaderTask(imageView); |
03 |
04 |
if (bitmapDownloaderTask
!= null )
{ |
05 |
String
bitmapUrl = bitmapDownloaderTask.url; |
06 |
if ((bitmapUrl
== null )
|| (!bitmapUrl.equals(url))) { |
07 |
bitmapDownloaderTask.cancel( true ); |
08 |
} else { |
09 |
//
相同的url已经在下载中. |
10 |
return false ; |
11 |
} |
12 |
} |
13 |
return true ; |
14 |
} |
getBitmapDownloaderTask代码如下:
01 |
private static BitmapDownloaderTask
getBitmapDownloaderTask(ImageView imageView) { |
02 |
if (imageView
!= null )
{ |
03 |
Drawable
drawable = imageView.getDrawable(); |
04 |
if (drawable instanceof DownloadedDrawable)
{ |
05 |
DownloadedDrawable
downloadedDrawable = (DownloadedDrawable)drawable; |
06 |
return downloadedDrawable.getBitmapDownloaderTask(); |
07 |
} |
08 |
} |
09 |
return null ; |
10 |
} |
01 |
static class DownloadedDrawable extends ColorDrawable
{ |
02 |
private final WeakReference<BitmapDownloaderTask>
bitmapDownloaderTaskReference; |
03 |
04 |
public DownloadedDrawable(BitmapDownloaderTask
bitmapDownloaderTask) { |
05 |
super (Color.BLACK); |
06 |
bitmapDownloaderTaskReference
= |
07 |
new WeakReference<BitmapDownloaderTask>(bitmapDownloaderTask); |
08 |
} |
09 |
10 |
public BitmapDownloaderTask
getBitmapDownloaderTask() { |
11 |
return bitmapDownloaderTaskReference.get(); |
12 |
} |
13 |
} |
最后, 我们回来修改BitmapDownloaderTask 的onPostExecute 方法:
1 |
if (imageViewReference
!= null )
{ |
2 |
ImageView
imageView = imageViewReference.get(); |
3 |
BitmapDownloaderTask
bitmapDownloaderTask = getBitmapDownloaderTask(imageView); |
4 |
//
Change bitmap only if this process is still associated with it |
5 |
if ( this ==
bitmapDownloaderTask) { |
6 |
imageView.setImageBitmap(bitmap); |
7 |
} |
8 |
} |
后记
在上面中,虽然我们解决了多线程的并发问题,但是,相信你一定也能看出有很多不足的地方,比如说,没有缓存,没有考虑内存问题等等,不过这个已经不是本章要讨论的内容了(不过不要失望,在后边的章节中,我将会一一为你讲解),通过这个简单的入门,相信你一定知道怎么去解决listview,gridview的线程并发问题了吧。
还有问题?
联系我吧,QQ群:130112760(老罗的Android之旅),个人网站:http://www.devChina.com ,或者csdn中给我留言。
欢迎转载,转载注明出处:http://blog.youkuaiyun.com/michael_yy/article/details/8012308