您的位置:首页 > 移动开发 > Android开发

android中listview,gridview加载图片的线程并发解决方案

2014-02-12 13:36 190 查看
如何处理listview的下载图片时候多线程并发问题,我这里参考了一些网络的资源和项目,总结了一下。希望能对有这些方面疑惑的朋友有所帮助。(listview和gridview,viewpager同一个道理,大家举一反三)。
这里涉及到三个知识点:

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
}
这个通过http去网络下载图片的功能很简单,我是直接从别的文章里复制过来的,不懂的可以给我留言。

在异步任务把图片显示在主线程上

在上面中,我们已经实现了从网络下载一张图片,接下来,我们要在异步任务中把图片显示在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
}
这个BitmapDownloadTask类是一个AsyncTask ,他的主要工作就是去网络下载图片并显示在imageview上。代码如下:

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
}
这个BitmapDownloaderTask 里面的doInBackground方法是在子线程运行,而onPostExecute是在主线程运行,doInBackground执行的结果返回给onPostExecute。关于更多的AsyncTask 相关技术和参考android的帮助文档(这个技术点不是本章要讨论的内容)。

到目前为止,我们已经可以实现了通过异步任务去网络下载图片,并显示在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
 
DownloadedDrawabledownloadedDrawable =
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
}
当 bitmapDownloaderTask.cancel(true)被执行的时候,则BitmapDownloaderTask 就会被取消,当BitmapDownloaderTask 的执行到onPostExecute的时候,如果这个任务加载到了图片,它也会把这个bitmap设为null了。
getBitmapDownloaderTask代码如下:

01
private
static

BitmapDownloaderTask getBitmapDownloaderTask(ImageView imageView) {
02
if

(imageView !=
null
) {
03
Drawable drawable = imageView.getDrawable();
04
if

(drawable
instanceof
DownloadedDrawable) {
05
DownloadedDrawabledownloadedDrawable = (DownloadedDrawable)drawable;
06
return

downloadedDrawable.getBitmapDownloaderTask();
07
}
08
}
09
return

null
;
10
}
DownloadedDrawable是我们自定义的一个类,它的主要功能是记录了下载的任务,并被设置到imageview中,代码如下:

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的线程并发问题了吧。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