ListView性能

ListView性能[译]

原文路径: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

时间: 2024-10-12 20:18:30

ListView性能的相关文章

Android ListView性能优化实战方案

前言: 对于ListView,大家绝对都不会陌生,只要是做过Android开发的人,哪有不用ListView的呢? 只要是用过ListView的人,哪有不关心对它性能优化的呢? 关于如何对ListView进行性能优化,不仅是面试中常常会被问到的(我前段时间面试了几家公司,全部都问到了这个问题了),而且在实际项目中更是非常重要的一环,它甚至在某种程度上决定了用户是否喜欢接受你的APP.(如果你的列表滑起来很卡,我敢说很多人会直接卸载) 网上关于如何对ListView进行性能优化,提出了很多方案.但

ListView 性能优化------使用ViewHolder,修改layout_weight属性为“match_parent”

转载自:http://blog.csdn.net/pkxiuluo01/article/details/7380860 Adapter是ListView界面与数据之间的桥梁,当列表里的每一项显示到页面时,都会调用Adapter的getView方法返回一个View.如果列表中有很多的项时会占用极大的系统资源,所以我们需要优化Adapter 1.convertView的使用 [java] view plaincopy Java代码 public View getView(int position, 

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

本文翻译了Lucas Rocha的Performance Tips for Android’s ListView.这是一篇关于介绍如何提升ListView性能的文章,非常的优秀.使得我拜读之后,忍不住将其翻译.本文采用了意译的翻译方式,尽可能的保持原文中要表达的内容.但是,任有几处翻译存在一些异议.请读者原谅.如果你对文章的内容有兴趣,请移步到我的blog,地址如下: 地址: http://kohoh1992.github.io/PerformanceTipsForAndroidListView

Android之ListView性能优化——万能适配器

如下图,加入现在有一个这样的需求图,你会怎么做?作为一个初学者,之前我都是直接用SimpleAdapter结合一个Item的布局来实现的,感觉这样实现起来很方便(基本上一行代码就可以实现),而且也没有觉得有什么不好的.直到最近在慕课网上看到鸿洋大神讲的“机器人小慕”和“万能适配器”两节课,才对BaseAdapter有所了解.看了鸿洋大神的课程之后,我又上网搜了几个博客,也看了一些源码和文档,于是打算写一个帖子来记录一下自己的学习历程. 在今天的帖子中,我们从一个最基本的实现BaseAdapter

Android进阶:ListView性能优化异步加载图片 使滑动效果流畅

ListView 是一种可以显示一系列项目并能进行滚动显示的 View,每一行的Item可能包含复杂的结构,可能会从网络上获取icon等的一些图标信息,就现在的网络速度要想保持ListView运行的很好滚动流畅是做不到的 所以这里就需要把这些信息利用多线程实现异步加载 实现这样功能的类 [java] view plaincopy public class AsyncImageLoader { private HashMap<String, SoftReference<Drawable>&

ym——Android之ListView性能优化

Android之ListView性能优化 如果有看过我写过的15k面试题的朋友们一定知道,ListView的优化方式有以下几种: 重用了convertView ViewHolder static class ViewHolder 在列表里面有图片的情况下,监听滑动不加载图片 以上是大致的说了以下,应付面试已经足够了,如果要使用到项目中,可能有些初学者就迷茫了.接下来我详细的说一下,这个是如何优化的. 重用了convertView getView这个方法会调用的次数是你们的数据条目数*2,重用了c

Android之ListView性能优化——使用ConvertView和ViewHolder

使用ConvertView和ViewHolder的优化是针对ListView的Adapter(BaseAdapter)的.这种优化的优点如下: 1)重用了ConveertView,在很大程度上减少了内存的消耗.通过判断ConvertView是否为NULL,如果是NULL那么就需要生成一个新的View出来(通过LayoutInflater生成),绑定数据后显示给用户:如果ConvertView不是NULL,则我们需要做的就只有绑定数据并呈现给用户. 2)由于ListView中的Item往往都是只有

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

ListView优化一直是一个老生常谈的问题.无论是面试还是寻常的开发中,ListView永远不会被忽略掉,那么这篇文章我们来看看怎样最大化的优化ListView的性能. 1.在adapter中的getView方法中尽量少使用逻辑 2.尽最大可能避免GC 3.滑动的时候不载入图片 4.将ListView的scrollingCache和animateCache设置为false 5.item的布局层级越烧越好 6.使用ViewHolder 1.在adapter中的getView方法中尽量少使用逻辑

Listview性能优化

首先,虽然大家都知道,还是提一下,利用好 convertView 来重用 View,切忌每次 getView() 都新建.ListView 的核心原理就是重用 View.ListView 中有一个回收器,Item 滑出界面的时候 View 会回收到这里,需要显示新的 Item 的时候,就尽量重用回收器里面的 View. 利用好 View Type,例如你的 ListView 中有几个类型的 Item,需要给每个类型创建不同的 View,这样有利于 ListView 的回收,当然类型不能太多: 尽