RecyclerView 静态布局完成进程解析:怎样构建高性能的列表

RecyclerView 滑动布局源码分析:带你深化把握列表滑动机制

RecyclerView 缓存深化解析:进步列表性能的关键技术

前面三篇文章从Recyclerview怎样布局讲到了Recycerview滑动时是怎样布局紧接着把Recyclerview是怎样获取ViewHolder,前面一连串的逻辑假如要概括一下,他们都是展现已有的内容解说,那这篇咱们就要讲讲Recyclerview的增修正是怎样完成的.

那这儿首要要讲的api便是notifyItemInserted(int)notifyItemRangeInserted(int, int)

Adapter.notifyItemInserted(int position)

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

这儿的代码很少,从命名能够看出来这儿采用的是观察者形式,mObservable便是那个发布者,发布者内部必定有一个List来保存一切监听者的引用.mObservable.notifyItemRangeInserted(position, 1)内部必定是遍历监听者.
mObservableAdapterDataObservable类型,那咱们来看看他的代码:

static class AdapterDataObservable extends Observable<AdapterDataObserver> {
    public boolean hasObservers() {
        return !mObservers.isEmpty();
    }
    public void notifyChanged() {
        for (int i = mObservers.size() - 1; i >= 0; i--) {
            mObservers.get(i).onChanged();
        }
    }
    public void notifyStateRestorationPolicyChanged() {
        for (int i = mObservers.size() - 1; i >= 0; i--) {
            mObservers.get(i).onStateRestorationPolicyChanged();
        }
    }
    public void notifyItemRangeChanged(int positionStart, int itemCount) {
        notifyItemRangeChanged(positionStart, itemCount, null);
    }
    public void notifyItemRangeChanged(int positionStart, int itemCount,
            @Nullable Object payload) {
        for (int i = mObservers.size() - 1; i >= 0; i--) {
            mObservers.get(i).onItemRangeChanged(positionStart, itemCount, payload);
        }
    }
    public void notifyItemRangeInserted(int positionStart, int itemCount) {
        for (int i = mObservers.size() - 1; i >= 0; i--) {
            mObservers.get(i).onItemRangeInserted(positionStart, itemCount);
        }
    }
    public void notifyItemRangeRemoved(int positionStart, int itemCount) {
        for (int i = mObservers.size() - 1; i >= 0; i--) {
            mObservers.get(i).onItemRangeRemoved(positionStart, itemCount);
        }
    }
    public void notifyItemMoved(int fromPosition, int toPosition) {
        for (int i = mObservers.size() - 1; i >= 0; i--) {
            mObservers.get(i).onItemRangeMoved(fromPosition, toPosition, 1);
        }
    }
}

这段代码中的mObservers来自父类,他便是一个List里面保存了一切的监听者,
然后能看到这段代码中的每个函数都是遍历监听者并调用其相关的函数.那咱们来看下这些监听者都是怎样被增加的,也便是看看哪里调用了mObservers.add()


Observable.java
public void registerObserver(T observer) {
    if (observer == null) {
        throw new IllegalArgumentException("The observer is null.");
    }
    synchronized(mObservers) {
        if (mObservers.contains(observer)) {
            throw new IllegalStateException("Observer " + observer + " is already registered.");
        }
        mObservers.add(observer);
    }
}

从姓名上来看便是一个增加监听者的函数,再看看哪里调用了这个

Recyclerview.Adapter.java
public void registerAdapterDataObserver(@NonNull AdapterDataObserver observer) {
    mObservable.registerObserver(observer);
}

由于数据改变是经过Adapter来履行的,而且也是Adapter也是唯一持有数据源的类,那他担任增加和删除监听者也很合理,现在就看看哪里调用了registerAdapterDataObserver()

/**
 * Replaces the current adapter with the new one and triggers listeners.
 *
 * @param adapter                The new adapter
 * @param compatibleWithPrevious If true, the new adapter is using the same View Holders and
 *                               item types with the current adapter (helps us avoid cache
 *                               invalidation).
 * @param removeAndRecycleViews  If true, we'll remove and recycle all existing views. If
 *                               compatibleWithPrevious is false, this parameter is ignored.
 */
