android ListView 运行速度和性能改进

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在需要的时候可以对缓存中的图片进行清理。它这样工作:
[color=blue] 1、调用loadDrawable(ImageUrl, imageCallback),传入一个匿名实现的ImageCallback接口
2、如果图片在缓存中不存在的话,图片将从单一的线程中下载并在下载结束时通过ImageCallback回调
3、如果图片确实存在于缓存中,就会马上返回,不会回调ImageCallback
在你的程序中,只能存在一个AsyncImageLoader实例,否则,缓存不能正常工作。在ImageAndTextListAdapter类中,我们可以这样替换:[/color]
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线程再也不会被图片加载所阻塞。
更好的性能改善 www.2cto.com
如果你尝试了上面的解决方案,你将注意到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性能的方法:
[color=red]·在单一线程里加载图片
·重用列表中行
·缓存行中的View
[/color]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要在AndroidListView中展示图片和播放暂停音乐,你可以在ListView的适配器中实现对应的逻辑。 1. 展示图片 在适配器中,你可以使用Android中的ImageView控件来展示图片。例如,在getView()方法中: ```java @Override public View getView(int position, View convertView, ViewGroup parent) { // 获取当前项的数据 Item item = getItem(position); // 创建或重用convertView if (convertView == null) { convertView = LayoutInflater.from(getContext()).inflate(R.layout.list_item, parent, false); } // 获取ImageView控件 ImageView imageView = convertView.findViewById(R.id.item_image); // 加载图片到ImageView中 Glide.with(getContext()) .load(item.getImageUrl()) .into(imageView); return convertView; } ``` 这里使用了Glide库来加载图片,你需要在build.gradle文件中添加以下依赖: ```groovy implementation 'com.github.bumptech.glide:glide:4.11.0' annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0' ``` 2. 播放暂停音乐 要在ListView中播放音乐,你需要使用MediaPlayer类。在适配器中,你可以为每个列表项创建一个MediaPlayer对象,然后在点击列表项时播放或暂停音乐。例如: ```java @Override public View getView(int position, View convertView, ViewGroup parent) { // 获取当前项的数据 final Item item = getItem(position); // 创建或重用convertView if (convertView == null) { convertView = LayoutInflater.from(getContext()).inflate(R.layout.list_item, parent, false); } // 获取播放/暂停按钮 final Button playButton = convertView.findViewById(R.id.item_play_button); // 创建MediaPlayer对象 final MediaPlayer mediaPlayer = new MediaPlayer(); try { mediaPlayer.setDataSource(item.getMusicUrl()); mediaPlayer.prepare(); } catch (IOException e) { e.printStackTrace(); } // 播放/暂停按钮点击事件 playButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (mediaPlayer.isPlaying()) { mediaPlayer.pause(); playButton.setText("播放"); } else { mediaPlayer.start(); playButton.setText("暂停"); } } }); return convertView; } ``` 这里假设Item类中有getImageUrl()方法和getMusicUrl()方法,分别返回图片和音乐的URL。另外,在布局文件中需要添加一个播放/暂停按钮,例如: ```xml <Button android:id="@+id/item_play_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="播放" /> ``` 需要注意的是,MediaPlayer对象需要在适当的时候释放资源,否则会导致内存泄漏。你可以在ListView的Activity或Fragment中的onDestroy()方法中释放所有MediaPlayer对象: ```java @Override protected void onDestroy() { super.onDestroy(); for (Item item : itemList) { item.releaseMediaPlayer(); } } // Item类中的releaseMediaPlayer()方法 public void releaseMediaPlayer() { if (mediaPlayer != null) { mediaPlayer.release(); } } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值