Listview与Recycleview的区别-(用法及缓存机制)

用法上的区别

1、listview的用法

  • 继承的时BaseAdapter,需要重写四个方法
  • 不强制使用viewholder
  • 可以直接使用item的点击事件
  • 不用单独设置分隔线
  • 不可以定向刷新某一条数据

示例代码如下:项目代码详见地址:

public class MyListAdapter<T> extends BaseAdapter {

    private static final String TAG = "MyListAdapter";

    private Context mContext;
//    private int itemViewId;
    private List<T> datas;

    public MyListAdapter(Context mContext, /*int itemViewId,*/ List<T> datas) {
        this.mContext = mContext;
//        this.itemViewId = itemViewId;
        this.datas = datas;
    }

    @Override
    public int getCount() {
        return datas == null ? 0 : datas.size();
    }

    @Override
    public T getItem(int position) {
        return datas.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        Log.d(TAG, "getView: "+position);
        MyHolder holder;
        if (convertView==null){
            convertView = View.inflate(mContext,R.layout.ada_item,null);
            holder = new MyHolder(convertView);
            convertView.setTag(holder);
        }else {
            holder = (MyHolder) convertView.getTag();
        }

        holder.itemTv.setText((CharSequence) datas.get(position));

        return convertView;
    }


    static class MyHolder {
        @BindView(R.id.item) TextView itemTv;

        public MyHolder(View view) {
            ButterKnife.bind(this,view);
        }
        }
        }

2、recycleview的用法

  • 继承的是Recycleview.Adapter
  • 必须使用viewholder,封装了view的复用
  • 使用布局管理器管理布局的样式(横向、竖向、网格、瀑布流布局)
  • 点击事件可以使用给控件设置点击事件,也可以自定义点击事件。
  • 可以自定义绘制分隔线
  • 可以自定义item删除增加的动画效果
  • 可以定向刷新某一条数据notifyItemChanged等众多方法

实例代码如下:


    private static final String TAG = "MyRecycleAdapter";

    private Context mContext;
    private List<T> datas;  //可换成泛型

    public MyRecycleAdapter(Context mContext, List<T> datas) {
        this.mContext = mContext;
        this.datas = datas;
    }
    static class MyViewHolder extends RecyclerView.ViewHolder{

        @BindView(R.id.item) TextView itemTv;

        public MyViewHolder(@NonNull View itemView) {
            super(itemView);
            ButterKnife.bind(this,itemView);
        }
    }

    @NonNull
    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i){

        View inflate = LayoutInflater.from(mContext).inflate(R.layout.ada_item, viewGroup, false);
        return new MyViewHolder(inflate);
    }

    @Override
    public void onBindViewHolder(@NonNull MyViewHolder myViewHolder, final int i) {
        Log.d(TAG, "onBindViewHolder: "+i);
//        myViewHolder = new MyViewHolder()
        myViewHolder.itemTv.setText((CharSequence) datas.get(i));
        //设置点击事件---或者使用自定义接口实现
        myViewHolder.itemTv.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(mContext, "点击了: "+i,Toast.LENGTH_LONG).show();
            }
        });
    }

    @Override
    public int getItemCount() {
        return datas==null ? 0:datas.size();
    }
}
recycleview 使用注意点:
  • 必须给recycleview设置布局管理方式
  • 滑动方向的item布局对应属性(垂直-height,横向-width)需要设置为wrap_content,否则会出现一个item占满整个屏幕的bug

缓存上的区别

recycleview的缓存机制

总结:
斜体样式

源码分析:

recycleview的缓存机制是通过内部类实现的,查看recycleview中的recycler,声明如下:

public final class Recycler {
 		//1级缓存集合 --mAttachedScrap
        final ArrayList<RecyclerView.ViewHolder> mAttachedScrap = new ArrayList();
        ArrayList<RecyclerView.ViewHolder> mChangedScrap = null;
        //2级缓存集合 mCachedViews
        final ArrayList<RecyclerView.ViewHolder> mCachedViews = new ArrayList();
        private final List<RecyclerView.ViewHolder> mUnmodifiableAttachedScrap;
        private int mRequestedCacheMax;
        int mViewCacheMax;
        //4级缓存view池
        RecyclerView.RecycledViewPool mRecyclerPool;
        //3级缓存
        private RecyclerView.ViewCacheExtension mViewCacheExtension;
        static final int DEFAULT_CACHE_SIZE = 2;
}

从getViewForPosition追踪,最终走的是tryGetViewHolderForPositionByDeadline方法:

 if (RecyclerView.this.mState.isPreLayout()) {
 //mState.isPreLayout()默认为false,只有有动画时才为true
                    holder = this.getChangedScrapViewForPosition(position);
                    fromScrapOrHiddenOrCache = holder != null;
 }
一级缓存mAttached中获取
二级缓存mCachedViews中获取

开始查找item:

