职业IT人-IT人生活圈

 找回密码
 成为会员
搜索
查看: 682|回复: 11

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

  [复制链接]
broken 发表于 2011-7-2 19:46 | 显示全部楼层 |阅读模式
前言
  ListView是Android中最常用的控件,通过适配器来进行数据适配然后显示出来,而其性能是个很值得研究的话题。本文与你一起探讨Google I/O提供的优化Adapter方案,欢迎大家交流。

声明
  欢迎转载,但请保留文章原始出处:)
    JavaEye社区:http://www.iteye.com
    农民伯伯: http://over140.cnblogs.com

正文
  一、准备
    1.1  了解关于Google IO大会关于Adapter的优化,参考以下文章:
      Android开发之ListView 适配器(Adapter)优化
      Android开发——09Google I/O之让Android UI性能更高效(1)
      PDF下载:Google IO.pdf
    1.2  准备测试代码:
      Activity
    private TestAdapter mAdapter;

    private String[] mArrData;
    private TextView mTV;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        mTV = (TextView) findViewById(R.id.tvShow);

        mArrData = new String[1000];
        for (int i = 0; i < 1000; i++) {
            mArrData = "Google IO Adapter" + i;
        }
        mAdapter = new TestAdapter(this, mArrData);
        ((ListView) findViewById(android.R.id.list)).setAdapter(mAdapter);
    }
      代码说明:模拟一千条数据,TestAdapter继承自BaseAdapter,main.xml见文章末尾下载。

  二、测试
    测试方法:手动滑动ListView至position至50然后往回滑动,充分利用convertView不等于null的代码段。
    2.1  方案一
      按照Google I/O介绍的第二种方案,把item子元素分别改为4个和10个,这样效果更佳明显。
      2.1.1  测试代码
        private int count = 0;
        private long sum = 0L;
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            //开始计时
            long startTime = System.nanoTime();
            
            if (convertView == null) {
                convertView = mInflater.inflate(R.layout.list_item_icon_text,
                        null);
            }
            ((ImageView) convertView.findViewById(R.id.icon1)).setImageResource(R.drawable.icon);
            ((TextView) convertView.findViewById(R.id.text1)).setText(mData[position]);
            ((ImageView) convertView.findViewById(R.id.icon2)).setImageResource(R.drawable.icon);
            ((TextView) convertView.findViewById(R.id.text2)).setText(mData[position]);
            
            //停止计时
            long endTime = System.nanoTime();
            //计算耗时
            long val = (endTime - startTime) / 1000L;
            Log.e("Test", "Position:" + position + ":" + val);
            if (count < 100) {
                if (val < 1000L) {
                    sum += val;
                    count++;
                }
            } else
                mTV.setText(String.valueOf(sum / 100L));//显示统计结果
            return convertView;
        }
      2.1.2  测试结果(微秒除以1000,见代码)
次数4个子元素10个子元素
第一次 366723
第二次356689
第三次 371692
第四次356696
第五次 371662


    2.2  方案二
      按照Google I/O介绍的第三种方案,是把item子元素分别改为4个和10个。
      2.2.1  测试代码
        private int count = 0;
        private long sum = 0L;

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            // 开始计时
            long startTime = System.nanoTime();

            ViewHolder holder;
            if (convertView == null) {
                convertView = mInflater.inflate(R.layout.list_item_icon_text,
                        null);
                holder = new ViewHolder();
                holder.icon1 = (ImageView) convertView.findViewById(R.id.icon1);
                holder.text1 = (TextView) convertView.findViewById(R.id.text1);
                holder.icon2 = (ImageView) convertView.findViewById(R.id.icon2);
                holder.text2 = (TextView) convertView.findViewById(R.id.text2);
                convertView.setTag(holder);
            }
            else{
                holder = (ViewHolder)convertView.getTag();
            }
            holder.icon1.setImageResource(R.drawable.icon);
            holder.text1.setText(mData[position]);
            holder.icon2 .setImageResource(R.drawable.icon);
            holder.text2.setText(mData[position]);

            // 停止计时
            long endTime = System.nanoTime();
            // 计算耗时
            long val = (endTime - startTime) / 1000L;
            Log.e("Test", "Position:" + position + ":" + val);
            if (count < 100) {
                if (val < 1000L) {
                    sum += val;
                    count++;
                }
            } else
                mTV.setText(String.valueOf(sum / 100L));// 显示统计结果
            return convertView;
        }
    }

    static class ViewHolder {
        TextView text1;
        ImageView icon1;
        TextView text2;
        ImageView icon2;
    }
      2.2.2  测试结果(微秒除以1000,见代码)
