首页 » 技术分享 » RecyclerView原理分析

RecyclerView原理分析

 

前言

RecyclerView是谷歌官方出的一个用于大量数据展示的新控件,可以用来代替传统的ListView,更加强大和灵活。

支持RecyclerView高效运行的主要六大类:

  1. Adapter:为每一项Item创建视图
  2. ViewHolder:承载Item视图的子布局
  3. LayoutManager:负责Item视图的布局的显示管理
  4. ItemDecoration:给每一项Item视图添加子View,例如可以进行画分隔线之类
  5. ItemAnimator:负责处理数据添加或者删除时候的动画效果
  6. Recycler:负责RecyclerView中子View的回收与复用

Adapter

适配器的作用都是类似的,用于提供每个 item 视图,并返回给RecyclerView 作为其子布局添加到内部。

但是,与ListView不同的是,ListView的适配器是直接返回一个View,将这个 View加入到ListView内部。而RecyclerView是返回一个ViewHolder并且不是直接将这个holder加入到视图内部,而是加入到一个缓存区域,在视图需要的时候去缓存区域找到holder再间接的找到holder包裹的View。

ViewHolder

我们写ListView的时候一般也会自己实现一个ViewHolder,它会持有一个View,避免不必要的findViewById(),来提高效率。

我们使用RecyclerView的时候必须创建一个继承自RecyclerView.ViewHolder的类。这主要是因为RecyclerView内部的缓存结构并不是像ListView那样去缓存一个View,而是直接缓存一个ViewHolder,在ViewHolder的内部又持有了一个View。既然是缓存一个ViewHolder,那么当然就必须所有的ViewHolder 都继承同一个类才能做到了。

LayoutManager

顾名思义,LayoutManager是布局的管理者。实际上,RecyclerView就是将 onMeasure()、onLayout()交给了LayoutManager去处理,因此如果给 RecyclerView设置不同的LayoutManager就可以达到不同的显示效果。

onMeasure()

RecyclerView的onMeasure()最终都会调用:mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);,这里的mLayout就是LayoutManager,最终还是调用了RecyclerView自己的方法对布局进行了测量。

onLayoutChildren()

RecyclerView的onLayout()中会调用:mLayout.onLayoutChildren(mRecycler, mState);,这个方法在LayoutManager中是空实现:

        public void onLayoutChildren(Recycler recycler, State state) {
            Log.e(TAG, "You must override onLayoutChildren(Recycler recycler, State state) ");
        }

所以LayoutManager的子类都应该实现onLayoutChildren(),这里就将layout()的工作交给了LayoutManager的实现类,来完成对子View的布局。

ItemDecoration

ItemDecoration是为了显示每个item之间分隔样式的。它的本质实际上就是一个Drawable。当RecyclerView执行到onDraw()方法的时候,就会调用到他的 onDraw(),这时,如果你重写了这个方法,就相当于是直接在RecyclerView 上画了一个Drawable表现的东西。而最后,在他的内部还有一个叫getItemOffsets()的方法,从字面就可以理解,他是用来偏移每个item视图的。当我们在每个item视图之间强行插入绘画了一段 Drawable,那么如果再照着原本的逻辑去绘 item视图,就会覆盖掉Decoration了,所以需要getItemOffsets()这个方法,让每个item往后面偏移一点,不要覆盖到之前画上的分隔样式了。

ItemAnimator

每一个item在特定情况下都会执行的动画。说是特定情况,其实就是在视图发生改变,我们手动调用notifyxxxx()的时候。通常这个时候我们会要传一个下标,那么从这个标记开始一直到结束,所有 item 视图都会被执行一次这个动画。

Recycler

    public final class Recycler {
        final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
        ArrayList<ViewHolder> mChangedScrap = null;

        final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();

        private final List<ViewHolder>
                mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);

        private int mRequestedCacheMax = DEFAULT_CACHE_SIZE;
        int mViewCacheMax = DEFAULT_CACHE_SIZE;

        private RecycledViewPool mRecyclerPool;

        private ViewCacheExtension mViewCacheExtension;
        ……
    }

RecyclerView拥有四级缓存

  1. 屏幕内缓存 :指在屏幕中显示的ViewHolder,这些ViewHolder会缓存在mAttachedScrapmChangedScrap中 。

    • mChangedScrap表示数据已经改变的ViewHolder列表
    • mAttachedScrap未与RecyclerView分离的ViewHolder列表
  2. 屏幕外缓存:当列表滑动出了屏幕时,ViewHolder会被缓存在 mCachedViews,其大小由mViewCacheMax决定,默认DEFAULT_CACHE_SIZE为2,可通过Recyclerview.setItemViewCacheSize()动态设置。

  3. 自定义缓存:可以自己实现ViewCacheExtension类实现自定义缓存,可通过Recyclerview.setViewCacheExtension()设置。通常我们也不会去设置他,系统已经预先提供了两级缓存了,除非有特殊需求,比如要在调用系统的缓存池之前,返回一个特定的视图,才会用到他。

  4. 缓存池 :ViewHolder首先会缓存在mCachedViews中,当超过了2个(默认为2),就会添加到mRecyclerPool中。mRecyclerPool会根据ViewType把ViewHolder分别存储在不同的集合中,每个集合最多缓存5个ViewHolder。

