原文路径:http://blog.jteam.nl/2009/09/17/exploring-the-world-of-android-part-2/

ListView是一种可以显示一系列项目并能进行滚动显示的View。在每行里,既可以是简单的文本,也可以是复杂的结构。一般情况下,你都需要保证ListView运行得很好(即:渲染更快,滚动流畅)。在接下来的内容里,我将就ListView的使用,向大家提供几种解决不同性能问题的解决方案。

如果你想使用ListView,你就不得不使用ListAdapter来显示内容。SDK中,已经有了几种简单实现的Adapter:

·         ArrayAdapter<T> (显示数组对象,使用toString()来显示)

·         SimpleAdapter (显示Maps列表)

·         SimpleCursorAdapter(显示通过Cursor从DB中获取的信息)

这些实现对于显示简单的列表来说,非常棒!一旦你的列表比较复杂,你就不得不书写自己的ListAdapter实现。在多数情况下,直接从ArrayAdapter扩展就能很好地处理一组对象。此时,你需要处理的工作只是告诉系统如何处理列表中的对象。通过重写getView(int, View, ViewGroup)方法即可达到。

在这里,举一个你需要自定义ListAdapter的例子:显示一组图片,图片的旁边有文字挨着。

ListView例:以图片文字方式显示的Youtube搜索结果

图片需要实时从internet上下载下来。让我们先创建一个Class来代表列表中的项目:

public class ImageAndText {

private String imageUrl;

private String text;

public ImageAndText(String imageUrl, String text) {

this.imageUrl = imageUrl;

this.text = text;

}

public String getImageUrl() {

return imageUrl;

}

public String getText() {

return text;

}

}

现在,我们要实现一个ListAdapter,来显示ImageAndText列表。

public class ImageAndTextListAdapter extends ArrayAdapter<ImageAndText> {

public ImageAndTextListAdapter(Activity activity, List<ImageAndText> imageAndTexts) {

super(activity, 0, imageAndTexts);

}

@Override

public View getView(int position, View convertView, ViewGroup parent) {

Activity activity = (Activity) getContext();

LayoutInflater inflater = activity.getLayoutInflater();

// Inflate the views from XML

View rowView = inflater.inflate(R.layout.image_and_text_row, null);

ImageAndText imageAndText = getItem(position);

// Load the image and set it on the ImageView

ImageView imageView = (ImageView) rowView.findViewById(R.id.image);

imageView.setImageDrawable(loadImageFromUrl(imageAndText.getImageUrl()));

// Set the text on the TextView

TextView textView = (TextView) rowView.findViewById(R.id.text);

textView.setText(imageAndText.getText());

return rowView;

}

public static Drawable loadImageFromUrl(String url) {

InputStream inputStream;

try {

inputStream = new URL(url).openStream();

} catch (IOException e) {

throw new RuntimeException(e);

}

return Drawable.createFromStream(inputStream, "src");

}

}

这些View都是从“image_and_text_row.xml”XML文件中inflate的:

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:orientation="horizontal"

android:layout_width="fill_parent"

android:layout_height="wrap_content">

<ImageView android:id="@+id/image"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:src="@drawable/default_image"/>

<TextView android:id="@+id/text"

android:layout_width="wrap_content"

android:layout_height="wrap_content"/>

</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来负责图片的下载。此外,它还缓存了图片,防止单个图片被下载多次。

public class AsyncImageLoader {

private HashMap<String, SoftReference<Drawable>> imageCache;

public AsyncImageLoader() {

imageCache = new HashMap<String, SoftReference<Drawable>>();

}

public Drawable loadDrawable(final String imageUrl, final ImageCallback imageCallback) {

if (imageCache.containsKey(imageUrl)) {

SoftReference<Drawable> softReference = imageCache.get(imageUrl);

Drawable drawable = softReference.get();

if (drawable != null) {

return drawable;

}

}

final Handler handler = new Handler() {

@Override

public void handleMessage(Message message) {

imageCallback.imageLoaded((Drawable) message.obj, imageUrl);

}

};

new Thread() {

@Override

public void run() {

Drawable drawable = loadImageFromUrl(imageUrl);

imageCache.put(imageUrl, new SoftReference<Drawable>(drawable));

Message message = handler.obtainMessage(0, drawable);

handler.sendMessage(message);

}

}.start();

return null;

}

public static Drawable loadImageFromUrl(String url) {

// ...

}

public interface ImageCallback {

public void imageLoaded(Drawable imageDrawable, String imageUrl);

}

}

