如何在ListView里加载远程图片?

[来源] 达内    [编辑] 达内   [时间]2012-11-12

ListView在Android应用里扮演非常重要的角色,但很多开发者在使用ListView时都遇到过不少麻烦

ListView在Android应用里扮演非常重要的角色,但很多开发者在使用ListView时都遇到过不少麻烦。一个常见的问题是:列表中要显示一系列记录,每条记录带有一张缩略图(产品照片、用户头像等等),而这个缩略图是通过一个远程URL地址来标识的。这样的应用场景该如何实现呢?

< p style="margin: 15px 0px; padding: 0px; text-indent: 0px; font-size: 14px; line-height: 20px; color: rgb(68, 68, 68); font-family: tahoma, arial, sans-serif; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-align: center; ">

< p style="margin: 15px 0px; padding: 0px; text-indent: 0px; font-size: 14px; line-height: 20px; color: rgb(68, 68, 68); font-family: tahoma, arial, sans-serif; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: -webkit-auto; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); "> 为了避免下载图片带来的延迟,所有远程图片都应该使用异步方式加载,即使用单独的线程下载图片,待 片下载完毕后显示在ImageView里。Android里可以像普通Java一样启动新线程,但当这个线程要更新界面时,必须使用Handler来请求,否则会为应用程序带来潜在危害。

< h1 style="margin: 0px; padding: 10px 5px; font-size: 26px; font-weight: normal; background-color: rgb(34, 40, 42); color: white; font-family: tahoma, arial, sans-serif; font-style: normal; font-variant: normal; letter-spacing: normal; line-height: 18px; orphans: 2; text-align: -webkit-auto; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; ">RemoteImageHelper < p style="margin: 15px 0px; padding: 0px; text-indent: 0px; font-size: 14px; line-height: 20px; color: rgb(68, 68, 68); font-family: tahoma, arial, sans-serif; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: -webkit-auto; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); "> 为了将复杂的逻辑分离,我们单独写一个名为RemoteImageHelper的类来处理“异步下载图片并更新到界面”这个问题,这个类能够实现以下功能:

