精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
作者 | 正文 | ||||||||||||||||||||||||||||||||||||
发表时间:2011-03-23
前言 ListView是Android中最常用的控件,通过适配器来进行数据适配然后显示出来,而其性能是个很值得研究的话题。本文与你一起探讨Google I/O提供的优化Adapter方案,欢迎大家交流。
正文 一、准备 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[i] = "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,见代码)
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,见代码)
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次数,但是从测试结果来看效果并不理想,这里不再做进一步的测试。
五、推荐文章
结束 对于Google I/O大会这个优化方案一直抱迟疑态度,此番测试总算是有了更进一步的了解,欢迎大家先测试后交流,看看还有什么办法能够再优化一点。 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|||||||||||||||||||||||||||||||||||||
返回顶楼 | |||||||||||||||||||||||||||||||||||||
发表时间:2011-03-24
最后修改:2011-03-24
这不是cnBlog 上面的帖子吗,下午刚看的。。。
是一个人吗?? |
|||||||||||||||||||||||||||||||||||||
返回顶楼 | |||||||||||||||||||||||||||||||||||||
发表时间:2011-03-25
caizi12 写道 这不是cnBlog 上面的帖子吗,下午刚看的。。。
是一个人吗?? 嗯,是同一个人,也发到这边来讨论一下 |
|||||||||||||||||||||||||||||||||||||
返回顶楼 | |||||||||||||||||||||||||||||||||||||
发表时间:2011-03-25
over140 写道 caizi12 写道 这不是cnBlog 上面的帖子吗,下午刚看的。。。
是一个人吗?? 嗯,是同一个人,也发到这边来讨论一下 我也是两边串啊,这些天发现javaEye上面的帖子不行了。很少有人发了。质量也不行。就跑到,CNBLOG上面看去了。 |
|||||||||||||||||||||||||||||||||||||
返回顶楼 | |||||||||||||||||||||||||||||||||||||
发表时间:2011-03-25
用tag有时候会碰到问题的
今天china android dev group里就有讨论帖。 |
|||||||||||||||||||||||||||||||||||||
返回顶楼 | |||||||||||||||||||||||||||||||||||||
发表时间:2011-03-25
tapestry1122 写道 用tag有时候会碰到问题的 今天china android dev group里就有讨论帖。 还请贴一下帖子地址,一起看看:) |
|||||||||||||||||||||||||||||||||||||
返回顶楼 | |||||||||||||||||||||||||||||||||||||
发表时间:2011-03-25
over140 写道 tapestry1122 写道 用tag有时候会碰到问题的
今天china android dev group里就有讨论帖。 还请贴一下帖子地址,一起看看:) 讨论地址在这边: http://groups.google.com/group/china-android-dev/browse_thread/thread/223d18319fc70f2a# 引用 在ListView中遇到的奇怪问题,拖动条目时就会变小 在虚拟机上是正常的,但在真机上就会变成了附件中的效果了,我是实现异步加载图片 ListView,我的机器是2.2,大家有遇到过吗? |
|||||||||||||||||||||||||||||||||||||
返回顶楼 | |||||||||||||||||||||||||||||||||||||
发表时间:2011-03-26
ViewHolder 谁用这种技术谁后悔,TAG是这样用的?变小的问题就摆在那。
|
|||||||||||||||||||||||||||||||||||||
返回顶楼 | |||||||||||||||||||||||||||||||||||||
发表时间:2011-03-26
http://www.iteye.com/topic/860925
这个是我的见解。 |
|||||||||||||||||||||||||||||||||||||
返回顶楼 | |||||||||||||||||||||||||||||||||||||
发表时间:2011-03-28
zhuixinjian 写道 ViewHolder 谁用这种技术谁后悔,TAG是这样用的?变小的问题就摆在那。
确实有这问题,尤其是图片,你给的地址非论坛会员访问不了,不知有否讨论出结果。此外对于TAG一般的理解就是添加额外的数据,既然出来讨论也指出了误点,还请不吝赐教为大家说明,以免以讹传讹误了更多人 |
|||||||||||||||||||||||||||||||||||||
返回顶楼 | |||||||||||||||||||||||||||||||||||||