注意:我使用了SoftReference来缓存图片,允许GC在需要的时候可以对缓存中的图片进行清理。它这样工作:

·         调用loadDrawable(ImageUrl, imageCallback),传入一个匿名实现的ImageCallback接口

·         如果图片在缓存中不存在的话,图片将从单一的线程中下载并在下载结束时通过ImageCallback回调

·         如果图片确实存在于缓存中,就会马上返回,不会回调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

因此,修改代码如下:

public class ImageAndTextListAdapter extends ArrayAdapter<ImageAndText> {

private ListView listView;

private AsyncImageLoader asyncImageLoader;

public ImageAndTextListAdapter(Activity activity, List<ImageAndText> imageAndTexts, ListView listView) {

super(activity, 0, imageAndTexts);

this.listView = listView;

asyncImageLoader = new AsyncImageLoader();

}

@Override

public View getView(int position, View convertView, ViewGroup parent) {

Activity activity = (Activity) getContext();

// Inflate the views from XML

View rowView = convertView;

ViewCache viewCache;

if (rowView == null) {

LayoutInflater inflater = activity.getLayoutInflater();

rowView = inflater.inflate(R.layout.image_and_text_row, null);

viewCache = new ViewCache(rowView);

rowView.setTag(viewCache);

} else {

viewCache = (ViewCache) rowView.getTag();

}

ImageAndText imageAndText = getItem(position);

// Load the image and set it on the ImageView

String imageUrl = imageAndText.getImageUrl();

ImageView imageView = viewCache.getImageView();

imageView.setTag(imageUrl);

Drawable cachedImage = asyncImageLoader.loadDrawable(imageUrl, new ImageCallback() {

public void imageLoaded(Drawable imageDrawable, String imageUrl) {

ImageView imageViewByTag = (ImageView) listView.findViewWithTag(imageUrl);

if (imageViewByTag != null) {

imageViewByTag.setImageDrawable(imageDrawable);

}

}

});

imageView.setImageDrawable(cachedImage);

// Set the text on the TextView

TextView textView = viewCache.getTextView();

textView.setText(imageAndText.getText());

return rowView;

}

}

这里有两点需要注意:第一点是drawable不再是加载完毕后直接设定到ImageView上。正确的ImageView是通过tag查找的,这是因为我们现在重用了View,并且图片有可能出现在错误的行上。我们需要拥有一个ListView的引用来通过tag查找ImageView。

另外一点是,实现中我们使用了一个叫ViewCache的对象。它这样定义:

public class ViewCache {

private View baseView;

private TextView textView;

private ImageView imageView;

public ViewCache(View baseView) {

this.baseView = baseView;

}

public TextView getTextView() {

if (textView == null) {

textView = (TextView) baseView.findViewById(R.id.text);

}

return titleView;

}

public ImageView getImageView() {

if (imageView == null) {

imageView = (ImageView) baseView.findViewById(R.id.image);

}

return imageView;

}

}

有了ViewCache对象,我们就不需要使用findViewById()来多次查询View对象了。

总结

我已经向大家演示了3种改进ListView性能的方法:

·         在单一线程里加载图片

·         重用列表中行

·         缓存行中的View

xirihanlin 2011.04.06