次数4个子元素10个子元素
第一次 311 417
第二次 291 441
第三次 302 462
第四次 286 444
第五次 299 436


    2.3  方案三
      此方案为“Henry Hu”提示,API Level 4以上提供,这里顺带测试了一下不使用静态内部类情况下性能。
      2.3.1  测试代码
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            // 开始计时
            long startTime = System.nanoTime();

            if (convertView == null) {
                convertView = mInflater.inflate(R.layout.list_item_icon_text, null);
                convertView.setTag(R.id.icon1, convertView.findViewById(R.id.icon1));
                convertView.setTag(R.id.text1, convertView.findViewById(R.id.text1));
                convertView.setTag(R.id.icon2, convertView.findViewById(R.id.icon2));
                convertView.setTag(R.id.text2, convertView.findViewById(R.id.text2));
            }
            ((ImageView) convertView.getTag(R.id.icon1)).setImageResource(R.drawable.icon);
            ((ImageView) convertView.getTag(R.id.icon2)).setImageResource(R.drawable.icon);
            ((TextView) convertView.getTag(R.id.text1)).setText(mData[position]);
            ((TextView) convertView.getTag(R.id.text2)).setText(mData[position]);

            // 停止计时
            long endTime = System.nanoTime();
            // 计算耗时
            long val = (endTime - startTime) / 1000L;
            Log.e("Test", "Position:" + position + ":" + val);
            if (count < 100) {
                if (val < 1000L) {
                    sum += val;
                    count++;
                }
            } else
                mTV.setText(String.valueOf(sum / 100L) + ":" + nullcount);// 显示统计结果
            return convertView;
        }
        2.3.2  测试结果(微秒除以1000,见代码)
        第一次:450
        第二次:467
        第三次:472
        第四次:451
        第五次:441

  四、总结
    4.1  首先有一个认识是错误的,我们先来看截图:
      
      
      可以发现,只有第一屏(可视范围)调用getView所消耗的时间远远多于后面的,通过对