缓存策略
在LayoutManager执行layoutChildren()中获取子View的时候,会调用RecyclerView的getViewForPosition()

        View getViewForPosition(int position, boolean dryRun) {
            if (position < 0 || position >= mState.getItemCount()) {
                throw new IndexOutOfBoundsException("Invalid item position " + position
                        + "(" + position + "). Item count:" + mState.getItemCount());
            }
            boolean fromScrap = false;
            ViewHolder holder = null;
            // 0) If there is a changed scrap, try to find from there
            if (mState.isPreLayout()) {
                // 从mChangedScrap找
                holder = getChangedScrapViewForPosition(position);
                fromScrap = holder != null;
            }
            // 1) Find from scrap by position
            if (holder == null) {
                // 通过position从mAttachedScrap找,找不到再通过position从mCachedViews找
                holder = getScrapViewForPosition(position, INVALID_TYPE, dryRun);
                if (holder != null) {
                    if (!validateViewHolderForOffsetPosition(holder)) {
                        // recycle this scrap
                        if (!dryRun) {
                            // we would like to recycle this but need to make sure it is not used by
                            // animation logic etc.
                            holder.addFlags(ViewHolder.FLAG_INVALID);
                            if (holder.isScrap()) {
                                removeDetachedView(holder.itemView, false);
                                holder.unScrap();
                            } else if (holder.wasReturnedFromScrap()) {
                                holder.clearReturnedFromScrapFlag();
                            }
                            recycleViewHolderInternal(holder);
                        }
                        holder = null;
                    } else {
                        fromScrap = true;
                    }
                }
            }
            if (holder == null) {
                final int offsetPosition = mAdapterHelper.findPositionOffset(position);
                if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) {
                    throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item "
                            + "position " + position + "(offset:" + offsetPosition + ")."
                            + "state:" + mState.getItemCount());
                }

                final int type = mAdapter.getItemViewType(offsetPosition);
                // 2) Find from scrap via stable ids, if exists
                if (mAdapter.hasStableIds()) {
                    // 通过id从mAttachedScrap找,找不到再通过id从mCachedViews找
                    holder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
                    if (holder != null) {
                        // update position
                        holder.mPosition = offsetPosition;
                        fromScrap = true;
                    }
                }
                if (holder == null && mViewCacheExtension != null) {
                    // 从mViewCacheExtension找
                    // We are NOT sending the offsetPosition because LayoutManager does not
                    // know it.
                    final View view = mViewCacheExtension
                            .getViewForPositionAndType(this, position, type);
                    if (view != null) {
                        holder = getChildViewHolder(view);
                        if (holder == null) {
                            throw new IllegalArgumentException("getViewForPositionAndType returned"
                                    + " a view which does not have a ViewHolder");
                        } else if (holder.shouldIgnore()) {
                            throw new IllegalArgumentException("getViewForPositionAndType returned"
                                    + " a view that is ignored. You must call stopIgnoring before"
                                    + " returning this view.");
                        }
                    }
                }

                if (holder == null) { // fallback to recycler
                    // 从mRecyclerPool找
                    // try recycler.
                    // Head to the shared pool.
                    if (DEBUG) {
                        Log.d(TAG, "getViewForPosition(" + position + ") fetching from shared "
                                + "pool");
                    }
                    holder = getRecycledViewPool().getRecycledView(type);
                    if (holder != null) {
                        holder.resetInternal();
                        if (FORCE_INVALIDATE_DISPLAY_LIST) {
                            invalidateDisplayListInt(holder);
                        }
                    }
                }
                if (holder == null) {
                    // 从缓存中找不到,创建新的ViewHolder
                    holder = mAdapter.createViewHolder(RecyclerView.this, type);
                    if (DEBUG) {
                        Log.d(TAG, "getViewForPosition created new ViewHolder");
                    }
                }
            }
            ……
        }

Recyclerview在获取ViewHolder时按四级缓存的顺序查找,如果没找到就创建。

通过了解RecyclerView的四级缓存,我们可以知道,RecyclerView最多可以缓存N(屏幕最多可显示的item数)+ 2 (屏幕外的缓存) + 5*M (M代表M个ViewType,缓存池的缓存)。

还需要注意的是,RecyclerViewPool可以被多个RecyclerView共享。

参考:
1.Android ListView 与 RecyclerView 对比浅析:缓存机制
2.深入浅出 RecyclerView
3.关于Recyclerview的缓存机制的理解

转载自原文链接, 如需删除请联系管理员。

原文链接:RecyclerView原理分析,转载请注明来源!

0