private void setAdapterInternal(@Nullable Adapter<?> adapter, boolean compatibleWithPrevious,
        boolean removeAndRecycleViews) {
    //① 假如当时的RV已经有Adapter,那就把当时RV的监听者与之前adapter进行解绑
    if (mAdapter != null) {
        mAdapter.unregisterAdapterDataObserver(mObserver);
        mAdapter.onDetachedFromRecyclerView(this);
    }
    if (!compatibleWithPrevious || removeAndRecycleViews) {
        removeAndRecycleViews();
    }
    mAdapterHelper.reset();
    final Adapter<?> oldAdapter = mAdapter;
    mAdapter = adapter;
    if (adapter != null) {
        //②将当时RV的的监听者与最新的adapter进行绑定
        adapter.registerAdapterDataObserver(mObserver);
        adapter.onAttachedToRecyclerView(this);
    }
    if (mLayout != null) {
        mLayout.onAdapterChanged(oldAdapter, mAdapter);
    }
    mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious);
    mState.mStructureChanged = true;
}

从代码的官方注释能够看到这个函数的作用替换当时RecyclerviewAdapter,现在发布者Adapter,那谁是监听者呢?也便是谁需求响应Adapter数据的改变,那必定是Recyclerview了,在②处能看到将mObserver与最新的Adapter进行了绑定,而mObserver正是Reyclerview的成员属性.也正是阐明监听者是与Recyclerview绑定的,发布者Adapter绑定的,那现在咱们发布者改变了,对应的监听者必定需求与不用的发布者进行解绑,然后再和最新的发布者进行绑定,上面代码的①和②分别对应解绑和绑定逻辑.不出意外便是在Recylerview.setadapter()的时分会调用当时函数,这儿我就不粘贴代码占用版位了.

现在咱们知道发布者监听者分别是谁,而且知道监听者什么时分订阅发布者的,那现在咱们就能够安安心心看监听者在收到订阅后是怎样做出反响的.

Recyclerview.RecyclerViewDataObserver.java
private class RecyclerViewDataObserver extends AdapterDataObserver {
    RecyclerViewDataObserver() {
    }
    @Override
    public void onChanged() {
        assertNotInLayoutOrScroll(null);
        mState.mStructureChanged = true;
        processDataSetCompletelyChanged(true);
        if (!mAdapterHelper.hasPendingUpdates()) {
            requestLayout();
        }
    }
    @Override
    public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
        assertNotInLayoutOrScroll(null);
        if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) {
            triggerUpdateProcessor();
        }
    }
    @Override
    public void onItemRangeInserted(int positionStart, int itemCount) {
        assertNotInLayoutOrScroll(null);
        if (mAdapterHelper.onItemRangeInserted(positionStart, itemCount)) {
            triggerUpdateProcessor();
        }
    }
    @Override
    public void onItemRangeRemoved(int positionStart, int itemCount) {
        assertNotInLayoutOrScroll(null);
        if (mAdapterHelper.onItemRangeRemoved(positionStart, itemCount)) {
            triggerUpdateProcessor();
        }
    }
    @Override
    public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
        assertNotInLayoutOrScroll(null);
        if (mAdapterHelper.onItemRangeMoved(fromPosition, toPosition, itemCount)) {
            triggerUpdateProcessor();
        }
    }
    void triggerUpdateProcessor() {
        if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
            ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
        } else {
            mAdapterUpdateDuringMeasure = true;
            requestLayout();
        }
    }
    @Override
    public void onStateRestorationPolicyChanged() {
        if (mPendingSavedState == null) {
            return;
        }
        // If there is a pending saved state and the new mode requires us to restore it,
        // we'll request a layout which will call the adapter to see if it can restore state
        // and trigger state restoration
        Adapter<?> adapter = mAdapter;
        if (adapter != null && adapter.canRestoreState()) {
            requestLayout();
        }
    }
}

从函数命名能够清楚看出每个函数对应Adapter的增修正的函数,这儿咱们以增为比如:

@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
    assertNotInLayoutOrScroll(null);
    if (mAdapterHelper.onItemRangeInserted(positionStart, itemCount)) {
        triggerUpdateProcessor();
    }
}
boolean onItemRangeInserted(int positionStart, int itemCount) {
    if (itemCount < 1) {
        return false;
    }
    mPendingUpdates.add(obtainUpdateOp(UpdateOp.ADD, positionStart, itemCount, null));
    mExistingUpdateTypes |= UpdateOp.ADD;
    return mPendingUpdates.size() == 1;
}
void triggerUpdateProcessor() {
    if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
        ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
    } else {
        mAdapterUpdateDuringMeasure = true;
        requestLayout();
    }
}

从代码中能够看到onItemRangeInserted()只做了两件事,调用了mAdapterHelper.onItemRangeInserted()triggerUpdateProcessor(),
onItemRangeInserted()内的逻辑是:

  1. 将增修正中的每个一个操作封装成一个UpdateOp
  2. UpdateOp保存下来

