ListView——缓存与优化剖析总结

本文深入剖析了ListView的缓存机制,包括单类型Item和多类型Item的缓存处理,强调了getItemViewType()和getViewTypeCount()在缓存中的作用。同时,文章详细介绍了ListView的缓存优化策略,如convertView的复用和减少findViewById()操作以提高性能,通过ViewHolder类优化查找控件的过程。此外,还提供了相关参考资料以供进一步学习。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ListView 缓存机制

单类型Item

在这里插入图片描述
上图B代表View的类型,1/2/3代表在ListView中第几个位置。

ListView的缓存池是ListView的父类AbsListView的内部类RecycleBin里面的一个ArrayList的数组。

ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];

看上图的List列表,当状态1变为状态2时,1B滑出6B滑入,此时scrapViews[0]中还没有缓存的View,所以6B创建,1B进入缓存。

状态2变为状态3时。缓存中存在B类型的元素,所以直接拿出来复用。

在这里插入图片描述

多类型Item

多类型机制和单类型区别不大,如下图示。

在这里插入图片描述
状态1到状态2,1A进入scrapViews[0]的缓存。

状态2到状态3,1A复用,2B进入scrapViews[1]缓存。

缓存剖析

 	/**
     * 根据数据列表的position返回需要展示的layout的对应的type
     * type的值必须从0开始 
     */
    @Override
    public int getItemViewType(int position) { 
    	return 0;
    }

    /**
     * 该方法返回多少个不同的布局
     */
    @Override
    public int getViewTypeCount() {
        return 1;
    }
 

这两个方法是BaseAdapter里面的,用于设置多Item的。

getItemViewType() 根据列表的position加载指定的Item。

getViewTypeCount() 返回Item的个数。这决定了scrapViews 数组的长度。

ListView缓存原理由它的父类AbsListView的内部类RecycleBin负责。

下面源码是对缓冲池的初始化。

        public void setViewTypeCount(int viewTypeCount) {
            if (viewTypeCount < 1) {
                throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
            }
            //noinspection unchecked
            ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
            for (int i = 0; i < viewTypeCount; i++) {
                scrapViews[i] = new ArrayList<View>();
            }
            mViewTypeCount = viewTypeCount;
            mCurrentScrap = scrapViews[0];
            mScrapViews = scrapViews;
        }

可以看出,缓存大小取决于BaseAdapter getViewTypeCount()的返回值。

下面这个方法是每次初始化都会调用的。

    private void setItemViewLayoutParams(View child, int position) {
        final ViewGroup.LayoutParams vlp = child.getLayoutParams();
        LayoutParams lp;
        if (vlp == null) {
            lp = (LayoutParams) generateDefaultLayoutParams();
        } else if (!checkLayoutParams(vlp)) {
            lp = (LayoutParams) generateLayoutParams(vlp);
        } else {
            lp = (LayoutParams) vlp;
        }

        if (mAdapterHasStableIds) {
            lp.itemId = mAdapter.getItemId(position);
        }
        lp.viewType = mAdapter.getItemViewType(position);
        lp.isEnabled = mAdapter.isEnabled(position);
        if (lp != vlp) {
          child.setLayoutParams(lp);
        }
    }

注意到

lp.viewType = mAdapter.getItemViewType(position);

这句的意思是给view的参数设置上它的类型,但是这个类型是调用getItemViewType()得到的,所以我们需要重写getItemViewType()方法,让相同类型的View返回同一个值。

下面是AbsListView.obtainView()函数,该函数的作用是绘制每一行的View

View obtainView(int position, boolean[] isScrap) {  
    isScrap[0] = false;  
    View scrapView; 
    scrapView = mRecycler.getScrapView(position);  
    // 根据position获取缓存的View
    View child;  
    if (scrapView != null) {  
    	// 如果不为null便可复用convertView
        child = mAdapter.getView(position, scrapView, this);  
        if (child != scrapView) {
        	// 和缓存中的不同的话,就重新存进去。
            mRecycler.addScrapView(scrapView);  
            if (mCacheColorHint != 0) {  
                child.setDrawingCacheBackgroundColor(mCacheColorHint);  
            }  
        } else {  
            isScrap[0] = true;  
            dispatchFinishTemporaryDetach(child);  
        }  
    } else {  
    	// 缓存为null的话,传递convertView为null
        child = mAdapter.getView(position, null, this);  
        if (mCacheColorHint != 0) {  
            child.setDrawingCacheBackgroundColor(mCacheColorHint);  
        }  
    }  
    return child;  
}  

关于如何获得缓存的View,代码里面的注释已经写的很清楚了。

参考
https://blog.csdn.net/geekerhw/article/details/52176914
https://hit-alibaba.github.io/interview/Android/basic/ListView-Optimize.html
https://www.jianshu.com/p/2791bc155b65

ListView优化

缓存优化

使用ListView自带的缓存优化。
上面也分析了,BaseAdapter自身的缓存原理。
ListView的Adapter的作用如下所示。
在这里插入图片描述

1. 复用convertView

一情况下getView()是是这样写的

   @Override
   public View getView(int position, View convertView, ViewGroup parent) { 
       View view = View.inflate(MainActivity.this, R.layout.listview_item, null); 
       TextView tv_item = (TextView) view.findViewById(R.id.tv_item);
       tv_item.setText(list.get(position));
       return view;
   } 

在上面你的缓存原理可以知道,在得到Item时会首先在缓存中找。

   child = mAdapter.getView(position, scrapView, this);  

当找到后会调用getView()并传入找到的缓存scrapView,此时我们应该在getView中对convertView进行复用。所以如果每次都进行View.inflate(MainActivity.this, R.layout.listview_item, null);缓存就失去了意义。

所以在getView中如果convertView不为null就要对其复用。更改为如下代码。

  public View getView(int position, View convertView, ViewGroup parent) {
      View view;
      // 判断convertView的状态,来达到复用效果
      if(null == convertView){
          //如果convertView为空,则表示第一次显示该条目,需要创建一个view
          view = View.inflate(Main1Activity.this,R.layout.listview_item,null);
      }else{
          //否则表示可以复用convertView
          view = convertView;
      }
      TextView textView =(TextView) view.findViewById(R.id.tv_item);
      textView.setText(list.get(position));
      return view;
  } 

2. 缓存Item条目

findViewById()这个方法是比较耗性能的操作,因为这个方法要找到指定的布局文件,进行不断地解析每个节点:从最顶端的节点进行一层一层的解析查询,找到后在一层一层的返回,如果在左边没找到,就会接着解析右边,并进行相应的查询,直到找到位置。因此可以对findViewById进行优化处理。

解决:定义控件引用的类ViewHolder

优化后的代码:

   public class MyAdapter extends BaseAdapter{ 
	······		
       @Override
       public View getView(int position, View convertView, ViewGroup parent) {
           ViewHolder  viewHolder;
           if(null == convertView){
               convertView = View.inflate(MainActivity.this,R.layout.listview_item,null);
               viewHolder = new ViewHolder();
               viewHolder.textView = (TextView) convertView.findViewById(R.id.tv_item);
               convertView.setTag(viewHolder);
           }else{
               viewHolder = (ViewHolder) convertView.getTag();
           }

           viewHolder.textView.setText(list.get(position));
           return convertView;
       }
   }
   static class ViewHolder{
       TextView textView;
   }

参考
https://blog.csdn.net/u013278940/article/details/52923807

其他优化

······

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值