if (holder == null) {
		//获取holder----接下来详细介绍
                    holder = this.getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
                    if (holder != null) {
                    //检验holder是不是当前position
                        if (!this.validateViewHolderForOffsetPosition(holder)) {									
                            if (!dryRun) { //dryRun传过来的值是false
                                holder.addFlags(4);
                                if (holder.isScrap()) {
//不是当前position---从mAttached中删除                                    RecyclerView.this.removeDetachedView(holder.itemView, false);
                                    holder.unScrap();
                                } else if (holder.wasReturnedFromScrap()) {
                                    holder.clearReturnedFromScrapFlag();
                                }
								//把当前view保存到mCachedViews或者 addViewHolderToRecycledViewPool中
                                this.recycleViewHolderInternal(holder);
                            }
							//并将holder置空,进入下一级查找
                            holder = null;
                        } else {
                            fromScrapOrHiddenOrCache = true;
                        }
                    }
                }

获取holder:getScrapOrHiddenOrCachedHolderForPosition

mCachedViews的默认存储大小是: DEFAULT_CACHE_SIZE = 2;

int scrapCount = this.mAttachedScrap.size();

            int cacheSize;
            RecyclerView.ViewHolder vh;
            //从mAttached中获取holder
            for(cacheSize = 0; cacheSize < scrapCount; ++cacheSize) {
                vh = (RecyclerView.ViewHolder)this.mAttachedScrap.get(cacheSize);
                if (!vh.wasReturnedFromScrap() && vh.getLayoutPosition() == position && !vh.isInvalid() && (RecyclerView.this.mState.mInPreLayout || !vh.isRemoved())) {
                ///holder是有效的,并且position相同
                    vh.addFlags(32);
                    return vh;
                }
            }
            
    ===================
    //从mCachedViews中获取holder
     cacheSize = this.mCachedViews.size();

            for(int i = 0; i < cacheSize; ++i) {
                RecyclerView.ViewHolder holder = (RecyclerView.ViewHolder)this.mCachedViews.get(i);
                if (!holder.isInvalid() && holder.getLayoutPosition() == position) {		
                ///holder是有效的,并且position相同
                    if (!dryRun) {
                    	//获取完之后从mCachedViews中移除
                        this.mCachedViews.remove(i);
                    }
                    return holder;
                }
            }
            return null;

从源码可以看到,mCacheViews默认缓存大小是 2,从mAttached和mCacheViews中取的holder都必须要求position是相同的位置,而且不关心type.

也就是说只有原来的位置可以重新复用这里的viewholder,新的位置无法从mCachedViews中去viewholder。因此复用时也不需要重新bindView

在取完holder后才判断位置是否正确,类型是否正确:

 boolean validateViewHolderForOffsetPosition(RecyclerView.ViewHolder holder) {
            if (holder.isRemoved()) {
                return RecyclerView.this.mState.isPreLayout();
            } else if (holder.mPosition >= 0 && holder.mPosition < RecyclerView.this.mAdapter.getItemCount()) {
                if (!RecyclerView.this.mState.isPreLayout()) {
                //type是否核对上
                    int type = RecyclerView.this.mAdapter.getItemViewType(holder.mPosition);
                    if (type != holder.getItemViewType()) {
                        return false;
                    }
                }

                if (RecyclerView.this.mAdapter.hasStableIds()) {
                    return holder.getItemId() == RecyclerView.this.mAdapter.getItemId(holder.mPosition);
                } else {
                    return true;
                }
            } else {
                throw new IndexOutOfBoundsException("Inconsistency detected. Invalid view holder adapter position" + holder + RecyclerView.this.exceptionLabel());
            }
        }
