ListView优化
2015-08-24 14:31
274 查看
前言
ListView是Android中最常用的控件,通过适配器来进行数据适配然后显示出来,而其性能是个很值得研究的话题。本文与你一起探讨Google I/O提供的优化Adapter方案,欢迎大家交流。
声明
欢迎转载,但请保留文章原始出处:)
博客园:http://www.cnblogs.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[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次数,但是从测试结果来看效果并不理想,这里不再做进一步的测试。
五、推荐文章
Android,谁动了我的内存(1)
Android 内存泄漏调试
六、后期维护
2011-3-30 参见这里(http://www.javaeye.com/topic/971782)的讨论,据此将计划写续篇。
结束
对于Google I/O大会这个优化方案一直抱迟疑态度,此番测试总算是有了更进一步的了解,欢迎大家先测试后交流,看看还有什么办法能够再优化一点。
****************************************************************************************************
1.利用Convertview缓存和ViewHolder静态类
2.ListView背景,若不使用就设为null,或者删除窗口背景,方法如下
代码修改:
getWindow().setBackgroundDrawable(null);
xml修改:
首先确定res/values/styles.xml有
@null
然后编辑androidmainfest.xml
android:theme="@style/NoBackgroundTheme">
...
3.图片尺寸要合适,否则会自动缩放;同时我们还要避免实时缩放,最好预先缩放到视图大小
originalImage =Bitmap.createScaledBitmap(
originalImage,//缩放图像
view.getWidth(), //视图宽度
view.getHeight(), //视图高度
true); //线性过滤器
4.invalidate()最好局部刷新,方法如下:
invalidate(Rectdirty);
invalidate(int left, int top,int right, int bottom);
5.布局上尽量使用相对布局和Framelayout,减少层次嵌套,另外最好textview和Drawable复合使用
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello"
android:drawableLeft="@drawable/icon"/>
6.建议使用自定义View
class CustomView extends View{
@Override
protected void onDraw(Canvascanvas) {
// 加入你的绘图编码
}
@Override
protected void onMeasure(intwidthMeasureSpec,
int heightMeasureSpec){
// 计算视图的尺寸
setMeasuredDimension(widthSpecSize,heightSpecSize);
}
}
7.建议使用自定义布局
class GridLayout extendsViewGroup {
@Override
protected void onLayout(booleanchanged, int l, int t,
int r, int b) {
final int count =getChildCount();
for (int i=0; i< count; i++) {
final View child =getChildAt(i);
if (child.getVisibility() !=GONE) {
// 计算子视图的位置
child.layout(left, top, right,bottom);
}
}
}
}
8.性能比较敏感的地方,减少java对象的创建
1.测量 onmeasure()
2.布局onlayout()
3.绘图 ondraw()dispatchdraw()
4.事件处理 ontouchevent()dispatchtouchevent()
5.adapter: getview()bindview()
强行限制(适用调试模式)
int prevLimit =-1;
try {
prevLimit =Debug.setAllocationLimit(0);
// 执行不分配内存的代码
} catch(dalvik.system.AllocationLimitError e) {
// 如果代码分配内存, Java虚拟机会抛出错误
Log.e(LOGTAG, e);
} finally {
Debug.setAllocationLimit(prevLimit);
}
9.适当软引用:内存缓存的最佳选择;适当弱引用:避免内存泄露,方法如下:
private finalHashMap> mCache;
public void put(String key, Tvalue) {
mCache.put(key, newSoftReference(value));
}
public T get(String key,ValueBuilder builder) {
T value = null;
SoftReferece reference =mCache.get(key);
if (reference != null){
value =reference.get();
}
}
ListView是Android中最常用的控件,通过适配器来进行数据适配然后显示出来,而其性能是个很值得研究的话题。本文与你一起探讨Google I/O提供的优化Adapter方案,欢迎大家交流。
声明
欢迎转载,但请保留文章原始出处:)
博客园:http://www.cnblogs.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[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 |
此方案为“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 内存泄漏调试
六、后期维护
2011-3-30 参见这里(http://www.javaeye.com/topic/971782)的讨论,据此将计划写续篇。
结束
对于Google I/O大会这个优化方案一直抱迟疑态度,此番测试总算是有了更进一步的了解,欢迎大家先测试后交流,看看还有什么办法能够再优化一点。
****************************************************************************************************
1.利用Convertview缓存和ViewHolder静态类
2.ListView背景,若不使用就设为null,或者删除窗口背景,方法如下
代码修改:
getWindow().setBackgroundDrawable(null);
xml修改:
首先确定res/values/styles.xml有
@null
然后编辑androidmainfest.xml
android:theme="@style/NoBackgroundTheme">
...
3.图片尺寸要合适,否则会自动缩放;同时我们还要避免实时缩放,最好预先缩放到视图大小
originalImage =Bitmap.createScaledBitmap(
originalImage,//缩放图像
view.getWidth(), //视图宽度
view.getHeight(), //视图高度
true); //线性过滤器
4.invalidate()最好局部刷新,方法如下:
invalidate(Rectdirty);
invalidate(int left, int top,int right, int bottom);
5.布局上尽量使用相对布局和Framelayout,减少层次嵌套,另外最好textview和Drawable复合使用
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello"
android:drawableLeft="@drawable/icon"/>
6.建议使用自定义View
class CustomView extends View{
@Override
protected void onDraw(Canvascanvas) {
// 加入你的绘图编码
}
@Override
protected void onMeasure(intwidthMeasureSpec,
int heightMeasureSpec){
// 计算视图的尺寸
setMeasuredDimension(widthSpecSize,heightSpecSize);
}
}
7.建议使用自定义布局
class GridLayout extendsViewGroup {
@Override
protected void onLayout(booleanchanged, int l, int t,
int r, int b) {
final int count =getChildCount();
for (int i=0; i< count; i++) {
final View child =getChildAt(i);
if (child.getVisibility() !=GONE) {
// 计算子视图的位置
child.layout(left, top, right,bottom);
}
}
}
}
8.性能比较敏感的地方,减少java对象的创建
1.测量 onmeasure()
2.布局onlayout()
3.绘图 ondraw()dispatchdraw()
4.事件处理 ontouchevent()dispatchtouchevent()
5.adapter: getview()bindview()
强行限制(适用调试模式)
int prevLimit =-1;
try {
prevLimit =Debug.setAllocationLimit(0);
// 执行不分配内存的代码
} catch(dalvik.system.AllocationLimitError e) {
// 如果代码分配内存, Java虚拟机会抛出错误
Log.e(LOGTAG, e);
} finally {
Debug.setAllocationLimit(prevLimit);
}
9.适当软引用:内存缓存的最佳选择;适当弱引用:避免内存泄露,方法如下:
private finalHashMap> mCache;
public void put(String key, Tvalue) {
mCache.put(key, newSoftReference(value));
}
public T get(String key,ValueBuilder builder) {
T value = null;
SoftReferece reference =mCache.get(key);
if (reference != null){
value =reference.get();
}
}
相关文章推荐
- 实现顶部轮播,下部listview经典布局的两种方式
- 2015-08-24 part2 bootloader前期准备
- bzoj-2693 jzptab
- C语言strlen函数和sizeof操作符
- 实现顶部轮播,下部listview经典布局的两种方式
- C#面向对象基础
- 最小费用最大流 【模板】
- 计算机应用——DOS命令下的网络管理和诊断-终
- 计算机应用——DOS命令下的网络管理和诊断-续
- 计算机应用记录
- 排序算法(四)插入排序
- webview 与 js交互
- github使用总结
- Spring整合JPA
- WampServer中Apache使用FastCGI模式跑PHP5.3nts版
- node-restify简介
- Nodejs学习路线图
- OpenFlow Switch学习笔记(七)——Matching Fields
- Util:Toast统一管理类
- 反渗透设备:反渗透设备操作简单