Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1275968
  • 博文数量: 247
  • 博客积分: 5587
  • 博客等级: 大校
  • 技术积分: 2060
  • 用 户 组: 普通用户
  • 注册时间: 2010-02-24 13:27
文章分类
文章存档

2012年(101)

2011年(44)

2010年(102)

分类: 嵌入式

2012-07-30 14:15:17

ListView是一种可以显示一系列项目并能进行滚动显示的View。在每行里,既可以是简单的文本,也可以是复杂的结构。一般情况下,你都需要保证 ListView运行得很好(即:渲染更快,滚动流畅)。在接下来的内容里,我将就ListView的使用,向大家提供几种解决不同性能问题的解决方案。
如果你想使用ListView,你就不得不使用ListAdapter来显示内容。SDK中,已经有了几种简单实现的Adapter:
Java代码  收藏代码
  1. ·ArrayAdapter (显示数组对象,使用toString()来显示)  
  2. ·SimpleAdapter (显示Maps列表)  
  3. ·SimpleCursorAdapter(显示通过Cursor从DB中获取的信息)  

这些实现对于显示简单的列表来说,非常棒!一旦你的列表比较复杂,你就不得不书写自己的ListAdapter实现。在多数情况下,直接从 ArrayAdapter扩展就能很好地处理一组对象。此时,你需要处理的工作只是告诉系统如何处理列表中的对象。通过重写getView(int, View, ViewGroup)方法即可达到。
在这里,举一个你需要自定义ListAdapter的例子:显示一组图片,图片的旁边有文字挨着。
ListView例:以图片文字方式显示的Youtube搜索结果
图片需要实时从internet上下载下来。让我们先创建一个Class来代表列表中的项目:
Java代码  收藏代码
  1. public class ImageAndText {  
  2.     private String imageUrl;  
  3.     private String text;  
  4.    
  5.     public ImageAndText(String imageUrl, String text) {  
  6.         this.imageUrl = imageUrl;  
  7.         this.text = text;  
  8.     }  
  9.     public String getImageUrl() {  
  10.         return imageUrl;  
  11.     }  
  12.     public String getText() {  
  13.         return text;  
  14.     }  
  15. }  

现在,我们要实现一个ListAdapter,来显示ImageAndText列表。
Java代码  收藏代码
  1. public class ImageAndTextListAdapter extends ArrayAdapter {  
  2.    
  3.     public ImageAndTextListAdapter(Activity activity, List imageAndTexts) {  
  4.         super(activity, 0, imageAndTexts);  
  5.     }  
  6.    
  7.     @Override  
  8.     public View getView(int position, View convertView, ViewGroup parent) {  
  9.         Activity activity = (Activity) getContext();  
  10.         LayoutInflater inflater = activity.getLayoutInflater();  
  11.    
  12.         // Inflate the views from XML  
  13.         View rowView = inflater.inflate(R.layout.image_and_text_row, null);  
  14.         ImageAndText imageAndText = getItem(position);  
  15.    
  16.         // Load the image and set it on the ImageView  
  17.         ImageView imageView = (ImageView) rowView.findViewById(R.id.image);  
  18.         imageView.setImageDrawable(loadImageFromUrl(imageAndText.getImageUrl()));  
  19.    
  20.         // Set the text on the TextView  
  21.         TextView textView = (TextView) rowView.findViewById(R.id.text);  
  22.         textView.setText(imageAndText.getText());  
  23.    
  24.         return rowView;  
  25.     }  
  26.    
  27.     public static Drawable loadImageFromUrl(String url) {  
  28.         InputStream inputStream;  
  29.         try {  
  30.             inputStream = new URL(url).openStream();  
  31.         } catch (IOException e) {  
  32.             throw new RuntimeException(e);  
  33.         }  
  34.         return Drawable.createFromStream(inputStream, "src");  
  35.     }  
  36. }  

这些View都是从“image_and_text_row.xml”XML文件中inflate的:
Xml代码  收藏代码
  1. xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android=""  
  3.               android:orientation="horizontal"  
  4.               android:layout_width="fill_parent"  
  5.               android:layout_height="wrap_content">  
  6.    
  7.         <ImageView android:id="@+id/image"  
  8.                    android:layout_width="wrap_content"  
  9.                    android:layout_height="wrap_content"  
  10.                    android:src="@drawable/default_image"/>  
  11.    
  12.         <TextView android:id="@+id/text"  
  13.                   android:layout_width="wrap_content"  
  14.                   android:layout_height="wrap_content"/>  
  15.    
  16. LinearLayout>  