//position的位置没有找到,根据Adapter的id再找一便
                int offsetPosition;
                int type;
                if (holder == null) {
                //
                    offsetPosition = RecyclerView.this.mAdapterHelper.findPositionOffset(position);
                    if (offsetPosition < 0 || offsetPosition >= RecyclerView.this.mAdapter.getItemCount()) {
                        throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item position " + position + "(offset:" + offsetPosition + ")." + "state:" + RecyclerView.this.mState.getItemCount() + RecyclerView.this.exceptionLabel());
                    }

                    type = RecyclerView.this.mAdapter.getItemViewType(offsetPosition);
                    if (RecyclerView.this.mAdapter.hasStableIds()) {
                    //根据ID找
                        holder = this.getScrapOrCachedViewForId(RecyclerView.this.mAdapter.getItemId(offsetPosition), type, dryRun);
                        if (holder != null) {
                            holder.mPosition = offsetPosition;
                            fromScrapOrHiddenOrCache = true;
                        }
                    }
三级缓存:mViewCacheExtension

mViewCacheExtension是Recycleview中提供的一个抽象类,供开发者自定义缓存机制。

 if (holder == null && this.mViewCacheExtension != null) {
 //如果有定义扩展类,从扩展类中查找
                        View view = this.mViewCacheExtension.getViewForPositionAndType(this, position, type);
                        if (view != null) {
                            holder = RecyclerView.this.getChildViewHolder(view);
                            if (holder == null) {
                                throw new IllegalArgumentException("getViewForPositionAndType returned a view which does not have a ViewHolder" + RecyclerView.this.exceptionLabel());
                            }

                            if (holder.shouldIgnore()) {
                                throw new IllegalArgumentException("getViewForPositionAndType returned a view that is ignored. You must call stopIgnoring before returning this view." + RecyclerView.this.exceptionLabel());
                            }
                        }
                    }
四级缓存:RecycledViewPool

根据type获取holder,内部使用的存储是使用SparseArray,存储的是包含ViewHolder集合属性的ScrapData对象。

//默认的缓存大小为:DEFAULT_MAX_SCRAP = 5;
@Nullable
//根据type取的viewholder
        public RecyclerView.ViewHolder getRecycledView(int viewType) {
            RecyclerView.RecycledViewPool.ScrapData scrapData = (RecyclerView.RecycledViewPool.ScrapData)this.mScrap.get(viewType);
            if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) {
                ArrayList<RecyclerView.ViewHolder> scrapHeap = scrapData.mScrapHeap;
                //通过remove方法拿到的viewholder,把与传入的type对应的ViewHolder集合中取最后一个ViewHolder来复用
                return (RecyclerView.ViewHolder)scrapHeap.remove(scrapHeap.size() - 1);
            } else {
                return null;
            }
        }
        =================
        //定义存储的数据结构
         SparseArray<RecyclerView.RecycledViewPool.ScrapData> mScrap = new SparseArray();
           static class ScrapData {
            final ArrayList<RecyclerView.ViewHolder> mScrapHeap = new ArrayList();
            int mMaxScrap = 5;
            long mCreateRunningAverageNs = 0L;
            long mBindRunningAverageNs = 0L;

            ScrapData() {
            }
        }

缓存在ViewPool里的ViewHolder只匹配type。是通过remove方法取的最后一个。

取完ViewHolder后将holder重置,之后ViewHolder就可以作为一个全新的ViewHolder来使用,也就是这个ViewHolder需要重新绑定调用:onBindViewHolder()

if (holder == null) {
    holder = this.getRecycledViewPool().getRecycledView(type);
    if (holder != null) {
    	//重置ViewHolder
        holder.resetInternal();
        if (RecyclerView.FORCE_INVALIDATE_DISPLAY_LIST) {
            this.invalidateDisplayListInt(holder);
        }
    }
}
最终创建:createViewHolder
  holder = RecyclerView.this.mAdapter.createViewHolder(RecyclerView.this, type);

Recycleview的回收机制

由LayoutManager遍历移出屏幕的卡位,然后对每个卡位进行回收操作,回收时,都是把ViewHolder装到mCachedViews里,如果mCachedViews满了,则将第一个移动ViewPool里。
在这里插入图片描述

源码分析

    void recycleViewHolderInternal(RecyclerView.ViewHolder holder) {
        if (!holder.isScrap() && holder.itemView.getParent() == null) {
          .........
                if (forceRecycle || holder.isRecyclable()) {
                    if (this.mViewCacheMax > 0 && !holder.hasAnyOfTheFlags(526)) {
                        int cachedViewSize = this.mCachedViews.size();
                        if (cachedViewSize >= this.mViewCacheMax && cachedViewSize > 0) {                            //(mViewCacheMax 默认是2)如果满了把第一个移ViewPool中去
                            this.recycleCachedViewAt(0);
                            --cachedViewSize;
                        }

                        int targetCacheIndex = cachedViewSize;
                        if (RecyclerView.ALLOW_THREAD_GAP_WORK && cachedViewSize > 0 && !RecyclerView.this.mPrefetchRegistry.lastPrefetchIncludedPosition(holder.mPosition)) {
                            int cacheIndex;
                            for(cacheIndex = cachedViewSize - 1; cacheIndex >= 0; --cacheIndex) {
                                int cachedPos = ((RecyclerView.ViewHolder)this.mCachedViews.get(cacheIndex)).mPosition;
                                if (!RecyclerView.this.mPrefetchRegistry.lastPrefetchIncludedPosition(cachedPos)) {
                                    break;
                                }
                            }
                            targetCacheIndex = cacheIndex + 1;
                        }
						//添加到mCachedViews中
                        this.mCachedViews.add(targetCacheIndex, holder);
                        cached = true;
                    }
					//添加到 ViewPool中
                    if (!cached) {
                        this.addViewHolderToRecycledViewPool(holder, true);
                        recycled = true;
                    }
                }
				........
            }
        } else {
			......
        }
    }

**注意:**Recycleview是先复用再回收的。

综上:listview的缓存机制比Recycleview的较为简单一些,当处理轻量级list时recycleBin的缓存机制会是的显示的效率更高,因此,listview还未过时。

后续会更新demo的缓存机制分析过程,demo代码地址:https://github.com/MarinaTsang/TestDemo

参考博客:

  1. https://blog.csdn.net/tencent_bugly/article/details/52981210
  2. https://www.jianshu.com/p/e44961f8add5
  3. https://www.cnblogs.com/dasusu/p/7746946.html

猜你喜欢

转载自blog.csdn.net/MarinaTsang/article/details/83870753