求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
  
 
 
     
   
分享到
android中listview,gridview加载图片的线程并发解决方案
 

作者:jopen,发布于2012-10-16,来源:OPEN经验库

 

如何处理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, cookie); 
      } 
         } 
     } 
 } 

首先我们先通过cancelPotentialDownload方法去判断imageView是否有线程正在为它下载图片资源,如果有现在正在下载,那么判断下载的这个图片资源(url)是否和现在的图片资源一样,不一样则取消之前的线程(之前的下载线程作废)。cancelPotentialDownload方法代码如下:

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代码如下:

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); 
     } 
 } 

相关文章

深度解析:清理烂代码
如何编写出拥抱变化的代码
重构-使代码更简洁优美
团队项目开发"编码规范"系列文章
相关文档

重构-改善既有代码的设计
软件重构v2
代码整洁之道
高质量编程规范
相关课程

基于HTML5客户端、Web端的应用开发
HTML 5+CSS 开发
嵌入式C高质量编程
C++高级编程

 
分享到
 
 
     


android人机界面指南
Android手机开发(一)
Android手机开发(二)
Android手机开发(三)
Android手机开发(四)
iPhone消息推送机制实现探讨
手机软件测试用例设计实践
手机客户端UI测试分析
手机软件自动化测试研究报告
更多...   


Android高级移动应用程序
Android应用开发
Android系统开发
手机软件测试
嵌入式软件测试
Android软、硬、云整合


领先IT公司 android开发平台最佳实践
北京 Android开发技术进阶
某新能源领域企业 Android开发技术
某航天公司 Android、IOS应用软件开发
阿尔卡特 Linux内核驱动
艾默生 嵌入式软件架构设计
西门子 嵌入式架构设计
更多...