文章目录
- Recycler
- 四级缓存
- 屏幕内缓存 mAttachedScrap 和 mChangedScrap
- 进入缓存
- 屏幕外缓存 mCachedViews
- 滚动
- 缓存池 RecycledViewPool
- 优化
- notifyDataSetChanged
- setHasFixedSize
Recycler承担了RecyclerView中的缓存功能,其中定义的5个集合代表了四个缓存层级。
Recycler
//一级缓存
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
ArrayList<ViewHolder> mChangedScrap = null;
//二级缓存
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
///三级缓存
private ViewCacheExtension mViewCacheExtension;
//四级缓存
RecycledViewPool mRecyclerPool;
//默认mCachedViews的最大缓存数量
static final int DEFAULT_CACHE_SIZE = 2;
关于四级缓存的优先级,可以看Recycler中的以下方法:
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
//省略其他代码
....
ViewHolder holder = null;
// 1) Find by position from scrap/hidden list/cache
if (holder == null) {
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
}
if (holder == null) {
// 2) Find from scrap/cache via stable ids, if exists
if (mAdapter.hasStableIds()) {
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
type, dryRun);
if (holder != null) {
// update position
holder.mPosition = offsetPosition;
fromScrapOrHiddenOrCache = true;
}
}
if (holder == null && mViewCacheExtension != null) {
// We are NOT sending the offsetPosition because LayoutManager does not
// know it.
final View view = mViewCacheExtension
.getViewForPositionAndType(this, position, type);
if (view != null) {
holder = getChildViewHolder(view);
}
}
if (holder == null) { // fallback to pool
holder = getRecycledViewPool().getRecycledView(type);
}
if (holder == null) {
holder = mAdapter.createViewHolder(RecyclerView.this, type);
}
}
return holder;
}
用图片的形式更加直观一些:
缓存有两个边界条件:进和出。
四级缓存
屏幕内缓存 mAttachedScrap 和 mChangedScrap
scrap仅仅在layout期间不为空。当LayoutManager开始 layout 的时候(pre-layout 或 post-layout),会将所有的viewHolder都放到scrap中。然后一个个再取回来,除非有些view发生了变化。
进入缓存
LinearLayoutManager.onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state)
-> LayoutManager.detachAndScrapAttachedViews(Recycler recycler)
-> LayoutManager.scrapOrRecycleView(Recycler recycler, int index, View view)
-> Recycler.scrapView(View view)
void scrapView(View view) {
final ViewHolder holder = getChildViewHolderInt(view);
if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
|| !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
// 标记为移除或失效的 || 没有改变 || item 无动画或动画不复用
mAttachedScrap.add(holder);
} else {
// 放入 mChangedScrap
mChangedScrap.add(holder);
}
}
屏幕外缓存 mCachedViews
mCachedViews是一个 ArrayList 类型集合,不区分 viewHolder 的类型,大小限制为2,可以使用 setItemViewCacheSize()这个方法调整它的大小。
滚动
当发生滚动时,方法调用流程
RecyclerView
-> onTouchEvent(MotionEvent e)
-> scrollByInternal(int x, int y, MotionEvent ev)
-> scrollStep(int dx, int dy, @Nullable int[] consumed)
LinearLayoutManager
-> scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,RecyclerView.State state)
-> scrollBy(int delta, RecyclerView.Recycler recycler, RecyclerView.State state)
-> fill(RecyclerView.Recycler recycler, LayoutState layoutState,RecyclerView.State state, boolean stopOnFocusable)
-> recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState)
-> recycleViewsFromStart(RecyclerView.Recycler recycler, int scrollingOffset,int noRecycleSpace)
-> recycleChildren(RecyclerView.Recycler recycler, int startIndex, int endIndex)
RecyclerView.LayoutManager
-> removeAndRecycleViewAt(int index, @NonNull Recycler recycler)
RecyclerView.Recycler
-> recycleView(@NonNull View view)
-> recycleViewHolderInternal(ViewHolder holder)
-> recycleCachedViewAt(int cachedViewIndex)
-> addViewHolderToRecycledViewPool(@NonNull ViewHolder holder, boolean dispatchRecycled)
RecyclerView.RecycledViewPool
-> putRecycledView(ViewHolder scrap)
在下面的方法中,判断了Holder是否需要回收
private void recycleViewsFromStart(RecyclerView.Recycler recycler, int scrollingOffset,
int noRecycleSpace) {
final int limit = scrollingOffset - noRecycleSpace;
final int childCount = getChildCount();
//依次判断childView是否已经离开屏幕可见区域,并进行回收
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (mOrientationHelper.getDecoratedEnd(child) > limit
|| mOrientationHelper.getTransformedEndWithDecoration(child) > limit) {
// stop here
recycleChildren(recycler, 0, i);
return;
}
}
}
回收时,如果mCachedViews已满,移除mCachedViews中的第一个元素,将其放入RecycledViewPool
void recycleViewHolderInternal(ViewHolder holder) {
if (forceRecycle || holder.isRecyclable()) {
if (mViewCacheMax > 0
&& !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
| ViewHolder.FLAG_REMOVED
| ViewHolder.FLAG_UPDATE
| ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
// mCachedViews的默认缓存数量是2,如果缓存已满,移除掉最老的Holer,为新的Holder让出空间
int cachedViewSize = mCachedViews.size();
if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
//移除mCachedViews中的第一个元素,将其放入RecycledViewPool
recycleCachedViewAt(0);
cachedViewSize--;
}
int targetCacheIndex = cachedViewSize;
mCachedViews.add(targetCacheIndex, holder);
cached = true;
}
if (!cached) {
// 不能放进 mCacheViews 的放 RecyclerViewPool
addViewHolderToRecycledViewPool(holder, true);
recycled = true;
}
}
}
void recycleCachedViewAt(int cachedViewIndex) {
ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
addViewHolderToRecycledViewPool(viewHolder, true);
mCachedViews.remove(cachedViewIndex);
}
public void putRecycledView(ViewHolder scrap) {
final int viewType = scrap.getItemViewType();
final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;
//如果RecycledViewPool的该类型集合已满(默认最大值 = 5),不做任何处理
if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
return;
}
scrap.resetInternal();
scrapHeap.add(scrap);
}
缓存池 RecycledViewPool
RecycledViewPool,它储存了各个类型的 viewHolder。最大数量为5,可以通过 setMaxRecycledViews() 方法来设置每个类型储存的容量。可以多个列表公用一个 RecycledViewPool,使用 setRecycledViewPool() 方法。
优化
notifyDataSetChanged
当调用这个方法时,所有的ViewHolder被标记为无效,意味着全部将进入RecycledViewPool缓存,其他缓存为空,因为RecycledViewPool的默认缓存最大值只有5,当RecyclerView展示的item数量超过5时,无法取得缓存,会选择重新创建,执行onCreateViewHolder。考虑到此,刷新数据尽量不要调用notifyDataSetChanged,使用其他方法代替,或者,调整RecycledViewPool的最大缓存数量
setHasFixedSize
如果RecyclerView的尺寸不受adapter的影响,RecyclerView可以做一些优化。
RecyclerView仍可以基于其他因素更改其尺寸(例如,父级的大小),但大小的计算不依赖于子项的大小或adapter的内容(adapter中的数量除外)。
设置为true,可避免adapter内容更改时整个布局无效。
简而言之,当设置为true的时候,adapter将不再影响recyclerView的大小,由此可以避免刷新recyclerView的时候,重新布局计算大小。
#RecyclerView
public void setHasFixedSize(boolean hasFixedSize) {
mHasFixedSize = hasFixedSize;
}
当调用adapter.notifyDataSetChanged
一类方法刷新界面时,会执行到下面的对应方法:
private class RecyclerViewDataObserver {
@Override
public void onChanged() {
processDataSetCompletelyChanged(true);
if (!mAdapterHelper.hasPendingUpdates()) {
requestLayout();
}
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) {
triggerUpdateProcessor();
}
}
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
if (mAdapterHelper.onItemRangeInserted(positionStart, itemCount)) {
triggerUpdateProcessor();
}
}
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
if (mAdapterHelper.onItemRangeRemoved(positionStart, itemCount)) {
triggerUpdateProcessor();
}
}
@Override
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
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();
}
}
}
onChanged
和其他方法的区别在于,其他方法都调用了triggerUpdateProcessor
,triggerUpdateProcessor
使用mHasFixedSize
做了一些判断,适当的条件下才去调用requestLayout
,onChanged
则直接调用了requestLayout
。因此想要跳过requestLayout
,除了不调用onChanged
外,mHasFixedSize
必须设置为true。
mHasFixedSize
为true的情况下,依然可以通过调用onChanged
,也就是notifyDataSetChanged
,调整recyclerView的大小。