triggerUpdateProcessor()从函数名上能看出来这儿便是触发更新的地方了,这儿有两个分支分别是

  1. 经过ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);履行逻辑
  2. 经过requestLayout()履行逻辑.

分支1 有三个条件POST_UPDATES_ON_ANIMATIONmIsAttachedmHasFixedSize,前面两个能够作为默认值是true,mHasFixedSize是开发者经过api来设置的,而这个值的作用便是标记当时的RV的巨细是否与children无关,是固定巨细.所以假如是固定巨细,那这次增修正child,RV自身巨细不会改变,反之分支2中是经过requestLayout()来履行逻辑,那他必定会触发测量逻辑,所以分支1的性能比分支2要强.

ViewCompat.postOnAnimation()完成

这儿仅仅对View.post()一种兼容,所以咱们只需求重视他终究一个参数Runnable

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 (mLayoutSuppressed) {
            mLayoutWasDefered = true;
            return; //we'll process updates when ice age ends.
        }
        consumePendingUpdateOperations();
    }
};

从这儿能看到逻辑都在consumePendingUpdateOperations()

void consumePendingUpdateOperations() {
    if (!mFirstLayoutComplete || mDataSetHasChangedAfterLayout) {
        TraceCompat.beginSection(TRACE_ON_DATA_SET_CHANGE_LAYOUT_TAG);
        dispatchLayout();
        TraceCompat.endSection();
        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判别是先判别当时是否是update操作,而且不能是add,remove,move,else if 的判别是操作行列
    //中是否还有未履行的UpdateOp, UpdateOp便是将增修正每个操作封装后的数据结构
    if (mAdapterHelper.hasAnyUpdateTypes(AdapterHelper.UpdateOp.UPDATE) && !mAdapterHelper
            .hasAnyUpdateTypes(AdapterHelper.UpdateOp.ADD | AdapterHelper.UpdateOp.REMOVE
                    | AdapterHelper.UpdateOp.MOVE)) {
        ...
    } else if (mAdapterHelper.hasPendingUpdates()) {
        TraceCompat.beginSection(TRACE_ON_DATA_SET_CHANGE_LAYOUT_TAG);
        dispatchLayout();
        TraceCompat.endSection();
    }
}

代码中增加了注释,由于咱们这儿是解说所以会进入else if逻辑,而else if逻辑是履行dispatchLayout()这儿就没有和measure相关的流程了.

void dispatchLayout() {
    if (mAdapter == null) {
        Log.w(TAG, "No adapter attached; skipping layout");
        // leave the state in START
        return;
    }
    if (mLayout == null) {
        Log.e(TAG, "No layout manager attached; skipping layout");
        // leave the state in START
        return;
    }
    mState.mIsMeasuring = false;
    // If the last time we measured children in onMeasure, we skipped the measurement and layout
    // of RV children because the MeasureSpec in both dimensions was EXACTLY, and current
    // dimensions of the RV are not equal to the last measured dimensions of RV, we need to
    // measure and layout children one last time.
    boolean needsRemeasureDueToExactSkip = mLastAutoMeasureSkippedDueToExact
                    && (mLastAutoMeasureNonExactMeasuredWidth != getWidth()
                    || mLastAutoMeasureNonExactMeasuredHeight != getHeight());
    mLastAutoMeasureNonExactMeasuredWidth = 0;
    mLastAutoMeasureNonExactMeasuredHeight = 0;
    mLastAutoMeasureSkippedDueToExact = false;
    if (mState.mLayoutStep == State.STEP_START) {
        dispatchLayoutStep1();
        mLayout.setExactMeasureSpecsFrom(this);
        dispatchLayoutStep2();
    } else if (mAdapterHelper.hasUpdates()
            || needsRemeasureDueToExactSkip
            || mLayout.getWidth() != getWidth()
            || mLayout.getHeight() != getHeight()) {
        // First 2 steps are done in onMeasure but looks like we have to run again due to
        // changed size.
        // TODO(shepshapard): Worth a note that I believe
        //  "mLayout.getWidth() != getWidth() || mLayout.getHeight() != getHeight()" above is
        //  not actually correct, causes unnecessary work to be done, and should be
        //  removed. Removing causes many tests to fail and I didn't have the time to
        //  investigate. Just a note for the a future reader or bug fixer.
        mLayout.setExactMeasureSpecsFrom(this);
        dispatchLayoutStep2();
    } else {
        // always make sure we sync them (to ensure mode is exact)
        mLayout.setExactMeasureSpecsFrom(this);
    }
    dispatchLayoutStep3();
}

能够看到dispatchLayout() 内部便是判别要履行dispatchLayoutStep1()dispatchLayoutStep2()dispatchLayoutStep3()三个函数中的哪些函数.

  • dispatchLayoutStep1()是为了播映动画做铺垫,记录每个child履行增修正动画前的布局信息.
  • dispatchLayoutStep2()首要任务便是按终究的样式来layout
  • dispatchLayoutStep3()便是履行增修正动画了.

那咱们就需求重点重视一下dispatchLayoutStep2()的逻辑了.

private void dispatchLayoutStep2() {
    startInterceptRequestLayout();
    onEnterLayoutOrScroll();
    mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS);
    //①
    mAdapterHelper.consumeUpdatesInOnePass();
    mState.mItemCount = mAdapter.getItemCount();
    mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;
 ...
}

整个dispatchLayoutStep2()一切的逻辑都和前面三章所介绍的填充逻辑一下,可是上面标示①的地方从姓名也能看出来是与增修正有关的,咱们的增修正封装对应的数据结构是mAdapterHelper担任的,而且函数名也带有Update,所以这个函数便是咱们的重视重点.

/**
 * Skips pre-processing and applies all updates in one pass.
 */
void consumeUpdatesInOnePass() {
    // we still consume postponed updates (if there is) in case there was a pre-process call
    // w/o a matching consumePostponedUpdates.
    consumePostponedUpdates();
    final int count = mPendingUpdates.size();
    for (int i = 0; i < count; i++) {
        UpdateOp op = mPendingUpdates.get(i);
        switch (op.cmd) {
            case UpdateOp.ADD:
                mCallback.onDispatchSecondPass(op);
                mCallback.offsetPositionsForAdd(op.positionStart, op.itemCount);
                break;
            case UpdateOp.REMOVE:
                mCallback.onDispatchSecondPass(op);
                mCallback.offsetPositionsForRemovingInvisible(op.positionStart, op.itemCount);
                break;
            case UpdateOp.UPDATE:
                mCallback.onDispatchSecondPass(op);
                mCallback.markViewHoldersUpdated(op.positionStart, op.itemCount, op.payload);
                break;
            case UpdateOp.MOVE:
                mCallback.onDispatchSecondPass(op);
                mCallback.offsetPositionsForMove(op.positionStart, op.itemCount);
                break;
        }
        if (mOnItemProcessedCallback != null) {
            mOnItemProcessedCallback.run();
        }
    }
    recycleUpdateOpsAndClearList(mPendingUpdates);
    mExistingUpdateTypes = 0;
}

从官方注释中能够看出来这儿便是一次性处理一切更新的地方,从这串代码能够看出,写法很简单,首要需求重视三个方面:

  1. mCallback 是什么?
  2. onDispatchSecondPass()这个函数在每个操作中都履行了,是什么滴干活?
  3. 每个操作对应的特有函数offsetPositionsForAdd()

mCallback是什么?

盯梢一下mCallback赋值地方发现只要一处

AdapterHelper(Callback callback, boolean disableRecycler) {
    mCallback = callback;
    mDisableRecycler = disableRecycler;
    mOpReorderer = new OpReorderer(this);
}

AdapterHelper必定是一个Recyclerview对应一个,所以也只要一处实例化AdapterHelper的代码

void initAdapterManager() {
    mAdapterHelper = new AdapterHelper(new AdapterHelper.Callback() {
        ...
        @Override
        public void onDispatchSecondPass(AdapterHelper.UpdateOp op) {
            dispatchUpdate(op);
        }
        @Override
        public void offsetPositionsForAdd(int positionStart, int itemCount) {
            offsetPositionRecordsForInsert(positionStart, itemCount);
            mItemsAddedOrRemoved = true;
        }
        ...
    });
}
void dispatchUpdate(AdapterHelper.UpdateOp op) {
    switch (op.cmd) {
        case AdapterHelper.UpdateOp.ADD:
            mLayout.onItemsAdded(RecyclerView.this, op.positionStart, op.itemCount);
            break;
        case AdapterHelper.UpdateOp.REMOVE:
            mLayout.onItemsRemoved(RecyclerView.this, op.positionStart, op.itemCount);
            break;
        case AdapterHelper.UpdateOp.UPDATE:
            mLayout.onItemsUpdated(RecyclerView.this, op.positionStart, op.itemCount,
                    op.payload);
            break;
        case AdapterHelper.UpdateOp.MOVE:
            mLayout.onItemsMoved(RecyclerView.this, op.positionStart, op.itemCount, 1);
            break;
    }
}
LinearLayoutManager.java
public void onItemsAdded(@NonNull RecyclerView recyclerView, int positionStart,
        int itemCount) {
}

