美文网首页
RecyclerView 源码深入解析(二)

RecyclerView 源码深入解析(二)

作者: StevenChou | 来源:发表于2018-11-10 15:56 被阅读25次

文章太长,无法发布,不得已分成了两部分,这是第二篇,上一篇可以查看链接:RecyclerView 源码深入解析——绘制流程、缓存机制、动画等

滑动加载更多数据

经过上一篇的分析,可以基本肯定 RecyclerView 在滑动加载更多数据时,会调用 fill 方法使用 mCachedViews 进行子 View 的缓存和复用,下面来验证一下:

public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {

    @Override
    public boolean onTouchEvent(MotionEvent e) {
        ...
        switch (action) {
            ...
            case MotionEvent.ACTION_MOVE: {
                ...
                if (mScrollState == SCROLL_STATE_DRAGGING) {
                    mLastTouchX = x - mScrollOffset[0];
                    mLastTouchY = y - mScrollOffset[1];

                    if (scrollByInternal(
                            canScrollHorizontally ? dx : 0,
                            canScrollVertically ? dy : 0,
                            vtev)) {
                        getParent().requestDisallowInterceptTouchEvent(true);
                    }
                    if (mGapWorker != null && (dx != 0 || dy != 0)) {
                        mGapWorker.postFromTraversal(this, dx, dy);
                    }
                }
            } break;
            ...
        }

        return true;
    }
    
    boolean scrollByInternal(int x, int y, MotionEvent ev) {
        int unconsumedX = 0, unconsumedY = 0;
        int consumedX = 0, consumedY = 0;

        consumePendingUpdateOperations();
        if (mAdapter != null) {
            ...
            // 滚动的事件处理由 LayoutManager 完成
            if (x != 0) {
                consumedX = mLayout.scrollHorizontallyBy(x, mRecycler, mState);
                unconsumedX = x - consumedX;
            }
            if (y != 0) {
                consumedY = mLayout.scrollVerticallyBy(y, mRecycler, mState);
                unconsumedY = y - consumedY;
            }
        }
        
        if (!mItemDecorations.isEmpty()) {
            invalidate();
        }

        if (dispatchNestedScroll(consumedX, consumedY, unconsumedX, unconsumedY, mScrollOffset,
                TYPE_TOUCH)) {
            ...
        } else if (getOverScrollMode() != View.OVER_SCROLL_NEVER) {
            ...
        }
        return consumedX != 0 || consumedY != 0;
    }
    
    // 如果 Adapter 更新了,则重新走一遍 layout 流程
    void consumePendingUpdateOperations() {
        if (!mFirstLayoutComplete || mDataSetHasChangedAfterLayout) {
            dispatchLayout();
            return;
        }
        if (!mAdapterHelper.hasPendingUpdates()) {
            return;
        }

        // if it is only an item change (no add-remove-notifyDataSetChanged) we can check if any
        // of the visible items is affected and if not, just ignore the change.
        if (mAdapterHelper.hasAnyUpdateTypes(AdapterHelper.UpdateOp.UPDATE) && !mAdapterHelper
                .hasAnyUpdateTypes(AdapterHelper.UpdateOp.ADD | AdapterHelper.UpdateOp.REMOVE
                        | AdapterHelper.UpdateOp.MOVE)) {
            if (!mLayoutWasDefered) {
                if (hasUpdatedView()) {
                    dispatchLayout();
                } else {
                    // no need to layout, clean state
                    mAdapterHelper.consumePostponedUpdates();
                }
            }
        } else if (mAdapterHelper.hasPendingUpdates()) {
            dispatchLayout();
        }
    }
    
}
public class LinearLayoutManager extends RecyclerView.LayoutManager implements
        ItemTouchHelper.ViewDropHandler, RecyclerView.SmoothScroller.ScrollVectorProvider {

    @Override
    public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
            RecyclerView.State state) {
        if (mOrientation == VERTICAL) {
            return 0;
        }
        return scrollBy(dx, recycler, state);
    }

    @Override
    public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
            RecyclerView.State state) {
        if (mOrientation == HORIZONTAL) {
            return 0;
        }
        return scrollBy(dy, recycler, state);
    }
    
    int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
        if (getChildCount() == 0 || dy == 0) {
            return 0;
        }
        mLayoutState.mRecycle = true;
        ...
        // fill 在上面分析过,即循环获取子 View 直到屏幕填充完毕,或子 View 消耗完毕
        final int consumed = mLayoutState.mScrollingOffset
                + fill(recycler, mLayoutState, state, false);
        ...
        return scrolled;
    }
    
}

可以看到,就像上面的推测的那样,RecyclerView 在滑动过程中会调用 fill 方法,使用 mCachedViews 进行子 View 的缓存和复用,如果 Adapter 更新了,则会重新走了一遍 layout 的流程。

Adapter

上面可以说已经把 RecyclerView最关键的部分都分析完成了,但还有一些问题是没有解决的,即 RecyclerView 是如何得知 Adapter 更新的?下面开始分析这个问题。

从 RecyclerView 的 setAdapter 方法开始看起:

public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {

    private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver();
    Adapter mAdapter;
    AdapterHelper mAdapterHelper;

    public void setAdapter(Adapter adapter) {
        // bail out if layout is frozen
        setLayoutFrozen(false);
        setAdapterInternal(adapter, false, true);
        processDataSetCompletelyChanged(false);
        requestLayout();
    }
    
    private void setAdapterInternal(Adapter adapter, boolean compatibleWithPrevious,
            boolean removeAndRecycleViews) {
        if (mAdapter != null) {
            mAdapter.unregisterAdapterDataObserver(mObserver);
            mAdapter.onDetachedFromRecyclerView(this);
        }
        ...
        mAdapter = adapter;
        if (adapter != null) {
            adapter.registerAdapterDataObserver(mObserver);
            adapter.onAttachedToRecyclerView(this);
        }
        ...
    }

    void processDataSetCompletelyChanged(boolean dispatchItemsChanged) {
        mDispatchItemsChangedEvent |= dispatchItemsChanged;
        // 标记 DataSet 改变了,之后会调用 dispatchLayout 走一遍 layout 流程
        mDataSetHasChangedAfterLayout = true;
        markKnownViewsInvalid();
    }

    public abstract static class Adapter<VH extends ViewHolder> {
        // 使用了观察者模式
        private final AdapterDataObservable mObservable = new AdapterDataObservable();

        @NonNull
        public abstract VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType);

        public abstract void onBindViewHolder(@NonNull VH holder, int position);
        
        ...

        // 注册观察者
        public void registerAdapterDataObserver(@NonNull AdapterDataObserver observer) {
            mObservable.registerObserver(observer);
        }

        public void unregisterAdapterDataObserver(@NonNull AdapterDataObserver observer) {
            mObservable.unregisterObserver(observer);
        }

        // 通知观察者,数据发生了改变
        public final void notifyDataSetChanged() {
            mObservable.notifyChanged();
        }

        public final void notifyItemInserted(int position) {
            mObservable.notifyItemRangeInserted(position, 1);
        }

        ...
        
    }
    
    static class AdapterDataObservable extends Observable<AdapterDataObserver> {

        // 通知所有观察者
        public void notifyChanged() {
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onChanged();
            }
        }

        public void notifyItemRangeInserted(int positionStart, int itemCount) {
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onItemRangeInserted(positionStart, itemCount);
            }
        }

        ...
    }
    
    private class RecyclerViewDataObserver extends AdapterDataObserver {
        RecyclerViewDataObserver() {
        }

        @Override
        public void onChanged() {
            processDataSetCompletelyChanged(true);
            if (!mAdapterHelper.hasPendingUpdates()) {
                requestLayout();
            }
        }

        @Override
        public void onItemRangeInserted(int positionStart, int itemCount) {
            if (mAdapterHelper.onItemRangeInserted(positionStart, itemCount)) {
                triggerUpdateProcessor();
            }
        }
        
        void triggerUpdateProcessor() {
            // 如果设置了 mHasFixedSize,那么可能会省略一些工作
            if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
                ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
            } else {
                // 否则需要重新走一遍 View 的三大流程
                mAdapterUpdateDuringMeasure = true;
                requestLayout();
            }
        }

    }
    
    final Runnable mUpdateChildViewsRunnable = new Runnable() {
        @Override
        public void run() {
            if (!mFirstLayoutComplete || isLayoutRequested()) {
                // a layout request will happen, we should not do layout here.
                return;
            }
            if (!mIsAttached) {
                requestLayout();
                // if we are not attached yet, mark us as requiring layout and skip
                return;
            }
            if (mLayoutFrozen) {
                mLayoutWasDefered = true;
                return; //we'll process updates when ice age ends.
            }
            consumePendingUpdateOperations();
        }
    };
    
    void consumePendingUpdateOperations() {
        // 重新执行一遍 dispatchLayoutStep1、dispatchLayoutStep2、dispatchLayoutStep3
        if (!mFirstLayoutComplete || mDataSetHasChangedAfterLayout) {
            dispatchLayout();
            return;
        }
        if (!mAdapterHelper.hasPendingUpdates()) {
            return;
        }

        // if it is only an item change (no add-remove-notifyDataSetChanged) we can check if any
        // of the visible items is affected and if not, just ignore the change.
        if (mAdapterHelper.hasAnyUpdateTypes(AdapterHelper.UpdateOp.UPDATE) && !mAdapterHelper
                .hasAnyUpdateTypes(AdapterHelper.UpdateOp.ADD | AdapterHelper.UpdateOp.REMOVE
                        | AdapterHelper.UpdateOp.MOVE)) { // 局部更新
            // 最终调用了 AdapterHelper 的 Callback 方法,Callback 的实现可以查看 RecyclerView 的 initAdapterManager 方法
            mAdapterHelper.preProcess();
            if (!mLayoutWasDefered) {
                if (hasUpdatedView()) {
                    dispatchLayout();
                } else {
                    // no need to layout, clean state
                    mAdapterHelper.consumePostponedUpdates();
                }
            }
        } else if (mAdapterHelper.hasPendingUpdates()) {
            dispatchLayout();
        }
    }

}

根据上面的分析,可以知道:

  1. RecyclerView 和 Adapter 之间是观察者、被观察者的关系,当 Adapter 调用了 notifyDataSetChanged 等方法时,RecyclerView 能够得知这个变化,并执行 layout 等相应的行为
  2. 如果 ReccylerView 设置了 mHasFixedSize,那么可以在 Adapter 被修改时执行 RecyclerView 的局部更新,从而避免重新走一遍三大流程,提高效率

总结

RecyclerView 的 measure 和 layout 分为三步:

  1. dispatchLayoutStep1,负责 Adpater 更新、计算并保存子 View 和动画的相关信息、预布局(如果有必要)
  2. dispatchLayoutStep2,负责实际上的布局,具体工作是交给 LayoutManager 完成的,但基本流程都一样,都有相同的子 View 复用机制
  3. dispatchLayoutStep3,负责执行动画及清理工作,默认的动画执行者是 DefaultItemAnimator

对比 RecyclerView 和 ListView:

  1. 同样在第一次 layout 时回调 Adapter 的相关方法从 xml 资源中加载子 View
  2. 同样会在第二第三次 layout 的先 remove / detach 所有的子 View,最后再 add / attach 回来,这是为了避免重复加载相同的子 View
  3. 同样只加载一个手机屏幕能显示的子 View
  4. 同样使用多个缓存数组/列表,且不同的缓存数组/列表对应的功能不同,RecylerView 的 mAttachedScrap 对应 ListView 的 mActiveViews,mCahcedViews 对应 mScrapViews
  5. 同样使用了观察者模式,在 Adapter 调用了 notifyDataSetChanged 等方法时,能够做出相应的改变

区别在于:

  1. 除了 attach / detach 之外,RecyclerView 还有可能会 remove 子 View 再 add 回来,ListView 只会 detach 再 attach
  2. RecyclerView 的子 View 缓存列表分工更细,共有 5 级缓存,即 mChangedScrap、mAttachedScrap、mCahcedViews、mViewCacheExtension、RecycledViewPool,其中 mViewCacheExtension 用于提供给用户自定义缓存逻辑,而 RecycledViewPool 甚至可以提供给多个 RecyclerView 共用;ListView 则只分为 mActiveViews 和 mScrapViews 两个级别
  3. RecyclerView 的缓存单位是 ViewHolder,ListView 的缓存单位是 View,ListView 需要配合 setTag 方法才能实现复用

相关文章

网友评论

      本文标题:RecyclerView 源码深入解析(二)

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