`
从此醉
  • 浏览: 1041306 次
  • 性别: Icon_minigender_1
  • 来自: US
社区版块
存档分类
最新评论

ListView与GridView优化

 
阅读更多

前言

  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

复制代码
privateTestAdaptermAdapter;

privateString[]mArrData;
privateTextViewmTV;

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

mArrData
=newString[1000];
for(inti=0;i<1000;i++){
mArrData[i]
="GoogleIOAdapter" + i;
}
mAdapter
=newTestAdapter(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  测试代码

复制代码
privateintcount=0;
privatelongsum=0L;
@Override
publicViewgetView(intposition,ViewconvertView,ViewGroupparent){
//开始计时
longstartTime=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]);

//停止计时
longendTime=System.nanoTime();
//计算耗时
longval=(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));//显示统计结果
returnconvertView;
}
复制代码

      2.1.2  测试结果(微秒除以1000,见代码)

次数

4个子元素

10个子元素

第一次

366

723

第二次

356

689

第三次

371

692

第四次

356

696

第五次

371

662

    2.2  方案二

      按照Google I/O介绍的第三种方案,是把item子元素分别改为4个和10个。

      2.2.1  测试代码

复制代码
privateintcount=0;
privatelongsum=0L;

@Override
publicViewgetView(intposition,ViewconvertView,ViewGroupparent){
//开始计时
longstartTime=System.nanoTime();

ViewHolderholder;
if(convertView==null){
convertView
=mInflater.inflate(R.layout.list_item_icon_text,
null);
holder
=newViewHolder();
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]);

//停止计时
longendTime=System.nanoTime();
//计算耗时
longval=(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));//显示统计结果
returnconvertView;
}
}

staticclassViewHolder{
TextViewtext1;
ImageViewicon1;
TextViewtext2;
ImageViewicon2;
}
复制代码

      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
publicViewgetView(intposition,ViewconvertView,ViewGroupparent){
//开始计时
longstartTime=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]);

//停止计时
longendTime=System.nanoTime();
//计算耗时
longval=(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);//显示统计结果
returnconvertView;
}
复制代码

      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
returnconvertView;
复制代码

      没错,你会发现滚动时会重复显示第一屏的数据!

      子控件里的事件因为是同一个控件,也可以直接放到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大会这个优化方案一直抱迟疑态度,此番测试总算是有了更进一步的了解,欢迎大家先测试后交流,看看还有什么办法能够再优化一点。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics