本文将简述RecyclerView刷新问题,也是借这篇文章能够对Rv的刷新有一个具体的认知。来探究我们日常出现的闪烁问题究竟是因何而起的。
抽象观察者 => AdapterDataObserver
//RecyclerView.java
public abstract static class AdapterDataObserver {
public AdapterDataObserver() {
}
public void onChanged() {
}
public void onItemRangeChanged(int positionStart, int itemCount) {
}
public void onItemRangeChanged(int positionStart, int itemCount, @Nullable Object payload) {
this.onItemRangeChanged(positionStart, itemCount);
}
public void onItemRangeInserted(int positionStart, int itemCount) {
}
public void onItemRangeRemoved(int positionStart, int itemCount) {
}
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
}
}
具体实现的观察者 RecyclerViewDataObserver
private class RecyclerViewDataObserver extends RecyclerView.AdapterDataObserver {
RecyclerViewDataObserver() {
}
public void onChanged() {
RecyclerView.this.assertNotInLayoutOrScroll((String)null);
RecyclerView.this.mState.mStructureChanged = true;
RecyclerView.this.processDataSetCompletelyChanged(true);
if (!RecyclerView.this.mAdapterHelper.hasPendingUpdates()) {
RecyclerView.this.requestLayout();
}
}
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
RecyclerView.this.assertNotInLayoutOrScroll((String)null);
if (RecyclerView.this.mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) {
this.triggerUpdateProcessor();
}
}
public void onItemRangeInserted(int positionStart, int itemCount) {
RecyclerView.this.assertNotInLayoutOrScroll((String)null);
if (RecyclerView.this.mAdapterHelper.onItemRangeInserted(positionStart, itemCount)) {
this.triggerUpdateProcessor();
}
}
public void onItemRangeRemoved(int positionStart, int itemCount) {
RecyclerView.this.assertNotInLayoutOrScroll((String)null);
if (RecyclerView.this.mAdapterHelper.onItemRangeRemoved(positionStart, itemCount)) {
this.triggerUpdateProcessor();
}
}
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
RecyclerView.this.assertNotInLayoutOrScroll((String)null);
if (RecyclerView.this.mAdapterHelper.onItemRangeMoved(fromPosition, toPosition, itemCount)) {
this.triggerUpdateProcessor();
}
}
void triggerUpdateProcessor() {
if (RecyclerView.POST_UPDATES_ON_ANIMATION && RecyclerView.this.mHasFixedSize && RecyclerView.this.mIsAttached) {
ViewCompat.postOnAnimation(RecyclerView.this, RecyclerView.this.mUpdateChildViewsRunnable);
} else {
RecyclerView.this.mAdapterUpdateDuringMeasure = true;
RecyclerView.this.requestLayout();
}
}
}
观察者mObserver初始化
//RecyclerView.java
private final RecyclerView.RecyclerViewDataObserver mObserver;
public RecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
this.mObserver = new RecyclerView.RecyclerViewDataObserver();
//.......
}
AdapterDataObservable具体实现如下代码所示,会在状态改变的时候通知观察者。
static class AdapterDataObservable extends Observable<RecyclerView.AdapterDataObserver> {
AdapterDataObservable() {
}
public boolean hasObservers() {
return !this.mObservers.isEmpty();
}
public void notifyChanged() {
for(int i = this.mObservers.size() - 1; i >= 0; --i) {
((RecyclerView.AdapterDataObserver)this.mObservers.get(i)).onChanged();
}
}
public void notifyItemRangeChanged(int positionStart, int itemCount) {
this.notifyItemRangeChanged(positionStart, itemCount, (Object)null);
}
public void notifyItemRangeChanged(int positionStart, int itemCount, @Nullable Object payload) {
for(int i = this.mObservers.size() - 1; i >= 0; --i) {
((RecyclerView.AdapterDataObserver)this.mObservers.get(i)).onItemRangeChanged(positionStart, itemCount, payload);
}
}
public void notifyItemRangeInserted(int positionStart, int itemCount) {
for(int i = this.mObservers.size() - 1; i >= 0; --i) {
((RecyclerView.AdapterDataObserver)this.mObservers.get(i)).onItemRangeInserted(positionStart, itemCount);
}
}
public void notifyItemRangeRemoved(int positionStart, int itemCount) {
for(int i = this.mObservers.size() - 1; i >= 0; --i) {
((RecyclerView.AdapterDataObserver)this.mObservers.get(i)).onItemRangeRemoved(positionStart, itemCount);
}
}
public void notifyItemMoved(int fromPosition, int toPosition) {
for(int i = this.mObservers.size() - 1; i >= 0; --i) {
((RecyclerView.AdapterDataObserver)this.mObservers.get(i)).onItemRangeMoved(fromPosition, toPosition, 1);
}
}
}
观察者mObservable是在adapter中进行初始化的,后续我们需要追溯其是在哪里进行注册或者取消注册观察者的。
public abstract static class Adapter<VH extends RecyclerView.ViewHolder> {
private final RecyclerView.AdapterDataObservable mObservable = new RecyclerView.AdapterDataObservable();
private boolean mHasStableIds = false;
//....
public final boolean hasObservers() {
return this.mObservable.hasObservers();
}
// 注册观察者
public void registerAdapterDataObserver(@NonNull RecyclerView.AdapterDataObserver observer) {
this.mObservable.registerObserver(observer);
}
// 取消注册观察者
public void unregisterAdapterDataObserver(@NonNull RecyclerView.AdapterDataObserver observer) {
this.mObservable.unregisterObserver(observer);
}
}
// RecyclerView.java
public void setAdapter(@Nullable RecyclerView.Adapter adapter) {
this.setLayoutFrozen(false);
this.setAdapterInternal(adapter, false, true);
this.processDataSetCompletelyChanged(false);
this.requestLayout();
}
private void setAdapterInternal(@Nullable RecyclerView.Adapter adapter, boolean compatibleWithPrevious, boolean removeAndRecycleViews) {
if (this.mAdapter != null) {
// 取消注册观察者
this.mAdapter.unregisterAdapterDataObserver(this.mObserver);
//
this.mAdapter.onDetachedFromRecyclerView(this);
}
if (!compatibleWithPrevious || removeAndRecycleViews) {
this.removeAndRecycleViews();
}
// 初始化adapterHelper
this.mAdapterHelper.reset();
RecyclerView.Adapter oldAdapter = this.mAdapter;
this.mAdapter = adapter;
if (adapter != null) {
// 注册观察者
adapter.registerAdapterDataObserver(this.mObserver);
adapter.onAttachedToRecyclerView(this);
}
if (this.mLayout != null) {
this.mLayout.onAdapterChanged(oldAdapter, this.mAdapter);
}
this.mRecycler.onAdapterChanged(oldAdapter, this.mAdapter, compatibleWithPrevious);
this.mState.mStructureChanged = true;
}
接下来将分析 notifyDataSetChanged和notifyItemRangeChanged的刷新差异
由于不知道变化有多少,所以会把所有的页面上显示的viwHolder以及mCachedViews都打上无效的标志。不然从正常流程走的缓存由于数据增加和删除会造成拿出的viewHolder错误或过时的。
private class RecyclerViewDataObserver extends RecyclerView.AdapterDataObserver {
// 观察者通知被观察者
public void onChanged() {
RecyclerView.this.assertNotInLayoutOrScroll((String)null);
RecyclerView.this.mState.mStructureChanged = true;
RecyclerView.this.processDataSetCompletelyChanged(true);
if (!RecyclerView.this.mAdapterHelper.hasPendingUpdates()) {
// 重新布局
RecyclerView.this.requestLayout();
}
}
}
// RecyclerView.java
void processDataSetCompletelyChanged(boolean dispatchItemsChanged) {
this.mDispatchItemsChangedEvent |= dispatchItemsChanged;
this.mDataSetHasChangedAfterLayout = true;
this.markKnownViewsInvalid();
}
// mFlags 1 => 7
void markKnownViewsInvalid() {
int childCount = this.mChildHelper.getUnfilteredChildCount();
// for循环当前显示在屏幕上的item,设置其为可忽略的
for(int i = 0; i < childCount; ++i) {
RecyclerView.ViewHolder holder = getChildViewHolderInt(this.mChildHelper.getUnfilteredChildAt(i));
if (holder != null && !holder.shouldIgnore()) {
holder.addFlags(6);
}
}
// 给layoutParams插入dirtys
this.markItemDecorInsetsDirty();
this.mRecycler.markKnownViewsInvalid();
}
// RecyclerView.java/Recycler
void markKnownViewsInvalid() {
// 遍历缓存的view设置标志
int cachedCount = this.mCachedViews.size();
for(int i = 0; i < cachedCount; ++i) {
RecyclerView.ViewHolder holder = (RecyclerView.ViewHolder)this.mCachedViews.get(i);
if (holder != null) {
holder.addFlags(6);
holder.addChangePayload((Object)null);
}
}
// 决定回收和清除view的关键是 是否设置的stableIds
if (RecyclerView.this.mAdapter == null || !RecyclerView.this.mAdapter.hasStableIds()) {
// 将mCacheViews中缓存的viewHolder全部移入mRecyclerPool中
this.recycleAndClearCachedViews();
}
}
如上代码所示,后面会执行刷新布局requestLayout,那么就会执行onMeasure()
onLayout(),那么势必就涉及到viewHolder的复用问题,而在填充fill之前,会在之前
执行 detachAndScrapAttachedViews 方法进行缓存。
onDispatchLayoutStep2 => onLayoutChindren => detachAndScrapAttachedViews(recycler);
// RecyclerView.java
// for循环遍历页面显示的ITEM, 进行scrap或缓存
public void detachAndScrapAttachedViews(@NonNull RecyclerView.Recycler recycler) {
int childCount = this.getChildCount();
for(int i = childCount - 1; i >= 0; --i) {
View v = this.getChildAt(i);
this.scrapOrRecycleView(recycler, i, v);
}
}
private void scrapOrRecycleView(RecyclerView.Recycler recycler, int index, View view) {
RecyclerView.ViewHolder viewHolder = RecyclerView.getChildViewHolderInt(view);
if (!viewHolder.shouldIgnore()) {
// 分支1
if (viewHolder.isInvalid() && !viewHolder.isRemoved() && !this.mRecyclerView.mAdapter.hasStableIds()) {
this.removeViewAt(index);
recycler.recycleViewHolderInternal(viewHolder);
} else {
// 分支2
this.detachViewAt(index);
recycler.scrapView(view);
this.mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
}
}
}
mHasStableIds = true, mFlags = 263,进入分支2,view缓存到mAttachedScraps中。
在来看获取view的过程
RecyclerView.ViewHolder tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs) {
//一级缓存
if (holder == null) {
holder = this.getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
}
//二级缓存
if (holder == null) {
if (RecyclerView.this.mAdapter.hasStableIds()) {
holder = this.getScrapOrCachedViewForId(RecyclerView.this.mAdapter.getItemId(offsetPosition), type, dryRun);
if (holder != null) {
holder.mPosition = offsetPosition;
fromScrapOrHiddenOrCache = true;
}
}
}
// 四级缓存
if (holder == null) {
holder = this.getRecycledViewPool().getRecycledView(type);
if (holder != null) {
holder.resetInternal();
if (RecyclerView.FORCE_INVALIDATE_DISPLAY_LIST) {
this.invalidateDisplayListInt(holder);
}
}
}
}
既然mAttachedScraps中缓存有页面上显示的viewHolder,但getScrapOrHiddenOrCachedHolderForPosition 一级缓存中的scraps返回的是postion一致而且是有效的holder,而我们经过全局刷新的adapter缓存的holder标志是无效的,无法通过一级缓存返回。此时我们就得分析二级缓存了。
RecyclerView.ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {
int scrapCount = this.mAttachedScrap.size();
int cacheSize;
RecyclerView.ViewHolder vh;
for(cacheSize = 0; cacheSize < scrapCount; ++cacheSize) {
vh = (RecyclerView.ViewHolder)this.mAttachedScrap.get(cacheSize);
//虽然能查询到下标一致的viewHolder,但由于该viewHolder无效,无法返回有效的holder
if (!vh.wasReturnedFromScrap() && vh.getLayoutPosition() == position && !vh.isInvalid() && (RecyclerView.this.mState.mInPreLayout || !vh.isRemoved())) {
vh.addFlags(32);
return vh;
}
}
// mCacheViews和mRecyclerPool都不存在页面上显示的ITEM,代码就省略了
}
如果mHasStableIds为ture,进入二级缓存,依据itemId遍历mAttachedScraps获取id相等viewHolder,找到之后会将mFlags 与32进行或运算,那么此时mFlags = 295。
RecyclerView.ViewHolder getScrapOrCachedViewForId(long id, int type, boolean dryRun) {
int count = this.mAttachedScrap.size();
int i;
for(i = count - 1; i >= 0; --i) {
RecyclerView.ViewHolder holder = (RecyclerView.ViewHolder)this.mAttachedScrap.get(i);
if (holder.getItemId() == id && !holder.wasReturnedFromScrap()) {
if (type == holder.getItemViewType()) {
holder.addFlags(32);
if (holder.isRemoved() && !RecyclerView.this.mState.isPreLayout()) {
holder.setFlags(2, 14);
}
return holder;
}
// .........
}
}
return null;
}
在获取holder之后还会对holder是否有效进行校验,来决定是否重新绑定holder。
mFlags = 295,而由于isPreLayout为false且needsUpdate()和isInvalid()返回true,所以此时会执行重新绑定holder的数据
if (RecyclerView.this.mState.isPreLayout() && holder.isBound()) {
holder.mPreLayoutPosition = position;
} else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
// 绑定数据的分支
type = RecyclerView.this.mAdapterHelper.findPositionOffset(position);
bound = this.tryBindViewHolderByDeadline(holder, type, position, deadlineNs);
}
附录上一些标志的变化
boolean shouldIgnore() {
return (this.mFlags & 128) != 0;
}
boolean isInvalid() {
return (this.mFlags & 4) != 0;
}
boolean needsUpdate() {
return (this.mFlags & 2) != 0;
}
boolean isBound() {
return (this.mFlags & 1) != 0;
}
boolean isRemoved() {
return (this.mFlags & 8) != 0;
}
当页面显示的Item同类型且条目数超过5时,
那么如果设置mHasStableIdsS时,则会复用标志为无效的ViewHolder,而且重新执行bindViewHolder重新绑定数据;
否则,前5个只需要执行bindViewHolder,而之后的会先执行createViewHolder再执行bindViewHolder,会出现复用不完整造成的闪烁问题。
这里提一下移除,当移除的是第一个item时,mCoordinate此时会有值,而且第一个有效的vh的下标会是1,那么从上到下绘制时会摆放第一个,而且会中间向上填充第0个,然后发现还有剩余空间会继续填充第二2。preLayout做了如上操作,而具体layout中又会经过如上步骤。