ListView性能[译]相关推荐

  1. ym——Android之ListView性能优化

    转载请注明本文出自Cym的博客(http://blog.csdn.net/cym492224103),谢谢支持! Android之ListView性能优化 假设有看过我写过的15k面试题的朋友们一定知 ...

  2. Android ListView性能优化实例讲解

    前言: 对于ListView,大家绝对都不会陌生,只要是做过Android开发的人,哪有不用ListView的呢? 只要是用过ListView的人,哪有不关心对它性能优化的呢? 关于如何对ListVi ...

  3. (翻译) Android ListView 性能优化指南

    本文翻译了Lucas Rocha的Performance Tips for Android's ListView.这是一篇关于介绍如何提升ListView性能的文章,非常的优秀.使得我拜读之后,忍不住 ...

  4. React Native 的 ListView 性能问题已解决

    长列表或者无限下拉列表是最常见的应用场景之一.RN 提供的 ListView 组件,在长列表这种数据量大的场景下,性能堪忧.而在最新的 0.43 版本中,提供了 FlatList 组件,或许就是你需要 ...

  5. [Android]ListView性能优化之视图缓存

    前言 ListView是Android中最常用的控件,通过适配器来进行数据适配然后显示出来,而其性能是个很值得研究的话题.本文与你一起探讨Google I/O提供的优化Adapter方案,欢迎大家交流 ...

  6. Android性能优化之提高ListView性能的技巧

    ListView优化一直是一个老生常谈的问题,不管是面试还是平常的开发中,ListView永远不会被忽略掉,那么这篇文章我们来看看如何最大化的优化ListView的性能. 1.在adapter中的ge ...

  7. Listview性能优化

    首先,虽然大家都知道,还是提一下,利用好 convertView 来重用 View,切忌每次 getView() 都新建.ListView 的核心原理就是重用 View.ListView 中有一个回收 ...

  8. 服务器webpack构建性能,[译] 优化 WEBPACK 以更快地构建 REACT

    如果您的 Webpack 构建缓慢且有大量的库 -- 别担心,有一种方法可以提高增量构建的速度!Webpack 的 DLLPlugin 允许您将所有的依赖项构建到一个文件中.这是一个取代分块的很好选择 ...

  9. Android Listview 性能优化

    首先我一般使用的适配器是BaseAdapter,其中有两个方法最主要,分别是: getCount,getView, 在对Listview 进行优化的时候,首先使用 convertview 和viewH ...

最新文章

  1. 资深程序员感叹:表妹成绩好却无奈辍学开理发店,月入6万,上大学没用!网友:那是你没用!...
  2. linux下的c socket编程(4)--server端的继续研究
  3. linux查看服务依赖关系图,技术|教你如何在Fedora,CentOS,RHEL中检查RPM包的依赖性
  4. Java学习路线(完整详细版)
  5. 如何在Mac上使用触控栏?
  6. c#socket编程 (转)
  7. 8-BIT OPTIMIZERS VIA BLOCK-WISE QUANTIZATION--通过块级量化的8位优化器
  8. 2019美赛A题翻译
  9. iptv写代理php,苏州电信iptv用openwrt dhcp客户端协议突破dhcp+鉴权
  10. php5.4.45连接mssql2000,用php在linux下连接mssql2000(转)
  11. 常见文件存储系统的解决方案
  12. 微信公众号怎么为用户提供文件下载功能
  13. rand()函数详解
  14. 基于学术研究于NASA官网GPM卫星数据下载详细教程
  15. 还在相信男女之间真的有纯友谊?太傻太天真!
  16. 我学编程全靠B站了,真香(第一期)
  17. SAP-ABAP-SE14丢失的数据如何恢复
  18. osg 三维gis开发_3D GIS与BIM的美丽邂逅(艾三维BIM分享)
  19. c语言程序设计医院就医,C语言程序设计(医院信息管理系统)附源代码[精品].doc...
  20. Unity 关卡编辑器(自动捕捉功能)

热门文章

  1. 巧用DevExpress GridView导入导出Excel
  2. 两种底层数据层操作时的架构方式,你喜欢那种?
  3. 英特尔未来教育核心课程
  4. html缩进快捷键_Mac技巧|如何高效使用苹果便笺?利用便笺快捷键快速完成操作!
  5. XDP/eBPF — 架构设计
  6. CentOS7_装机软件推荐
  7. altium designer pcb文件大
  8. JS面向对象一:MVC的面向对象封装
  9. Xcode+OpenCV3.4.0 折腾(2)
  10. React Native填坑之旅--动画篇