这个ListAdapter实现正如你所期望的那样,能在ListView中加载ImageAndText。但是,它唯一可用的场合是那些拥有很 少项目、无需滚动即可看到全部的列表。如果ImageAndText列表内容很多的时候,你会看到,滚动起来不是那么的平滑(事实上,远远不是)。
性能改善
上面例子最大的瓶颈是图片需要从internet上下载。因为我们的代码都在UI线程中执行,所以,每当一张图片从网络上下载时,UI就会变得停滞。如果你用3G网络代替WiFi的话,性能情况会变得更糟。
为了避免这种情况,我们想让图片的下载处于单独的线程里,这样就不会过多地占用UI线程。为了达到这一目的,我们可能需要使用为这种情况特意设计 的AsyncTask。实际情况中,你将注意到AsyncTask被限制在10个以内。这个数量是在Android SDK中硬编码的,所以我们无法改变。这对我们来说是一个制限事项,因为常常有超过10个图片同时在下载。
AsyncImageLoader
一个变通的做法是手动的为每个图片创建一个线程。另外,我们还应该使用Handler来将下载的图片invoke到UI线程。我们这样做的原因是 我们只能在UI线程中修改UI。我创建了一个AsyncImageLoader类,利用线程和Handler来负责图片的下载。此外,它还缓存了图片,防 止单个图片被下载多次。
Java代码  收藏代码
  1. public class AsyncImageLoader {  
  2.     private HashMap> imageCache;  
  3.    
  4.     public AsyncImageLoader() {  
  5.         imageCache = new HashMap>();  
  6.     }  
  7.    
  8.     public Drawable loadDrawable(final String imageUrl, final ImageCallback imageCallback) {  
  9.         if (imageCache.containsKey(imageUrl)) {  
  10.             SoftReference softReference = imageCache.get(imageUrl);  
  11.             Drawable drawable = softReference.get();  
  12.             if (drawable != null) {  
  13.                 return drawable;  
  14.             }  
  15.         }  
  16.         final Handler handler = new Handler() {  
  17.             @Override  
  18.             public void handleMessage(Message message) {  
  19.                 imageCallback.imageLoaded((Drawable) message.obj, imageUrl);  
  20.             }  
  21.         };  
  22.         new Thread() {  
  23.             @Override  
  24.             public void run() {  
  25.                 Drawable drawable = loadImageFromUrl(imageUrl);  
  26.                 imageCache.put(imageUrl, new SoftReference(drawable));  
  27.                 Message message = handler.obtainMessage(0, drawable);  
  28.                 handler.sendMessage(message);  
  29.             }  
  30.         }.start();  
  31.         return null;  
  32.     }  
  33.    
  34.     public static Drawable loadImageFromUrl(String url) {  
  35.         // ...  
  36.     }  
  37.    
  38.     public interface ImageCallback {  
  39.         public void imageLoaded(Drawable imageDrawable, String imageUrl);  
  40.     }  
  41. }  

注意:我使用了SoftReference来缓存图片,允许GC在需要的时候可以对缓存中的图片进行清理。它这样工作:
1、调用loadDrawable(ImageUrl, imageCallback),传入一个匿名实现的ImageCallback接口
  2、如果图片在缓存中不存在的话,图片将从单一的线程中下载并在下载结束时通过ImageCallback回调
  3、如果图片确实存在于缓存中,就会马上返回,不会回调ImageCallback
在你的程序中,只能存在一个AsyncImageLoader实例,否则,缓存不能正常工作。在ImageAndTextListAdapter类中,我们可以这样替换:

