小葵花妈妈课堂开课了《RecyclerView 复用解析》
最新項目遇到一個問題,就是RecycleView的itemview會頻繁拉取圖片,同一時間多次拉取同一張照片。
初探,是因為該場景notifyDataSetChanged()過于頻繁,一秒鐘會調用5次左右,
導致ViewHolder沒有復用,也不是沒有復用而是復用的并沒有像理想中的樣式。
notify
4.1.1 markKnownViewsInvalid
//1 Mark all known views as invalid,僅標記可見的布局為ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID
//2 mAdapter.hasStableIds() true -> mCachedViews 標記 ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID
//3 mAdapter.hasStableIds() false -> addViewHolderToRecycledViewPool ->getRecycledViewPool().putRecycledView(holder); AND mCachedViews.clear();
// we cannot re-use cached views in this case. Recycle them all
4.1.2 markItemDecorInsetsDirty
//child.getLayoutParams()).mInsetsDirty = true;
//mCachedViews layoutParams.mInsetsDirty = true;
回收邏輯
6.1 viewHolder.isInvalid() && !viewHolder.isRemoved() && !mRecyclerView.mAdapter.hasStableIds() -->
6.1.1 true recycler.recycleViewHolderInternal(viewHolder);
addViewHolderToRecycledViewPool(holder, true); //最終放入getRecycledViewPool()
6.1.2 false scrapView(View view)
holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID) || !holder.isUpdated() || canReuseUpdatedViewHolder(holder) -->
6.1.2.1 true mAttachedScrap.add(holder);
6.1.2.2 false mChangedScrap.add(holder);
總結:如果Holder無效最終放入getRecycledViewPool,否則mAttachedScrap
回收另外一個分支
vh.setIsRecyclable(true);
forceRecycle || holder.isRecyclable() 成立
(mViewCacheMax > 0 && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) 不成立,包含ViewHolder.FLAG_INVALID
查找可復用Holder過程
RecyclerView.State state, boolean stopOnFocusable)
LayoutState layoutState, LayoutChunkResult result)
9.1 isPreLayout() --> getChangedScrapViewForPosition(int position) //如果是pre-layout,會從mChangedScrap獲取復用
9.2 getScrapOrHiddenOrCachedHolderForPosition(position, dryRun)
9.2.1 mAttachedScrap 首先從該處獲取,
9.2.2 mChildHelper.findHiddenNonRemovedView(position) 拿到隱藏的ViewHolder
9.2.3 mCachedViews //Search in our first-level recycled view cache. 最后從一級緩存獲取
// invalid view holders may be in cache if adapter has stable ids as they can be
// retrieved via getScrapOrCachedViewForId
// 通過原廠注釋可以知道,has stable才可以使用mCachedViews緩存。在回收處也可以看出。
9.2.4 mAdapter.hasStableIds() --> getScrapOrCachedViewForId() //仍然需要hasStableIds
// 首先mAttachedScrap
// 之后mCachedViews
9.3 mViewCacheExtension //外部擴展
9.4 getRecycledViewPool().getRecycledView(type) //此處查到holder被resetInternal,即需要重新bind
9.5 mAdapter.createViewHolder(RecyclerView.this, type); //最后,create
bind過程
boolean dryRun, long deadlineNs)
至此RecyclerView的復用機制已經差不多了。至此也看到了我的問題也出現了答案的線索。
因為是notifyDataSetChanged出發的刷新,會將所有ViewHolder標記為FLAG_INVALID。
在回收ViewHolder時,會放入getRecycledViewPool中。
RecycledViewPool官方注釋
RecycledViewPool lets you share Views between multiple RecyclerViews.
If you want to recycle views across RecyclerViews, create an instance of RecycledViewPool
and use {@link RecyclerView#setRecycledViewPool(RecycledViewPool)}.
RecyclerView automatically creates a pool for itself if you don’t provide one.
作用為多個RecyclerViews之間復用ViewHolder。
可以通過setRecycledViewPool進行設置。
SparseArray mScrap = new SparseArray<>(); 用來存儲不同類型的ViewHolder
默認mMaxScrap大小為5個
private static final int DEFAULT_MAX_SCRAP = 5;也就是做多存儲5個。我的項目正好有6個Item顯示,就導致只能存儲 1,2,3,4,5 而0沒有進行存儲
onBind的時候就會出現0 - 綁定 1,1 綁定2… 的問題。
解決辦法為通過
new一個RecyclerView.RecycledViewPool, 并設置recycledViewPool.setMaxRecycledViews(0, 6);
第一個參數viewType,我這里僅有一種type,所以就寫0
最終通過mRecycleList.setRecycledViewPool(recycledViewPool); 設置RecycledViewPool。
至此大工告成。
RecyclerView源碼有1w多行。剛看的時候無從下手,向其他博主所說的見森林而不見樹木。
我就是從notifyDataSetChanged 和 onBindViewHolder入手,查找上下邏輯從而追蹤定位問題。
總結
以上是生活随笔為你收集整理的小葵花妈妈课堂开课了《RecyclerView 复用解析》的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: nginx的带宽限制和并发控制
- 下一篇: 红桃3