美文网首页
RecyclerView 源码分析(一):Recycler

RecyclerView 源码分析(一):Recycler

作者: MrFengZH | 来源:发表于2019-08-04 18:51 被阅读0次

    前言

    RecyclerView 是一个好用又复杂的控件,其功能的高度解耦化,规范化的 ViewHolder 写法,以及对动画的友好支持,都是它与传统 ListView 的区别。

    它有几大模块:

    • LayoutManager:控制 item 的布局
    • RecyclerView.Adapter:为 RecyclerView 提供数据
    • ItemDecoration:为 RecyclerView 添加分割线
    • ItemAnimator:控制 item 的动画
    • Recycler:负责回收和提供 View,和 RecyclerView 的复用机制相关

    下面就从源码(API 28)角度分析 RecyclerView,RecyclerView 的源码很复杂,很难在一篇文章内讲完,所以打算分几篇来讲,本文是第一篇,将围绕 RecyclerView 的内部类 Recycler 展开分析:

    RecyclerView.Recycler

    首先看一下它的作用,源码上是这样写的:

    A Recycler is responsible for managing scrapped or detached item views for reuse.

    意思就是 Recycler 负责管理废弃或被 detached 的 item 视图,以便重复利用。

    它有以下几个成员变量:

    主要成员变量

    
        final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
        
        ArrayList<ViewHolder> mChangedScrap = null;
    
        final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
    
        RecycledViewPool mRecyclerPool;
    
        private ViewCacheExtension mViewCacheExtension;
    

    这几个成员变量都和 RecyclerView 的缓存相关,如果按照四级缓存的话,它们可以这样划分:

    第一级缓存:mAttachedScrap、mChangedScrap

    第二级缓存:mCachedViews

    第三级缓存:ViewCacheExtension

    第四级缓存:RecycledViewPool

    后面再介绍 mAttachedScrap、mChangedScrap、mCachedViews 具体存的是哪些 ViewHolder。

    现在先了解下 RecycledViewPool 和 ViewCacheExtension这两个类:

    RecycledViewPool

    继续先看官方注释:

    RecycledViewPool lets you share Views between multiple RecyclerViews.

    RecycledViewPool 用于在多个 RecyclerView 间共享 View。

    在使用时,只需创建 RecycledViewPool 实例,然后调用 RecyclerView 的 setRecycledViewPool(RecycledViewPool) 方法即可。

    RecycledViewPool 存储在 Recycler 中,通过 Recycler 存取。

    成员变量

    RecycledViewPool 有一个重要的成员变量:

        // SparseArray 类似于 key 为 int 类型 的 HashMap
        SparseArray<ScrapData> mScrap = new SparseArray<>();
    

    其中 ScrapData 的定义如下:

        static class ScrapData {
            final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
            int mMaxScrap = DEFAULT_MAX_SCRAP;  // 5
            long mCreateRunningAverageNs = 0;
            long mBindRunningAverageNs = 0;
        }
    

    mScrap 是一个 <int, ScrapData> 的映射,其中 int 代表了 viewType,ScrapData 则存储了一个 ViewHolder 集合。

    主要方法

    getScrapDataForType

        private ScrapData getScrapDataForType(int viewType) {
            ScrapData scrapData = mScrap.get(viewType);
            if (scrapData == null) {
                scrapData = new ScrapData();
                mScrap.put(viewType, scrapData);
            }
            return scrapData;
        }
    

    该方法根据 viewType 获取相应的 ScrapData,如果该 viewType 还没有绑定 ScrapData,就新创建一个 ScrapData 并绑定到该 viewType。

    setMaxRecycledViews

        public void setMaxRecycledViews(int viewType, int max) {
            ScrapData scrapData = getScrapDataForType(viewType);
            scrapData.mMaxScrap = max;
            final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
            // 从后面开始删除,直到满足新的容量
            while (scrapHeap.size() > max) {
                scrapHeap.remove(scrapHeap.size() - 1);
            }
        }
    

    该方法可以设置相应 viewType 的 View 容量,超出容量时,从后面开始删除,直到满足新的容量。

    getRecycledView

        public ViewHolder getRecycledView(int viewType) {
            final ScrapData scrapData = mScrap.get(viewType);
            if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) {
                final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
                return scrapHeap.remove(scrapHeap.size() - 1);
            }
            return null;
        }
    

    该方法根据 viewType 获取一个 ViewHolder,获取到的 ViewHolder 将会被移除出 Scrap 堆。获取不到则返回 null。

    putRecycledView

        public void putRecycledView(ViewHolder scrap) {
            final int viewType = scrap.getItemViewType();
            final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;
            // 容量已满,不再添加
            if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
                return;
            }
            // 重置 ViewHolder,例如清空 flags
            scrap.resetInternal();
            // 将该 ViewHolder 添加到对应 viewType 的 集合中缓存起来
            scrapHeap.add(scrap);
        }
    

    该方法也很好理解,根据 ViewHolder 的 viewType 放入 RecycledViewPool 的相应集合中,如果集合已满,不再添加。

    接下来看另一个类:

    ViewCacheExtension

    ViewCacheExtension 是一个由开发者控制的 View 缓存帮助类,其定义如下:

        public abstract static class ViewCacheExtension {
    
            /**
             * Returns a View that can be binded to the given Adapter position.
             */
            @Nullable
            public abstract View getViewForPositionAndType(@NonNull Recycler recycler, int position,
                    int type);
        }
    

    开发者可以实现这个抽象类,通过调用 RecyclerView 的 setViewCacheExtension(ViewCacheExtension) 方法设置,最终将 ViewCacheExtension 存储在 Recycler 中。

    当调用 Recycler 的 getViewForPosition 方法时,如果 attached scrap 和 已经缓存都没有找到合适的 View,就会调用 ViewCacheExtension 的 getViewForPositionAndType 方法来获取 View。

    需要注意的是,Recycler 不会对这个类做任何缓存处理,是否需要缓存 View 由开发者自己控制。

    主要方法

    看完这两个类,现在回到 Recycler 中,看一下 Rcycler 的主要方法:

    getViewForPosition

    getViewForPosition 方法比较重要,用于获取某个位置需要展示的 View,如下:

        public View getViewForPosition(int position) {
            return getViewForPosition(position, false);
        }
        
        View getViewForPosition(int position, boolean dryRun) {
            return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
        }
    

    继续看 tryGetViewHolderForPositionByDeadline 方法,该方法会依次从几个缓存中获取,分别来看一下:

        // 如果是处于预布局阶段(先简单理解为执行 dispatchLayoutStep1 方法)
        // (其实下面方法要返回 ture 还需要开启“预处理动画”,这跟动画有关,先不多说)
        if (mState.isPreLayout()) {
            holder = getChangedScrapViewForPosition(position);
            fromScrapOrHiddenOrCache = holder != null;
        }
    

    第一步,从 mChangedScrap 中获取,获取不到就返回 null。

    如果 holder 还是为 null,执行下面代码:

        // 1) Find by position from scrap/hidden list/cache
        if (holder == null) {
            holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
            if (holder != null) {
                if (!validateViewHolderForOffsetPosition(holder)) {
                    // recycle holder (and unscrap if relevant) since it can't be used
                    // 回收无效的 ViewHolder
                    // ...
                } else {
                    fromScrapOrHiddenOrCache = true;
                }
            }
        }
    

    第二步,根据 position 依次从 mAttachedScrap、mHiddenViews(存储在 ChildHelper 类)、mCachedViews 中获取缓存的 ViewHolder。

    可以从 mHiddenViews 获取到缓存的话,就将其从 mHiddenViews 移除并添加到 Scrap 缓存(根据情况添加到 mAttachedScrap 或 mChangedScrap)。可以从 mCacheViews 中获取到缓存的话,就将其从 mCacheViews 移除。

    获取到后,发现无效的话,将对获取到的 ViewHolder 进行清理并回收(放入 mCachedViews 或 RecycledViewPool)。

    获取不到,就继续往下执行:

        // 默认返回 false,可通过 Adapter.setHasStableIds 方法设置该值
        if (mAdapter.hasStableIds()) {
            // 根据 id 依次在 mAttachedScrap、mCachedViews 中获取缓存
            holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
                    type, dryRun);
            if (holder != null) {
                holder.mPosition = offsetPosition;
                fromScrapOrHiddenOrCache = true;
            }
        }
    

    第三步,根据 id 依次从 mAttachedScrap、mCachedViews 中获取缓存,还没有获取到就继续往下:

        // 如果用户设置了 ViewCacheExtension
        if (holder == null && mViewCacheExtension != null) {
            // 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);
                // ...
            }
        }
    

    第四步,从用户设置的 ViewCacheExtension 中获取缓存,没有获取到就继续往下:

        if (holder == null) { // fallback to pool
            holder = getRecycledViewPool().getRecycledView(type);
            // ...
        }
    

    第五步,根据 viewType 从 RecycledViewPool 中得到缓存。

    RecycledViewPool 已经是最后一级缓存了,如果这里也没有获取到,只能通过 Adapter 的 createViewHolder 方法创建一个 ViewHolder:

        if (holder == null) {
    
            holder = mAdapter.createViewHolder(RecyclerView.this, type);
    
            // ...
        }
    

    最后小结一下获取某个位置的 View 的过程:

    1. 先后根据 position 或 id 从 mChangedScrap 中获取缓存
    2. 根据 position 依次从 mAttachedScrap、mHiddenViews(存储在 ChildHelper 类)、mCachedViews 中获取缓存
    3. 根据 id 依次从 mAttachedScrap、mCachedViews 中获取缓存
    4. 从用户设置的 ViewCacheExtension 中获取缓存
    5. 从 RecycledViewPool 中得到缓存的废弃 ViewHolder
    6. 通过 Adapter 的 createViewHolder 方法创建一个 ViewHolder

    recycleView

    既然叫 Recycler,那肯定要做回收工作了,recycleView 方法就完成了这些工作,下面看一下该方法的实现:

        public void recycleView(@NonNull View view) {
            ViewHolder holder = getChildViewHolderInt(view);
            // ...
            recycleViewHolderInternal(holder);
        }
    

    继续看 recycleViewHolderInternal:

        void recycleViewHolderInternal(ViewHolder holder) {
            // ...
    
            if (forceRecycle || holder.isRecyclable()) {
                if (mViewCacheMax > 0
                        && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
                        | ViewHolder.FLAG_REMOVED
                        | ViewHolder.FLAG_UPDATE
                        | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
                            
                    int cachedViewSize = mCachedViews.size();
                    // 若 CacheViews 达到最大容量(2),将最老的缓存从 CacheViews 移除,并添加到 RecycledViewPool 中
                    if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
                        recycleCachedViewAt(0);
                        cachedViewSize--;
                    }
    
                    // ...
                    
                    // 将 View 缓存到 mCachedViews 中
                    mCachedViews.add(targetCacheIndex, holder);
                    cached = true;
                }
                if (!cached) {
                    // 没有添加到 mCachedViews 的话,就添加到 RecycledViewPool 中
                    addViewHolderToRecycledViewPool(holder, true);
                    recycled = true;
                }
            }
    
            // ...
        }
    

    可以看到,回收过程主要涉及到两层缓存,第一层缓存是 CacheViews,在添加时,如果发现原来的 CacheViews 已经达到最大容量,就将最老的缓存从 CacheViews 移除,并添加到 RecycledViewPool。第二层缓存是 RecycledViewPool,如果不能添加到 mCacheViews,就会添加到 RecycledViewPool 中。

    补充

    mChangedScrap 和 mAttachedScrap 中的 View 从何而来

    从前面可以得知,在执行 Recycler 的 recycleView 方法时,会将回收的 View 缓存到 mCahceViews 或 recycledViewPool 中,那么另外两个 Scrap 缓存(mChangedScrap 和 mAttachedScrap)中的 View 是何时添加进来的呢?

    无论是 mAttachedScrap 还是 mChangedScrap ,它们获得 View 的途径都只有一个,那就是通过 Recycler 的 scrapView 方法。先看下该方法:

    Recycler#scrapView

        void scrapView(View view) {
            final ViewHolder holder = getChildViewHolderInt(view);
            
            // 满足这几个条件中的一个就可以进入 if 循环,有机会将 View 缓存到 mAttachedScrap
            // 1. ViewHolder 设置了 FLAG_REMOVED 或 FLAG_INVALID
            // 2. ViewHolder 没有设置 FLAG_UPDATE
            // 3. 没有设置动画或者动画可以重用该 ViewHolder
            if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
                    || !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
                if (holder.isInvalid() && !holder.isRemoved() && !mAdapter.hasStableIds()) {
                    throw new IllegalArgumentException("Called scrap view with an invalid view."
                            + " Invalid views cannot be reused from scrap, they should rebound from"
                            + " recycler pool." + exceptionLabel());
                }
                // 给 ViewHolder 绑定 Recycler
                holder.setScrapContainer(this, false);
                mAttachedScrap.add(holder);
            } 
            // 不满足上述任意一个条件时,将 View 缓存到 mChangedScrap 中
            else {
                if (mChangedScrap == null) {
                    mChangedScrap = new ArrayList<ViewHolder>();
                }
                holder.setScrapContainer(this, true);
                mChangedScrap.add(holder);
            }
        }
    

    该方法通过判断 ViewHolder 的 flag 以及是否设置 ItemAnimator 等,决定将 View 缓存到 mAttachedScrap 还是 mChangedScrap。

    那么该方法在何时调用呢?有两种情况:

    1. 以 LinearLayoutManager 为例,在它的 onLayoutChildren 方法中,会调用
        detachAndScrapAttachedViews(recycler);
    

    该方法定义在 RecyclerView 的 LayoutManager 中,它继续调用 scrapOrRecycleView 方法,如果在该方法符合条件就调用 Recycler 的 scrapView 方法。

    1. 通过 mHiddenViews 获取到缓存时,也会调用 scrapView 方法。

    场景分析

    下面就根据一些场景来分析下 Recycler 是如何进行回收和复用的。

    第一次 layout

    由于这里不是专门分析 layout 过程的,就不从 onLayout 开始说了,中间的过程省略掉,它最终会调用到 LayoutManager 的 onLayoutChildren,这里以 LinearLayoutManager 为例:

    LinearLayoutManager#onLayoutChildren

        @Override
        public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
            // ...
    
            // 找到锚点(具体过程等到分析 layout 时再说)
            
            // (1)
            detachAndScrapAttachedViews(recycler);
            
            if (mAnchorInfo.mLayoutFromEnd) {
                // ...
            } else {
                
                // (2)
                fill(recycler, mLayoutState, state, false);
    
                // ...
            }
            
            // ...
        }
    

    首先看(1)处,detachAndScrapAttachedViews 方法会根据情况将子 View 回收到相应缓存,具体过程之后再看,由于现在是第一次 layout,RecyclerView 中没有子 View,所以现在该方法没啥用。

    接下来看(2)处,这里的 fill 方法比较重要,它的作用是填充布局。看一下该方法:

    LinearLayoutManager#fill

        int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
                RecyclerView.State state, boolean stopOnFocusable) {
    
            // 进行 layout 时 layoutState.mScrollingOffset 的值被设置为
            // LayoutState.SCROLLING_OFFSET_NaN,不会进入此 if 块
            if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
                // ...
                recycleByLayoutState(recycler, layoutState);
            }
            
            // 需要填充的空间
            int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
            // 还有需要填充的空间并且 item 数未满
            while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
                // ...
                
                // (1)
                layoutChunk(recycler, state, layoutState, layoutChunkResult);
    
                // 计算剩余空间
    
                // 同上,在 layout 时不会进入 if 块中
                if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
                    // ...
                    recycleByLayoutState(recycler, layoutState);
                }
                
                // ...
            }
        }
    

    主要看(1)处的 layoutChunk 方法,只要还有需要填充的空间,就会不断调用该方法:

    LinearLayoutManager#layoutChunk

        void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
                LayoutState layoutState, LayoutChunkResult result) {
            // (1)
            View view = layoutState.next(recycler);
    
            // ...
            
            // 默认情况下,layoutState.mScrapList 等于 null
            if (layoutState.mScrapList == null) {
                if (mShouldReverseLayout == (layoutState.mLayoutDirection
                        == LayoutState.LAYOUT_START)) {
                    // (2)
                    addView(view);
                } else {
                    addView(view, 0);
                }
            } else {
                // ...
            }
        }
    

    (2)处的 addView 方法就不多说了,该方法将得到的子 View 添加到 RecyclerView 中。主要看(1)处,看看子 View 从何而来:

        View next(RecyclerView.Recycler recycler) {
            // ...
            
            final View view = recycler.getViewForPosition(mCurrentPosition);
    
            return view;
        }
    

    这个方法是不是很熟悉呢?没错,它就是之前分析的 Recycler 的 getViewForPosition 方法。

    不过由于现在没有任何缓存,所以第一次 layout 的时候是通过 Adapter 的 createViewHolder 来创建子 View的,并且没有添加任何缓存。

    更新列表

    更新列表可以使用 Adapter 的一系列 notify 方法,这里分析其中两个方法:notifyDataSetChanged 和 notifyItemChanged(int)。

    Adapter#notifyDataSetChanged

    该方法最终调用了 RecyclerViewDataObserver 的 onChanged 方法:

        @Override
        public void onChanged() {
            // ...
    
            // 该方法主要做了这两件事
            // 1. 给所有 ViewHolder 添加了 FLAG_UPDATE 和 FLAG_INVALID
            // 2. 默认情况下(mHasStableIds 为 false)清空 CacheViews
            processDataSetCompletelyChanged(true);
            
            if (!mAdapterHelper.hasPendingUpdates()) {
                // 进行视图重绘
                requestLayout();
            }
        }
    

    该方法会进行视图重绘,又来到了 layout 过程,继续以 LinearLayoutManager 为例,从它的 onLayoutChildren 方法看起,由于分析第一次 layout 时已经看过一遍了,这次主要看下不同之处:

        @Override
        public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
            // ...
            
            detachAndScrapAttachedViews(recycler);
            
            // ...
        }
    

    主要区别在于 detachAndScrapAttachedViews 方法,这次它开始起作用了,该方法在 RecyclerView 的 LayoutManager 中定义,看下它的实现:

    LayoutManager#detachAndScrapAttachedViews

        public void detachAndScrapAttachedViews(@NonNull Recycler recycler) {
            final int childCount = getChildCount();
            for (int i = childCount - 1; i >= 0; i--) {
                final View v = getChildAt(i);
                scrapOrRecycleView(recycler, i, v);
            }
        }
    

    由于不是第一次 layout,RecyclerView 这时已经有子 View 了,该方法遍历子 View,调用 scrapOrRecycleView 方法:

        private void scrapOrRecycleView(Recycler recycler, int index, View view) {
            final ViewHolder viewHolder = getChildViewHolderInt(view);
            // 不能回收添加了 FLAG_IGNORE 标记的 ViewHolder
            // 可通过 LayoutManager 的 ignoreView 为相应的 View 添加该标记
            if (viewHolder.shouldIgnore()) {
                return;
            }
            // 这些条件都满足,进入 if 块
            if (viewHolder.isInvalid() && !viewHolder.isRemoved()
                    && !mRecyclerView.mAdapter.hasStableIds()) {
                removeViewAt(index);
                recycler.recycleViewHolderInternal(viewHolder);
            } else {
                // ...
            }
        }
    

    这里将子 View 移除并通过 Recycler 的 recycleViewHolderInternal 方法进行回收:

    Recycler#recycleViewHolderInternal

            void recycleViewHolderInternal(ViewHolder holder) {
                // ...
                boolean cached = false;
                boolean recycled = false;
    
                if (forceRecycle || holder.isRecyclable()) {
                    // 由于此时的 ViewHolder 有 FLAG_INVALID 标记,不会进入此 if 块
                    if (mViewCacheMax > 0
                            && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
                            | ViewHolder.FLAG_REMOVED
                            | ViewHolder.FLAG_UPDATE
                            | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
                        //...
                    }
                    // cached 仍为 false,进入此 if 块
                    if (!cached) {
                        // 通过 RecycledViewPool 的 putRecycledView 方法缓存该 ViewHolder
                        addViewHolderToRecycledViewPool(holder, true);
                        recycled = true;
                    }
                } 
                
                // ...
            }
    

    最终被移除的子 View 缓存到了 RecycledViewPool 中。

    后面在调用 fill 方法进行布局填充时,就可以从 RecycledViewPool 中拿取缓存的 View。

    Adapter#notifyItemChanged

    该方法传入一个 int 参数,表示要数据有更新的 item 的 position。

        public final void notifyItemChanged(int position) {
            mObservable.notifyItemRangeChanged(position, 1);
        }
    

    最终调用 RecyclerViewDataObserver 的 onItemRangeChanged 方法:

        @Override
        public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
            // 会在 mAdapterHelper 中创建一个 UpdateOp,将信息保存起来
            if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) {
                // 如果可以进行更新操作,执行该方法
                triggerUpdateProcessor();
            }
        }
    

    继续看 triggerUpdateProcessor 方法:

        void triggerUpdateProcessor() {
            // 判断条件默认为 false,执行 else 块
            if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
                // ...
            } else {
                mAdapterUpdateDuringMeasure = true;
                requestLayout();
            }
        }
    

    在保存了一些信息后,还是进行视图重绘。来到了 layout 过程后,还是以 LinearLayoutManager 为例,这次先看下布局过程的 step1,也就是 dispatchLayoutStep1 方法:

    RecyclerView#dispatchLayoutStep1

        private void dispatchLayoutStep1() {
            // ...
            
            processAdapterUpdatesAndSetAnimationFlags();
            
            // ...
        }
    

    主要看 processAdapterUpdatesAndSetAnimationFlags 方法,从名字也可以看出,它负责更新 adapter 的信息:

        private void processAdapterUpdatesAndSetAnimationFlags() {
            // ...
    
            if (predictiveItemAnimationsEnabled()) {
                mAdapterHelper.preProcess();
            } else {
                mAdapterHelper.consumeUpdatesInOnePass();
            }
    
            // ...
        }
    

    这里借助了 mAdapterHelper,它最终又通过接口回调(回调了 markViewHoldersUpdated 方法)调用了 RecyclerView 的 viewRangeUpdate 方法:

        void viewRangeUpdate(int positionStart, int itemCount, Object payload) {
            // ...
    
            for (int i = 0; i < childCount; i++) {
                // ...
                
                if (holder.mPosition >= positionStart && holder.mPosition < positionEnd) {
                    // (1)
                    holder.addFlags(ViewHolder.FLAG_UPDATE);
                    // ...
                }
            }
        }
    

    该方法就是遍历所有子 View,找到所有发生了改变的子 View,进行相关操作。这里重点看注释(1),为改变的 ViewHolder 添加了 FLAG_UPDATE 标记。先记住这点,在后面会用到。

    接下来看 onLayoutChildren 方法,和 notifyDataSetChanged 一样,主要的不同之处也是在于 detachAndScrapAttachedViews 方法,该方法遍历子 View,调用 scrapOrRecycleView 方法,下面看一下该方法:

    LayoutManager#scrapOrRecycleView

        private void scrapOrRecycleView(Recycler recycler, int index, View view) {
            final ViewHolder viewHolder = getChildViewHolderInt(view);
            // ...
            
            // 这次 ViewHolder 没有添加 FLAG_INVALID 标记,进入 else 块
            if (viewHolder.isInvalid() && !viewHolder.isRemoved()
                    && !mRecyclerView.mAdapter.hasStableIds()) {
                // ...
            } else {
                detachViewAt(index);
                recycler.scrapView(view);
                mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
            }
        }
    

    这里就和 notifyDataSetChanged 时不一样了,由于在视图重绘前没有给 ViewHolder 添加 FLAG_INVALID 标记,这次进入的是 else 块。

    首先将 View 从 RecyclerView 中 detach 掉(而不是 remove 掉)。然后在回收时,调用的是 Recycler 的 scrapView 方法。该方法在前面也分析过了,这里再看一次:

        void scrapView(View view) {
            final ViewHolder holder = getChildViewHolderInt(view);
            
            // 满足这几个条件中的一个就可以进入 if 循环
            // 1. ViewHolder 设置了 FLAG_REMOVED 或 FLAG_INVALID 
            // 2. ViewHolder 没有设置 FLAG_UPDATE 
            // 3. 没有设置动画或者动画可以重用该 ViewHolder 
            if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
                    || !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
                // ...
                
                mAttachedScrap.add(holder);
            } 
            // 不满足上述任意一个条件时,将 View 缓存到 mChangedScrap 中
            else {
                if (mChangedScrap == null) {
                    mChangedScrap = new ArrayList<ViewHolder>();
                }
                holder.setScrapContainer(this, true);
                mChangedScrap.add(holder);
            }
        }
    

    重点看判断里面的条件 2,从前面的分析可以得知,对于发生改变的 ViewHolder,给它设置了 FLAG_UPDATE,所以它现在三个条件都不满足,进入 else 块,而对于其他的 ViewHolder,由于没有设置 FLAG_UPDATE,所以满足条件 2,进入 if 循环。

    所以通过 notifyItemChanged 方法更新列表时,发生了改变的子 View 将被缓存到 ChangedScrap 中,而没有发生改变的子 View 则缓存到 AttachedScrap 中,之后通过填充布局的时候对于不同 item 就可以从相应的 Scrap 缓存中得到子 View。

    另外,Scrap 缓存只作用于布局阶段,在 layout 的 step3 中将会清空 mAttachedScrap 和 mChangedScrap。

    其实还有一个常见的场景是滑动操作,滑动出屏幕的子 View 将会缓存到 mCachedView,不过这里就不详细说了,在之后会在其他文章专门分析滑动这块。

    后记

    本文围绕 Recycler 展开叙述,重点是要通过它的几个成员变量了解它的缓存机制,四级缓存分别是什么,是在何时调用的,各自起到的作用,不同场景下使用哪种缓存等。

    Recycler 和 LayoutManager 的布局以及动画都有联系,例如 LayoutManager 负责布局,它决定获取子 View 和回收子 View 的时机,具体的工作就交由 Recycler 负责。这些会在之后对 RecyclerView 的其他方面作分析时进行更详细的说明。

    参考

    相关文章

      网友评论

          本文标题:RecyclerView 源码分析(一):Recycler

          本文链接:https://www.haomeiwen.com/subject/xrwydctx.html