ImageView imageView = (ImageView) rowView.findViewById(R.id.image);
imageView.setImageDrawable(loadImageFromUrl(imageAndText.getImageUrl()));
换成
final ImageView imageView = (ImageView) rowView.findViewById(R.id.image);
Drawable cachedImage = asyncImageLoader.loadDrawable(imageAndText.getImageUrl(), new ImageCallback() {
    public void imageLoaded(Drawable imageDrawable, String imageUrl) {
        imageView.setImageDrawable(imageDrawable);
    }
});
imageView.setImageDrawable(cachedImage);
使用这个方法,ListView执行得很好了,并且感觉滑动更平滑了,因为UI线程再也不会被图片加载所阻塞。
更好的性能改善
如果你尝试了上面的解决方案,你将注意到ListView也不是100%的平滑,仍然会有些东西阻滞着它的平滑性。这里,还有两个地方可以进行改善:
·findViewById()的昂贵调用
·每次都inflate XML
因此,修改代码如下:
Java代码  收藏代码
  1. public class ImageAndTextListAdapter extends ArrayAdapter {  
  2.    
  3.     private ListView listView;  
  4.     private AsyncImageLoader asyncImageLoader;  
  5.    
  6.     public ImageAndTextListAdapter(Activity activity, List imageAndTexts, ListView listView) {  
  7.         super(activity, 0, imageAndTexts);  
  8.         this.listView = listView;  
  9.         asyncImageLoader = new AsyncImageLoader();  
  10.     }  
  11.    
  12.     @Override  
  13.     public View getView(int position, View convertView, ViewGroup parent) {  
  14.         Activity activity = (Activity) getContext();  
  15.    
  16.         // Inflate the views from XML  
  17.         View rowView = convertView;  
  18.         ViewCache viewCache;  
  19.         if (rowView == null) {  
  20.             LayoutInflater inflater = activity.getLayoutInflater();  
  21.             rowView = inflater.inflate(R.layout.image_and_text_row, null);  
  22.             viewCache = new ViewCache(rowView);  
  23.             rowView.setTag(viewCache);  
  24.         } else {  
  25.             viewCache = (ViewCache) rowView.getTag();  
  26.         }  
  27.         ImageAndText imageAndText = getItem(position);  
  28.    
  29.         // Load the image and set it on the ImageView  
  30.         String imageUrl = imageAndText.getImageUrl();  
  31.         ImageView imageView = viewCache.getImageView();  
  32.         imageView.setTag(imageUrl);  
  33.         Drawable cachedImage = asyncImageLoader.loadDrawable(imageUrl, new ImageCallback() {  
  34.             public void imageLoaded(Drawable imageDrawable, String imageUrl) {  
  35.                 ImageView imageViewByTag = (ImageView) listView.findViewWithTag(imageUrl);  
  36.                 if (imageViewByTag != null) {  
  37.                     imageViewByTag.setImageDrawable(imageDrawable);  
  38.                 }  
  39.             }  
  40.         });  
  41.         imageView.setImageDrawable(cachedImage);  
  42.    
  43.         // Set the text on the TextView  
  44.         TextView textView = viewCache.getTextView();  
  45.         textView.setText(imageAndText.getText());  
  46.    
  47.         return rowView;  
  48.     }  
  49. }  

这里有两点需要注意:第一点是drawable不再是加载完毕后直接设定到ImageView上。正确的ImageView是通过tag查找的, 这是因为我们现在重用了View,并且图片有可能出现在错误的行上。我们需要拥有一个ListView的引用来通过tag查找ImageView。
另外一点是,实现中我们使用了一个叫ViewCache的对象。它这样定义:
Java代码  收藏代码
  1. public class ViewCache {  
  2.    
  3.     private View baseView;  
  4.     private TextView textView;  
  5.     private ImageView imageView;  
  6.    
  7.     public ViewCache(View baseView) {  
  8.         this.baseView = baseView;  
  9.     }  
  10.    
  11.     public TextView getTextView() {  
  12.         if (textView == null) {  
  13.             textView = (TextView) baseView.findViewById(R.id.text);  
  14.         }  
  15.         return titleView;  
  16.     }  
  17.    
  18.     public ImageView getImageView() {  
  19.         if (imageView == null) {  
  20.             imageView = (ImageView) baseView.findViewById(R.id.image);  
  21.         }  
  22.         return imageView;  
  23.     }  
  24. }  

有了ViewCache对象,我们就不需要使用findViewById()来多次查询View对象了。
总结
我已经向大家演示了3种改进ListView性能的方法:
·在单一线程里加载图片
·重用列表中行
·缓存行中的View
阅读(3961) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~