您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
   
 
 
     
   
 订阅
  捐助
[Android]ListView性能优化之视图缓存
 
作者:农民伯伯 来源:博客园 发布于 2015-6-10
  2142  次浏览      15
 

前言

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。

   
2142 次浏览       15
 
相关文章

手机软件测试用例设计实践
手机客户端UI测试分析
iPhone消息推送机制实现与探讨
Android手机开发(一)
 
相关文档

Android_UI官方设计教程
手机开发平台介绍
android拍照及上传功能
Android讲义智能手机开发
相关课程

Android高级移动应用程序
Android系统开发
Android应用开发
手机软件测试
最新课程计划
信息架构建模(基于UML+EA)3-21[北京]
软件架构设计师 3-21[北京]
图数据库与知识图谱 3-25[北京]
业务架构设计 4-11[北京]
SysML和EA系统设计与建模 4-22[北京]
DoDAF规范、模型与实例 5-23[北京]

android人机界面指南
Android手机开发(一)
Android手机开发(二)
Android手机开发(三)
Android手机开发(四)
iPhone消息推送机制实现探讨
手机软件测试用例设计实践
手机客户端UI测试分析
手机软件自动化测试研究报告
更多...   

Android高级移动应用程序
Android应用开发
Android系统开发
手机软件测试
嵌入式软件测试
Android软、硬、云整合

领先IT公司 android开发平台最佳实践
北京 Android开发技术进阶
某新能源领域企业 Android开发技术
某航天公司 Android、IOS应用软件开发
阿尔卡特 Linux内核驱动
艾默生 嵌入式软件架构设计
西门子 嵌入式架构设计
更多...