[Android]ListView性能优化之视图缓存(续)


前言

  在上一篇ListView性能优化之视图缓存我们讨论了Google I/O中的优化方法,在各个论坛发帖后得到了不错的反馈,诸如:使用ViewHolder技术Tag的问题,利用HashMap自行存储的方案等。这里结合新浪微博中主界面的做法及测试数据与大家进一步探讨。

 

声明

  欢迎转载,但请保留文章原始出处:)

    博客园:http://www.cnblogs.com

    农民伯伯: http://over140.cnblogs.com 

 

文章

  [Android]ListView性能优化之视图缓存 [本文的上篇]

  [Android]ListView性能优化之视图缓存 [JavaEye讨论帖]

 

正文

  一、新浪微博

    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(2130903061this);
    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  测试结果

次数

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的解释:

Stony Wang
tag的用途应该是仿照delphi的来的,设置一个关联的数据。

简单的说就是,你的UI控件有时候显示的内容带源于(绑定?)某个数据或者对象实例。
当你处理一些事件的时候,不推荐从UI上来重新获取,而是从Tag里面取出来。

举一个例子是,有一个按钮,一开始显示"0",然后每按一次计数增1。
每次click的时候,
1 从btn.getText()再转回Integer
2 从tag里面把之前设好的Integer拿出来,加一,再settag?

如果觉得1和2区别不大的话,那么如果显示的内容不是"0",而是"已经点了0次"呢?

更新UI的时候,可以将关联的对象放到tag里面,在处理相关触发事件的时候,可以方便的获取原始的数据。

ListView的Tag用法也不算很错,而是用的时候没有注意设置的时候要注意“对称”。
Tag本身可以理解成放ViewHolder,那么和ViewHolder的加速只不过是存放的位置不同,加速效果基本一致。
“对称”我所指的内容是:
不管你要显示的数据的逻辑是如何的,如果你设置了某个View的宽度,那么在任何一种数据的填充UI逻辑里面,不可以有不设置这个View宽度的代码路径。
简单的例子就是,我根据某个布尔值,如果是false的话,将ImageView设置为View.INVISIBLE
if ( item.isHidden()){
  mImage.setVisibility(View.INVISIBLE);

}
这样是错误的,因为如果这里缓存的UI控件的状态会被复用到其它item上,而这个item恰巧可能是需要显示的。
必须补上else语句
else{
  mImage.setVisibility(View.VISIBLE);
}
这个估计就是那位仁兄拖动图片变小的原因了。
最后说一下,ListView控件条目部分,一共产生的条目是屏幕能容纳的数目+2(还是+1?我记不清楚了),然后循环使用。

 

 

结束

  优化ListView不仅仅只有对convertView的优化,还有许多这样那样的技巧,欢迎大家交流与分享 :)

posted @ 2011-04-06 12:02  农民伯伯  阅读(9311)  评论(19编辑  收藏  举报