评价:
一个研究ListView优化比较全面的博客,可惜的是,没有研究map内存缓存这种策略。。
[Android]ListView性能优化之视图缓存
前言
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 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 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 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以上提供,这里顺带测试了一下不使用静态内部类情况下性能。
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
4.1 首先有一个认识是错误的,我们先来看截图:
可以发现,只有第一屏(可视范围)调用getView所消耗的时间远远多于后面的,通过对
此外了解这个原理了,那么以下代码不运行你可能猜到结果了:
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次数,但是从测试结果来看效果并不理想,这里不再做进一步的测试。
五、推荐文章
2011-3-30 参见这里(http://www.javaeye.com/topic/971782)的讨论,据此将计划写续篇。
结束
对于Google I/O大会这个优化方案一直抱迟疑态度,此番测试总算是有了更进一步的了解,欢迎大家先测试后交流,看看还有什么办法能够再优化一点。
[Android]ListView性能优化之视图缓存(续)
前言
在上一篇ListView性能优化之视图缓存我们讨论了Google I/O中的优化方法,在各个论坛发帖后得到了不错的反馈,诸如:使用ViewHolder技术Tag的问题,利用HashMap自行存储的方案等。这里结合新浪微博中主界面的做法及测试数据与大家进一步探讨。
文章
[Android]ListView性能优化之视图缓存 [本文的上篇]
[Android]ListView性能优化之视图缓存 [JavaEye讨论帖]
正文
一、新浪微博
1.1 截图
(来自网络)
1.2 反编译后相关代码
HomeListActivity
{
int i = -- paramInt;
int j = - 1 ;
if (i == j);
for (Object localObject1 = HomeListActivity. this .getReloadView(); ; localObject1 = HomeListActivity. this .getLoadMoreView())
{
label26: return localObject1;
int k = HomeListActivity. this .mList.size();
int l = paramInt;
int i1 = k;
if (l != i1)
break ;
}
boolean bool1 = true ;
boolean bool2 = null ;
String str1;
label110: Object localObject2;
if (StaticInfo.mUser == null )
{
List localList1 = HomeListActivity. this .mList;
int i2 = paramInt;
str1 = ((MBlog)localList1.get(i2)).uid;
List localList2 = HomeListActivity. this .mList;
int i3 = paramInt;
String str2 = ((MBlog)localList2.get(i3)).uid;
String str3 = str1;
if ( ! str2.equals(str3))
break label271;
int i4 = 1 ;
label156: if (paramView != null )
break label277;
HomeListActivity localHomeListActivity1 = HomeListActivity. this ;
ListView localListView1 = HomeListActivity. this .mLvHome;
List localList3 = HomeListActivity. this .mList;
int i5 = paramInt;
MBlog localMBlog1 = (MBlog)localList3.get(i5);
HomeListActivity localHomeListActivity2 = HomeListActivity. this ;
int i6 = paramInt;
boolean bool4 = localHomeListActivity2.isNewCommer(i6);
int i7 = HomeListActivity. this .mReadMode;
localObject2 = new MBlogListItemView(localHomeListActivity1, localListView1, localMBlog1, bool1, bool2, i4, bool4, i7);
}
while ( true )
{
localObject1 = localObject2;
break label26:
str1 = StaticInfo.mUser.uid;
break label110:
label271: boolean bool3 = null ;
break label156:
label277: localObject2 = paramView;
try
{
MainListItemView localMainListItemView = (MainListItemView)localObject2;
List localList4 = HomeListActivity. this .mList;
int i8 = paramInt;
Object localObject3 = localList4.get(i8);
HomeListActivity localHomeListActivity3 = HomeListActivity. this ;
int i9 = paramInt;
boolean bool5 = localHomeListActivity3.isNewCommer(i9);
int i10 = HomeListActivity. this .mReadMode;
boolean bool6 = bool1;
boolean bool7 = bool2;
localMainListItemView.update(localObject3, bool6, bool7, bool5, i10);
}
catch (Exception localException)
{
HomeListActivity localHomeListActivity4 = HomeListActivity. this ;
ListView localListView2 = HomeListActivity. this .mLvHome;
List localList5 = HomeListActivity. this .mList;
int i11 = paramInt;
MBlog localMBlog2 = (MBlog)localList5.get(i11);
HomeListActivity localHomeListActivity5 = HomeListActivity. this ;
int i12 = paramInt;
boolean bool8 = localHomeListActivity5.isNewCommer(i12);
int i13 = HomeListActivity. this .mReadMode;
localObject2 = new MBlogListItemView(localHomeListActivity4, localListView2, localMBlog2, bool1, bool2, bool3, bool8, i13);
}
}
}
代码说明:
代码流程已经比较混乱,但是这里能看到并没有直接的inflate,而是自定义了继承自LinearLayout的MBlogListItemView。
{
super (paramContext);
this .context = paramContext;
this .parent = paramListView;
this .mBlog = paramMBlog;
String str1 = paramContext.getCacheDir().getAbsolutePath();
this .mCacheDir = str1;
String str2 = paramContext.getFilesDir().getAbsolutePath();
this .mFileDir = str2;
((LayoutInflater)paramContext.getSystemService( " layout_inflater " )).inflate( 2130903061 , this );
TextView localTextView1 = (TextView)findViewById( 2131624016 );
this .mName = localTextView1;
TextView localTextView2 = (TextView)findViewById( 2131624041 );
this .mDate = localTextView2;
TextView localTextView3 = (TextView)findViewById( 2131624018 );
this .mContent = localTextView3;
TextView localTextView4 = (TextView)findViewById( 2131624046 );
this .mSubContent = localTextView4;
ImageView localImageView1 = (ImageView)findViewById( 2131624040 );
this .mIconV = localImageView1;
ImageView localImageView2 = (ImageView)findViewById( 2131624042 );
this .mIconPic = localImageView2;
ImageView localImageView3 = (ImageView)findViewById( 2131624044 );
this .mUploadPic1 = localImageView3;
ImageView localImageView4 = (ImageView)findViewById( 2131623979 );
this .mUploadPic2 = localImageView4;
TextView localTextView5 = (TextView)findViewById( 2131624047 );
this .tvForm = localTextView5;
TextView localTextView6 = (TextView)findViewById( 2131623989 );
this .tvComment = localTextView6;
this .tvComment.setOnClickListener( this );
TextView localTextView7 = (TextView)findViewById( 2131623988 );
this .tvRedirect = localTextView7;
this .tvRedirect.setOnClickListener( this );
ImageView localImageView5 = (ImageView)findViewById( 2131624049 );
this .imComment = localImageView5;
this .imComment.setOnClickListener( this );
ImageView localImageView6 = (ImageView)findViewById( 2131624048 );
this .imRedirect = localImageView6;
this .imRedirect.setOnClickListener( this );
ImageView localImageView7 = (ImageView)findViewById( 2131624043 );
this .imGpsIcon = localImageView7;
ImageView localImageView8 = (ImageView)findViewById( 2131624013 );
this .mPortrait = localImageView8;
LinearLayout localLinearLayout = (LinearLayout)findViewById( 2131624045 );
this .mSubLayout = localLinearLayout;
this .mReadMode = paramInt;
MBlogListItemView localMBlogListItemView = this ;
MBlog localMBlog = paramMBlog;
boolean bool1 = paramBoolean1;
boolean bool2 = paramBoolean2;
boolean bool3 = paramBoolean4;
int i = paramInt;
localMBlogListItemView.update(localMBlog, bool1, bool2, bool3, i);
this .mUploadPic1.setOnClickListener( this );
this .mUploadPic2.setOnClickListener( this );
}
代码说明:
a). MBlogListItemView extends LinearLayout implements MainListItemView
二、测试方案(方案五)
按照新浪微博类似的做法进行测试。
2.1 测试代码
public View getView( int position, View convertView, ViewGroup parent) {
// 开始计时
long startTime = System.nanoTime();
TestItemLayout item;
if (convertView == null ) {
item = new TestItemLayout(BaseAdapterActivity. this );
} else
item = (TestItemLayout) convertView;
item.icon1.setImageResource(R.drawable.icon);
item.text1.setText(mData[position]);
item.icon2.setImageResource(R.drawable.icon);
item.text2.setText(mData[position]);
// 停止计时
long endTime = System.nanoTime();
// 计算耗时
long val = (endTime - startTime) / 1000L ;
Log.e( " Test " , " Position: " + position + " : " + val);
if (count < 100 ) {
if (val < 2000L ) {
sum += val;
count ++ ;
}
} else
mTV.setText(String.valueOf(sum / 100L ) + " : " + nullcount); // 显示统计结果
return item;
}
TestItemLayout
public TextView text1;
public ImageView icon1;
public TextView text2;
public ImageView icon2;
public TestItemLayout(Context context) {
super (context);
((LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(
R.layout.list_item_icon_text, this );
icon1 = (ImageView) findViewById(R.id.icon1);
text1 = (TextView) findViewById(R.id.text1);
icon2 = (ImageView) findViewById(R.id.icon2);
text2 = (TextView) findViewById(R.id.text2);
}
}
2.2 测试结果
次数 | 4个子元素 | 10个子元素 |
第一次 | 347 | 460 |
第二次 | 310 | 477 |
第三次 | 324 | 508 |
第四次 | 339 | 492 |
第五次 | 341 | 465 |
三、总结
从测试结果来看与ViewHolder性能非常接近,不会出现tag图片变小的问题(关于图片变小的问题,有朋友说是TAG中的元素对大小和位置有记忆),也能有效的减少findViewById的执行次数,这里建议完全可以取代ViewHolder。
关于ListView内部Adapter的心得大家可以看一下上文的总结4.1。
四、考虑
关于静态内部类这里不是很理解,是否能应用方案五还有待验证。
2011-4-29 来自Stony Wang关于Tag的解释:
结束
优化ListView不仅仅只有对convertView的优化,还有许多这样那样的技巧,欢迎大家交流与分享 :)