convertView == null内代码监控也是同样的结果。也就是说ListView仅仅缓存了可视范围内的View,随后的滚动都是对这些View进行数据更新。不管你有多少数据,他都只用ArrayList缓存可视范围内的View,这样保证了性能,也造成了我以为ListView只缓存View结构不缓存数据的假相(不会只有我一人这么认为吧- - #)。这也能解释为什么GOOGLE优化方案一比二高很多的原因。那么剩下的也就只有findViewById比较耗时了。据此大家可以看看 AbsListView的源代码,看看
obtainView这个方法内的代码及RecycleBin这个类的实现,欢迎分享。
      此外了解这个原理了,那么以下代码不运行你可能猜到结果了:
            if (convertView == null) {
                convertView = mInflater.inflate(R.layout.list_item_icon_text, null);
                ((ImageView) convertView.findViewById(R.id.icon1)).setImageResource(R.drawable.icon);
                ((TextView) convertView.findViewById(R.id.text1)).setText(mData[position]);
                ((ImageView) convertView.findViewById(R.id.icon2)).setImageResource(R.drawable.icon);
                ((TextView) convertView.findViewById(R.id.text2)).setText(mData[position]);
            }
            else
                return convertView;
      没错,你会发现滚动时会重复显示第一屏的数据!
      子控件里的事件因为是同一个控件,也可以直接放到convertView == null 代码块内部,如果需要交互数据比如position,可以通过tag方式来设置并获取当前数据。
    4.2  本文方案一与方案二对比
      这里推荐如果只是一般的应用(一般指子控件不多),无需都是用静态内部类来优化,使用第二种方案即可;反之,对性能要求较高时可采用。此外需要提醒的是这里也是用空间换时间的做法,View本身因为setTag而会占用更多的内存,还会增加代码量;而findViewById会临时消耗更多的内存,所以不可盲目使用,依实际情况而定。
    4.3  方案三
      此方案为“Henry Hu”提示,API Level 4以上支持,原理和方案三一致,减少findViewById次数,但是从测试结果来看效果并不理想,这里不再做进一步的测试。

  五、推荐文章
    Android,谁动了我的内存(1)
    Android 内存泄漏调试

结束
  对于Google I/O大会这个优化方案一直抱迟疑态度,此番测试总算是有了更进一步的了解,欢迎大家先测试后交流,看看还有什么办法能够再优化一点。

愚人 发表于 2011-7-2 19:46 | 显示全部楼层
这不是cnBlog 上面的帖子吗,下午刚看的。。。

是一个人吗??

爱车车 发表于 2011-7-2 19:46 | 显示全部楼层
caizi12
这不是cnBlog 上面的帖子吗,下午刚看的。。。

是一个人吗??

嗯,是同一个人,也发到这边来讨论一下

fl 发表于 2011-7-2 19:46 | 显示全部楼层
over140
caizi12
这不是cnBlog 上面的帖子吗,下午刚看的。。。

是一个人吗??

嗯,是同一个人,也发到这边来讨论一下


我也是两边串啊,这些天发现javaEye上面的帖子不行了。很少有人发了。质量也不行。就跑到,CNBLOG上面看去了。

北大青鸟 发表于 2011-7-2 19:46 | 显示全部楼层
用tag有时候会碰到问题的

今天china android dev group里就有讨论帖。

能文能武 发表于 2011-7-2 19:47 | 显示全部楼层
tapestry1122
用tag有时候会碰到问题的

今天china android dev group里就有讨论帖。

还请贴一下帖子地址,一起看看:)

ksdal 发表于 2011-7-2 19:47 | 显示全部楼层
over140
tapestry1122
用tag有时候会碰到问题的

今天china android dev group里就有讨论帖。

还请贴一下帖子地址,一起看看:)


讨论地址在这边:
http://groups.google.com/group/china-android-dev/browse_thread/thread/223d18319fc70f2a#

引用

在ListView中遇到的奇怪问题,拖动条目时就会变小
在虚拟机上是正常的,但在真机上就会变成了附件中的效果了,我是实现异步加载图片 ListView,我的机器是2.2,大家有遇到过吗?






醉倚西风 发表于 2011-7-2 19:47 | 显示全部楼层
ViewHolder  谁用这种技术谁后悔,TAG是这样用的?变小的问题就摆在那。

jinchang 发表于 2011-7-2 19:47 | 显示全部楼层
http://www.iteye.com/topic/860925

这个是我的见解。


钰云 发表于 2011-7-2 19:47 | 显示全部楼层
zhuixinjian
ViewHolder  谁用这种技术谁后悔,TAG是这样用的?变小的问题就摆在那。

确实有这问题,尤其是图片,你给的地址非论坛会员访问不了,不知有否讨论出结果。此外对于TAG一般的理解就是添加额外的数据,既然出来讨论也指出了误点,还请不吝赐教为大家说明,以免以讹传讹误了更多人


秋秋 发表于 2011-7-20 10:34 | 显示全部楼层
晕倒.......
话说我当年 发表于 2011-7-20 10:34 | 显示全部楼层
看起来好~~像啊~~~~~
您需要登录后才可以回帖 登录 | 成为会员

本版积分规则

QQ|手机版|小黑屋|网站帮助|职业IT人-IT人生活圈 ( 粤ICP备12053935号-1 )|网站地图
本站文章版权归原发布者及原出处所有。内容为作者个人观点,并不代表本站赞同其观点和对其真实性负责,本站只提供参考并不构成任何投资及应用建议。本站是信息平台,网站上部分文章为转载,并不用于任何商业目的,我们已经尽可能的对作者和来源进行了通告,但是能力有限或疏忽造成漏登,请及时联系我们,我们将根据著作权人的要求立即更正或者删除有关内容。

GMT+8, 2024-4-30 06:06 , Processed in 0.145224 second(s), 20 queries , Gzip On.

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表