RecyclerView的緩存機制是為了提高列表滾動時的性能。采用了多級緩存策略來存儲和復用視圖(View),減少視圖的創建和銷毀,進而減少內存分配和GC的頻率。
負責回收和復用ViewHolder的類是Recycler,負責緩存的主要就是這個類的幾個成員變量。
public final class Recycler { // 存放可見范圍內的 ViewHolder (但是在 onLayoutChildren 的時候,會將所有 View 都會緩存到這), 從這里復用的 ViewHolder 如果 position 或者 id 對應的上,則不需要重新綁定數據。 final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>(); // 存放可見范圍內并且數據發生了變化的 ViewHolder,從這里復用的 ViewHolder 需要重新綁定數據。 ArrayList<ViewHolder> mChangedScrap = null; // 存放 remove 掉的 ViewHolder,從這里復用的 ViewHolder 如果 position 或者 id 對應的上,則不需要重新綁定數據。 final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>(); // 默認值是 2 private int mRequestedCacheMax = DEFAULT_CACHE_SIZE; // 默認值是 2 int mViewCacheMax = DEFAULT_CACHE_SIZE; // 存放 remove 掉,并且重置了數據的 ViewHolder,從這里復用的 ViewHolder 需要重新綁定數據。 // 默認值大小是 5 RecycledViewPool mRecyclerPool; // 自定義的緩存 private ViewCacheExtension mViewCacheExtension; }
RecyclerView的緩存機制主要由四個部分組成,它們按照從高到低的優先級排列:
包括mAttachedScrap和mChangedScrap,也稱為屏內緩存,因為它們主要用于保存屏幕內當前可見或者即將可見的ViewHolder。
mAttachedScrap:存放的是已添加到RecyclerView但與RecyclerView臨時分離(例如在滾動或布局調整過程中)的ViewHolder。
mChangedScrap:存放的是數據已改變但尚未重新綁定數據的ViewHolder,通常用于動畫播放等場景。
又稱離屏緩存,用于保存最新被移除(remove)但尚未被回收的ViewHolder。
緩存的大小是有限制的,默認最大數量為2(由DEFAULT_CACHE_SIZE定義)。
當需要展示新視圖時,會首先檢查Cache緩存中是否有可用的ViewHolder。
「ViewCacheExtension」
為開發者預留的緩存池,允許開發者自定義緩存策略,存儲更多的或特定類型的ViewHolder。
開發者可以通過實現ViewCacheExtension接口來擴展緩存功能。
「RecycledViewPool(mRecyclerPool)」
終極的回收緩存池,用于存放被標識為廢棄(即其他緩存池不再需要的)的ViewHolder。
這些ViewHolder已經被抹除了數據,需要重新綁定數據才能使用。
RecycledViewPool會根據不同的item類型創建不同的List來存儲ViewHolder。
int fill(RecyclerView.Recycler recycler, LayoutState layoutState, RecyclerView.State state, boolean stopOnFocusable) { // max offset we should set is mFastScroll + available final int start = layoutState.mAvailable; //首選該語句塊的判斷,判斷當前狀態是否為滾動狀態,如果是的話,則觸發 recycleByLayoutState 方法 if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) { // TODO ugly bug fix. should not happen if (layoutState.mAvailable < 0) { layoutState.mScrollingOffset += layoutState.mAvailable; } // 分析1----回收 recycleByLayoutState(recycler, layoutState); } while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) { //分析2----復用 layoutChunk(recycler, state, layoutState, layoutChunkResult); }}// 分析1----回收 // 通過一步步追蹤,我們發現最后調用的是 removeAndRecycleViewAt() public void removeAndRecycleViewAt(int index, @NonNull Recycler recycler) { final View view = getChildAt(index); //分析1-1 removeViewAt(index); //分析1-2 recycler.recycleView(view);}// 分析1-1// 從 RecyclerView 移除一個 View public void removeViewAt(int index) { final View child = getChildAt(index); if (child != null) { mChildHelper.removeViewAt(index); }}//分析1-2 // recycler.recycleView(view) 最終調用的是 recycleViewHolderInternal(holder) 進行回收 VH (ViewHolder)void recycleViewHolderInternal(ViewHolder holder) { if (forceRecycle || holder.isRecyclable()) { //判斷是否滿足放進 mCachedViews if (mViewCacheMax > 0 && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID| ViewHolder.FLAG_REMOVED| ViewHolder.FLAG_UPDATE| ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)){ // 判斷 mCachedViews 是否已滿 if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) { // 如果滿了就將下標為0(即最早加入的)移除,同時將其加入到 RecyclerPool 中 recycleCachedViewAt(0); cachedViewSize--; } mCachedViews.add(targetCacheIndex, holder); cached = true; } //如果沒有滿足上面的條件,則直接存進 RecyclerPool 中 if (!cached) { addViewHolderToRecycledViewPool(holder, true); recycled = true; } }}//分析2void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result) { //分析2-1 View view = layoutState.next(recycler); if (layoutState.mScrapList == null) { if (mShouldReverseLayout == (layoutState.mLayoutDirection == LayoutState.LAYOUT_START)) { //添加到 RecyclerView 上 addView(view); } else { addView(view, 0); } }}//分析2-1//layoutState.next(recycler) 最后調用的是 tryGetViewHolderForPositionByDeadline() 這個方法正是 復用 核心的方法ViewHolder tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs) { // 0) If there is a changed scrap, try to find from there // 例如:我們調用 notifyItemChanged 方法時 if (mState.isPreLayout()) { // 如果是 changed 的 ViewHolder 那么就先從 mChangedScrap 中找 holder = getChangedScrapViewForPosition(position); fromScrapOrHiddenOrCache = holder != null; } // 1) Find by position from scrap/hidden list/cache if (holder == null) { //如果在上面沒有找到(holder == null),那就嘗試從通過 pos 在 mAttachedScrap/ mHiddenViews / mCachedViews 中獲取 holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun); } if (holder == null) { // 2) Find from scrap/cache via stable ids, if exists if (mAdapter.hasStableIds()) { //如果在上面沒有找到(holder == null),那就嘗試從通過 id 在 mAttachedScrap/ mCachedViews 中獲取 holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), } if (holder == null && mViewCacheExtension != null) { //這里是通過自定義緩存中獲取,忽略 } //如果在上面都沒有找到(holder == null),那就嘗試在 RecycledViewPool 中獲取 if (holder == null) { // fallback to pool holder = getRecycledViewPool().getRecycledView(type); if (holder != null) { //這里拿的是,要清空數據的 holder.resetInternal(); } } //如果在 Scrap / Hidden / Cache / RecycledViewPool 都沒有找到,那就只能創建一個了。 if (holder == null) { holder = mAdapter.createViewHolder(RecyclerView.this, type); } } return holder;}
「注意」:當RecyclerView不再需要某個ViewHolder時(例如,當列表項被完全移出屏幕并且緩存已滿時),ViewHolder會被放入RecycledViewPool并最終可能被系統回收。
本文鏈接:http://www.tebozhan.com/showinfo-26-97296-0.htmlRecyclerView的緩存機制及使用策略
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com
上一篇: 一個合理的前端應用文件結構
下一篇: 一個詭異的Json反序列化問題