< ul style="margin: 0px 0px 0px 45px; padding: 0px; word-break: break-all; list-style-type: disc; color: rgb(68, 68, 68); font-family: tahoma, arial, sans-serif; font-size: 12px; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: 18px; orphans: 2; text-align: -webkit-auto; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); ">
  • 图片开始下载前,ImageView里显示一个表示“正在加载”的占位图;
  • 图片在后台下载,下载完成后显示在ImageView里;
  • 若图片下载失败,ImageView显示一个表示下载失败的占位图;
  • < p style="margin: 15px 0px; padding: 0px; text-indent: 0px; font-size: 14px; line-height: 20px; color: rgb(68, 68, 68); font-family: tahoma, arial, sans-serif; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: -webkit-auto; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); ">下面让我们来看一下实现代码:

    < p style="margin: 15px 0px; padding: 0px; text-indent: 0px; font-size: 14px; line-height: 20px; color: rgb(68, 68, 68); font-family: tahoma, arial, sans-serif; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: -webkit-auto; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); "> 首先需要有一个方法下载远程图片,这里我们不用把图片下载到手机上,直接返回一个InputStream类型的结果即可。如果运行时这个方法报错,请检查是否在AndroidManifest.xml里添加了android.permission.INTERNET权限。

    < div style="margin: 5px 0px; padding: 5px; background-color: rgb(245, 245, 245); font-family: 'Courier New'; font-size: 12px; border: 1px solid rgb(204, 204, 204); overflow: auto; color: rgb(68, 68, 68); font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: 18px; orphans: 2; text-align: -webkit-auto; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; " class="cnblogs_code">
    private
     InputStream download(String urlString) throws
    
    
     MalformedURLException, IOException {     InputStream inputStream = (InputStream) new
     URL(urlString).getContent();     
    return
    
     inputStream; }
    < p style="margin: 15px 0px; padding: 0px; text-indent: 0px; font-size: 14px; line-height: 20px; color: rgb(68, 68, 68); font-family: tahoma, arial, sans-serif; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: -webkit-auto; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); "> 然后是最主要的异步加载图片方法,“正在下载”和“下载失败”的图片可根据需要自己替换。代码如下所示:

    < div style="margin: 5px 0px; padding: 5px; background-color: rgb(245, 245, 245); font-family: 'Courier New'; font-size: 12px; border: 1px solid rgb(204, 204, 204); overflow: auto; color: rgb(68, 68, 68); font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: 18px; orphans: 2; text-align: -webkit-auto; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; " class="cnblogs_code">
    private
     final
     Map<String, Drawable> cache = new
    
     HashMap<String, Drawable>();  
    public void loadImage(
    final
    
     ImageView imageView, final
     String urlString, boolean
     useCache) {     
    
    if (useCache && cache.containsKey(urlString)) {         imageView.setImageDrawable(cache.get(urlString));     }      
    //
    Show a "Loading" image here
    
    
        imageView.setImageResource(R.drawable.image_indicator);      Log.d(
    this
    .getClass().getSimpleName(), "Image url:" + urlString);      
    
    final Handler handler = new
     Handler() {         @Override         
    public
    
     void
     handleMessage(Message message) {             imageView.setImageDrawable((Drawable) message.obj);         }     };      Runnable runnable = new
     Runnable() {         public
     void
     run() {             Drawable drawable 
    
    = null;             
    try
     {                 InputStream is 
    
    = download(urlString);                 drawable 
    = Drawable.createFromStream(is, "src");                  
    
    if (drawable != null
    ) {                     cache.put(urlString, drawable);                 }             } 
    catch
     (Exception e) {                 Log.e(
    
    this
    .getClass().getSimpleName(), "Image download failed", e);                 
    
    //
    Show a "download fail" image 
    
    
                    drawable = imageView.getResources().getDrawable(R.drawable.image_fail);             }                          
    //
    Notify UI thread to show this image using Handler
    
    
                Message msg = handler.obtainMessage(1, drawable);             handler.sendMessage(msg);         }     };     
    new Thread(runnable).start(); }
    
    < p style="margin: 15px 0px; padding: 0px; text-indent: 0px; font-size: 14px; line-height: 20px; color: rgb(68, 68, 68); font-family: tahoma, arial, sans-serif; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: -webkit-auto; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); "> 关于缓存:在这个例子里我们使用一个内存中的HashMap作为图片缓存,它实现简单但当应用退出后缓存就会被清除。在实际项目里,你可以考虑实现一个基于文件的缓存机制,即将下载的图片保存到SD卡上,注意要定期清除长期不用的图片以节约存储空间。

    < h1 style="margin: 0px; padding: 10px 5px; font-size: 26px; font-weight: normal; background-color: rgb(34, 40, 42); color: white; font-family: tahoma, arial, sans-serif; font-style: normal; font-variant: normal; letter-spacing: normal; line-height: 18px; orphans: 2; text-align: -webkit-auto; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; ">使用RemoteImageHelper < p style="margin: 15px 0px; padding: 0px; text-indent: 0px; font-size: 14px; line-height: 20px; color: rgb(68, 68, 68); font-family: tahoma, arial, sans-serif; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: -webkit-auto; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); "> 如何使用这个类呢?下面是一个例子。请注意,为了达到更好的演示效果,代码里在调用loadImage()方法时第三个参数用false禁止了图片缓存功能,在实际项目中,你很可能需要改为true来避免重复下载图片以便提高性能。

    < div style="margin: 5px 0px; padding: 5px; background-color: rgb(245, 245, 245); font-family: 'Courier New'; font-size: 12px; border: 1px solid rgb(204, 204, 204); overflow: auto; color: rgb(68, 68, 68); font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: 18px; orphans: 2; text-align: -webkit-auto; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; " class="cnblogs_code">
    List<MyRecord> exampleRecords; LazyImageHelper lazyImageHelper 
    = new LazyImageHelper();  
    class MyAdapter 
    extends
    
     ArrayAdapter<MyRecord> {      
    public MyAdapter(Context context) {         
    super
    (context, R.layout.record_row, R.id.lblLabel, exampleRecords);     }      @Override     
    
    public View getView(int
     position, View convertView, ViewGroup parent) {         View view 
    = super
    
    .getView(position, convertView, parent);         MyRecord record 
    =
    
     getItem(position);          TextView lblLabel = (TextView) view.findViewById(R.id.lblLabel);         ImageView imageView 
    = (ImageView) view.findViewById(R.id.img);          lblLabel.setText(record.getLabel());          
    //
    For demo purpose, cache is DISABLED here.
    
    
            lazyImageHelper.loadImage(imageView, record.getImageUrl(), false
    );          //
    To enable cache, simply use following code:         
    //
    
    lazyImageHelper.loadImage(imageView, record.getImageUrl(), true);
    
    
            return view;     } }
    
    < p style="margin: 15px 0px; padding: 0px; text-indent: 0px; font-size: 14px; line-height: 20px; color: rgb(68, 68, 68); font-family: tahoma, arial, sans-serif; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-align: -webkit-auto; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); "> 以上代码中的MyRecord是一个简单的POJO类,表示一个业务对象,它具有id、label和imageUrl三个属性。你可以在完整的工程代码中找到它。

    资源下载