RecyclerView刷新机制

1、简介

本文将简述RecyclerView刷新问题,也是借这篇文章能够对Rv的刷新有一个具体的认知。来探究我们日常出现的闪烁问题究竟是因何而起的。

2、分析

观察者

抽象观察者 => 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;
    }

3、刷新差异

接下来将分析 notifyDataSetChanged和notifyItemRangeChanged的刷新差异

notifyDataSetChanged

由于不知道变化有多少,所以会把所有的页面上显示的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;
 }
是否设置mHasStableIds复用差异总结

当页面显示的Item同类型且条目数超过5时,
那么如果设置mHasStableIdsS时,则会复用标志为无效的ViewHolder,而且重新执行bindViewHolder重新绑定数据;
否则,前5个只需要执行bindViewHolder,而之后的会先执行createViewHolder再执行bindViewHolder,会出现复用不完整造成的闪烁问题。
RecyclerView刷新机制_第1张图片

notifyItemChanged 总结

RecyclerView刷新机制_第2张图片
这里提一下移除,当移除的是第一个item时,mCoordinate此时会有值,而且第一个有效的vh的下标会是1,那么从上到下绘制时会摆放第一个,而且会中间向上填充第0个,然后发现还有剩余空间会继续填充第二2。preLayout做了如上操作,而具体layout中又会经过如上步骤。

你可能感兴趣的:(经典源码)