`
bk_lin
  • 浏览: 331402 次
社区版块
存档分类
最新评论

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在需要的时候可以对缓存中的图片进行清理。它这样工作:
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线程再也不会被图片加载所阻塞。
更好的性能改善 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性能的方法:
·在单一线程里加载图片
·重用列表中行
·缓存行中的View
3
0
分享到:
评论
1 楼 Trinea 2012-04-11  
楼主写的3点不错

但是对于图片缓存,大家都喜欢网上开源写的用SoftReference来缓存图片,允许GC在需要的时候可以对缓存中的图片进行清理。其实这种方式并不合理,我想你在使用的时候也已经发现,注重不确定的缓存清理导致图片突然就不存在又需要重新加载浪费流量、消耗性能。这周我会整理出自己写的一个缓存,相对上面的SoftReference方式来说功能强大的多并且易用,欢迎关注http://trinea.iteye.com/

相关推荐

    对android开发listview的可靠性研究.pdf

    通过不断优化和改进,可以确保ListView在处理大量数据时仍能保持良好的性能表现,满足用户对高效、流畅应用的需求。同时,掌握ListView的正确使用方法,有助于开发者编写出更高质量的Android应用。

    listview异步下载图片改进版

    在Android开发中,ListView是一种常见的UI控件,用于展示大量数据列表。然而,当列表项包含图片时,如果直接在主线程中加载图片,会...对于Android开发者来说,掌握这种技术对于优化应用性能和提升用户体验至关重要。

    网络数据ListView展开与隐藏加强版

    这样可以显著减少初次加载时的数据量,提高应用的启动速度和运行效率。在网络请求中,通常会采用异步加载策略,通过AsyncTask或者Retrofit等网络库进行后台数据获取,避免阻塞主线程。 其次,支持文件中嵌套文件夹...

    QQListView 测试程序代码

    这个测试程序代码是开发者为了验证和调试QQListView的功能而编写的,可能包括了对控件性能、渲染速度、滑动效果以及自定义项的测试。 在Android开发中,ListView是最常见的用来展示大量数据的控件之一,但它的性能...

    Android:如何提高UI的速度和效率

    本文将根据提供的内容,深入探讨如何优化Android UI,特别是针对列表视图(ListView)的性能改进。 #### 一、适配器(Adapters) 适配器在Android中扮演着桥梁的角色,它连接数据源与视图组件。通过合理设计和使用...

    android L samples code 示例程序

    5. ART (Android RunTime):Android L默认使用ART,替代了Dalvik虚拟机,提升了应用的运行速度和启动时间。开发者可以通过示例了解如何针对ART进行优化。 6. Notification Enhancements:通知栏进行了大幅度改进,...

    Lazarus开发Android应用程序指南(2017新版)第一部分

    8. **性能优化**:Lazarus和Free Pascal编译器的优化能力使其产生的代码运行速度快,内存占用少,对于Android设备尤其重要,因为资源有限。 9. **社区支持**:Lazarus是开源项目,拥有活跃的社区,开发者可以在论坛...

    simple-android,用于记录血压测量的android应用程序.zip

    同时,合理使用ViewStub和懒加载策略,减少非必需视图的加载,提高应用启动速度和运行效率。 另外,为了提升用户体验,应用可能包含通知功能,提醒用户定时测量血压。Android的AlarmManager或WorkManager可以实现...

    android手机通讯录的毕业论文

    9. **性能优化**:对于大量联系人的处理,优化查询性能、内存占用和响应速度是必要的。这可能涉及索引优化、异步加载和缓存策略等。 10. **版本兼容性**:由于Android系统的碎片化,开发过程中要考虑不同版本间的...

    Android 2.2英文帮助文档离线完整版

    1. **性能提升**:Android 2.2带来了显著的性能优化,尤其是对于Java代码的运行速度,通过引入Just-In-Time (JIT) 编译器,应用程序的启动和运行速度得到了显著提升。 2. **USB网络共享**:Froyo允许设备通过USB...

    Android 5.0 samples

    7. ART (Android RunTime):代替Dalvik,ART成为Android 5.0默认的运行时环境,提高了应用的性能和启动速度。样例可能包括针对ART优化的代码。 8. Sync Adapter:为了实现后台数据同步,Android 5.0提供了Sync ...

    Android通讯录课程设计

    10. **测试与优化**:在开发过程中,进行单元测试和集成测试以确保功能的正确性,同时进行性能优化,如减少数据库查询时间、提高UI响应速度等。 通过这个课程设计,开发者不仅可以掌握Android应用开发的基本技能,...

    Android项目

    10. **性能优化**: 包括内存优化、渲染优化、启动速度优化等,以确保应用运行流畅。 11. **版本兼容性**: Android有许多版本,开发者需要考虑应用在不同版本上的兼容性问题。 12. **测试和调试**: 使用Android ...

    android实现城市选择 关键词、拼音检索 滑动检索

    确保在各种设备和Android版本上运行良好,测试搜索速度和响应性,以及在大量数据下的性能表现。通过持续迭代和用户反馈,不断改进功能,以满足不同用户的需求。 总之,实现一个高效的城市选择功能,需要结合关键词...

    android音乐播放器V2.0

    为解决这一问题,开发团队可能采用了更高效的列表渲染算法,如使用RecyclerView替代ListView,通过DiffUtil减少不必要的视图重绘,以及利用异步加载策略提高响应速度。此外,可能还对UI线程和数据处理线程进行了分离...

    基于android平台手机茶百科开发毕业设计.doc

    对性能瓶颈进行优化,如数据库查询效率、图片加载速度等,提升整体运行效率。 5 结论 基于Android平台的手机茶百科查询软件,实现了茶叶信息的便捷查询和丰富展示,为用户提供了优质的茶叶知识学习平台。其良好的...

    复习用的常见Android面试题

    1. **响应速度与UI刷新速度**:这是衡量Android应用性能的两个重要指标。响应速度指的是应用程序对用户操作做出反应的速度;UI刷新速度则是指界面更新的流畅度。 2. **TraceView**:这是一个内置在Android SDK中的...

    Android通过view复用优化ViewFlipper用法

    在Android中,view复用是一种节省内存和提高性能的技术,常见于ListView、RecyclerView等滚动视图中。通过复用不再可见的视图,而不是每次都创建新的视图实例,可以显著减少内存消耗和渲染时间。对于ViewFlipper,...

    android博客系统

    Android 4.0(Ice Cream Sandwich,简称ICS)是Google推出的一个重大更新,它引入了许多新特性和改进,使得开发者能够创建更精美、响应更快的应用。在这一版本中,UI设计更加统一,性能优化显著,同时也支持更多的...

Global site tag (gtag.js) - Google Analytics