前言
ListView是Android中最常用的控件,通过适配器来进行数据适配然后显示出来,而其性能是个很值得研究的话题。本文与你一起探讨Google I/O提供的优化Adapter方案,欢迎大家交流。
声明
欢迎转载,但请保留文章原始出处:)
移动社区:http://dev.10086.cn/
农民伯伯: 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[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,见代码)
次数 |
4个子元素 |
10个子元素 |
第一次 |
366 |
723 |
第二次 |
356 |
689 |
第三次 |
371 |
692 |
第四次 |
356 |
696 |
第五次 |
371 |
662 |
按照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 首先有一个认识是错误的,我们先来看截图:
![点击图片可在新窗口打开 [Android]ListView性能优化之视图缓存 解决listView显示重复问题_第2张图片](http://img.e-com-net.com/image/info5/4bd073e92ed94b96b429b7ed3cfa2757.jpg)
可以发现,只有第一屏(可视范围)调用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次数,但是从测试结果来看效果并不理想,这里不再做进一步的测试。