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)
内部必定是遍历监听者
.
mObservable
是AdapterDataObservable
类型,那咱们来看看他的代码:
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;
}
从代码的官方注释能够看到这个函数的作用替换当时Recyclerview
的Adapter
,现在发布者
是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()
内的逻辑是:
- 将增修正中的每个一个操作封装成一个
UpdateOp
- 将
UpdateOp
保存下来
triggerUpdateProcessor()
从函数名上能看出来这儿便是触发更新的地方了,这儿有两个分支分别是
- 经过
ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
履行逻辑 - 经过
requestLayout()
履行逻辑.
分支1 有三个条件POST_UPDATES_ON_ANIMATION
、mIsAttached
和mHasFixedSize
,前面两个能够作为默认值是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;
}
从官方注释中能够看出来这儿便是一次性处理一切更新的地方,从这串代码能够看出,写法很简单,首要需求重视三个方面:
-
mCallback
是什么? -
onDispatchSecondPass()
这个函数在每个操作中都履行了,是什么滴干活? - 每个操作对应的特有函数
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) {
}
能够看到在mCallback
是Recyclerview
和AdapterHelper
的桥梁,AdapterHleper
又是Adapter
的一个工具类,那也就意味着mCallback
是Recyclerview
和Adapter
之间的桥梁.当Adapter
经过api
进行增修正
的时分直接经过mCallback
影响Recyclerview
.
onDispatchSecondPass()作用
从上面的代码能够看出在LinearLayoutManager
中onDispatchSecondPass()
经过一连串的调用后终究调用的函数是空完成,可是在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();
}
能够看出总共就两个逻辑:
- 将已经填充的
ViewHolder
对应position
进行更新 - 将已经在缓存池中的
ViweHolder
对应的position
进行更新
这个逻辑很正常吧,你增加了一个child
那本来的child
的position
必定都会改变,咱们缓存大多都是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
改
改
操作就和增
、删
操作没什么不同的,大家能够自己测验盯梢一下代码