前言
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大会这个优化方案一直抱迟疑态度,此番测试总算是有了更进一步的了解,欢迎大家先测试后交流,看看还有什么办法能够再优化一点。
一、新浪微博
1.1截图
1.2反编译后相关代码
HomeListActivity
public View getView(int paramInt, View paramView, ViewGroup paramViewGroup) { 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。
MBlogListItemView
public MBlogListItemView(Context paramContext, ListView paramListView,
MBlog paramMBlog, boolean paramBoolean1, boolean paramBoolean2,
boolean paramBoolean3, boolean paramBoolean4, int paramInt) { 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
b).inflate(2130903061,this)这个数字代表R.layout.itemview。
二、测试方案(方案五)
按照新浪微博类似的做法进行测试。
2.1测试代码
@Override 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 class TestItemLayout extends LinearLayout {
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测试结果
三、总结
从测试结果来看与ViewHolder性能非常接近,不会出现tag图片变小的问题(关于图片变小的问题,有朋友说是TAG中的元素对大小和位置有记忆),也能有效的减少findViewById的执行次数,这里建议完全可以取代ViewHolder。
|