能够看到在mCallbackRecyclerviewAdapterHelper的桥梁,AdapterHleper又是Adapter的一个工具类,那也就意味着mCallbackRecyclerviewAdapter之间的桥梁.当Adapter经过api进行增修正的时分直接经过mCallback影响Recyclerview.

onDispatchSecondPass()作用

从上面的代码能够看出在LinearLayoutManageronDispatchSecondPass()经过一连串的调用后终究调用的函数是空完成,可是在GridLayoutManager是有相关完成的,可是内部都是状况整理的相关逻辑.

offsetPositionsForAdd()

public void offsetPositionsForAdd(int positionStart, int itemCount) {
    offsetPositionRecordsForInsert(positionStart, itemCount);
    mItemsAddedOrRemoved = true;
}
void offsetPositionRecordsForInsert(int positionStart, int itemCount) {
    final int childCount = mChildHelper.getUnfilteredChildCount();
    for (int i = 0; i < childCount; i++) {
        final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
        if (holder != null && !holder.shouldIgnore() && holder.mPosition >= positionStart) {
            if (sVerboseLoggingEnabled) {
                Log.d(TAG, "offsetPositionRecordsForInsert attached child " + i + " holder "
                        + holder + " now at position " + (holder.mPosition + itemCount));
            }
            holder.offsetPosition(itemCount, false);
            mState.mStructureChanged = true;
        }
    }
    mRecycler.offsetPositionRecordsForInsert(positionStart, itemCount);
    requestLayout();
}

能够看出总共就两个逻辑:

  1. 将已经填充的ViewHolder对应position进行更新
  2. 将已经在缓存池中的ViweHolder对应的position进行更新

这个逻辑很正常吧,你增加了一个child那本来的childposition必定都会改变,咱们缓存大多都是position进行匹对的,不能说你增加一个child咱们现有的缓存由于不更新position导致匹配失利而当值缓存全部失利.

当他更新完每个ViewHolder后调用requestLayout()进行布局,然后又是一连串的fill()流程,便是第一篇和第二篇的逻辑了.可是有一个很致命的问题不知道大家意识到没有,一切的增修正逻辑仅仅修正了Recyclerview的子View,咱们的数据源并没有被修正.所以这也是为什么咱们在经过Adapter进行增修正api调用的时分还需求自己保护数据源增修正.假如咱们咱们对Recyclerview的子View进行视图的增修正,数据源不修正的话,在调用requestlayout()他又会依据数据源进行填充,终究不会有实践的修正作用.

删除的逻辑同上面add逻辑差不多有80%的逻辑是相同的,要经过对remote操作封装成UpdateOp然后调用dispatchLayout(),再然后履行dispatchLayoutStep1,dispatchLayoutStep2等逻辑,在dispatchLayoutStep2中经过mCallback履行remove相关逻辑.

public void offsetPositionsForRemovingInvisible(int start, int count) {
    offsetPositionRecordsForRemove(start, count, true);
    mItemsAddedOrRemoved = true;
    mState.mDeletedInvisibleItemCountSincePreviousLayout += count;
}
void offsetPositionRecordsForRemove(int positionStart, int itemCount,
        boolean applyToPreLayout) {
    final int positionEnd = positionStart + itemCount;
    final int childCount = mChildHelper.getUnfilteredChildCount();
    for (int i = 0; i < childCount; i++) {
        final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
        if (holder != null && !holder.shouldIgnore()) {
            if (holder.mPosition >= positionEnd) {
                if (sVerboseLoggingEnabled) {
                    Log.d(TAG, "offsetPositionRecordsForRemove attached child " + i
                            + " holder " + holder + " now at position "
                            + (holder.mPosition - itemCount));
                }
                holder.offsetPosition(-itemCount, applyToPreLayout);
                mState.mStructureChanged = true;
            } else if (holder.mPosition >= positionStart) {
                if (sVerboseLoggingEnabled) {
                    Log.d(TAG, "offsetPositionRecordsForRemove attached child " + i
                            + " holder " + holder + " now REMOVED");
                }
                holder.flagRemovedAndOffsetPosition(positionStart - 1, -itemCount,
                        applyToPreLayout);
                mState.mStructureChanged = true;
            }
        }
    }
    mRecycler.offsetPositionRecordsForRemove(positionStart, itemCount, applyToPreLayout);
    requestLayout();
}

remove操作同add操作,更新已经填充的ViewHolder对应的position然后更新缓存池中的ViewHolder对应的position

操作就和操作没什么不同的,大家能够自己测验盯梢一下代码