RecyclerView 源碼、回收、複用

RecyclerView

根據平常對recyclerview的調用過程進行代碼跟蹤,以此來了解RecyclerView的原理

RecyclerView recycler = findViewById(R.id.xxx);
recycler.setLayoutManager(new LinearLayoutManager(this,Orientation,false));
//recyclerView.setItemAnimator(new DefaultItemAnimator());  //item的動畫,這裏不涉及,不設置也是這個默認動畫
//recyclerView.addItemDecoration(new DividerItemDecoration(this,DividerItemDecoration.VERTICAL)); //item之間的分割線,這裏不涉及
recycler.setAdapter(new RecyclerView.Adapyer<VH extends ViewHolder>);
recycler.set

瞭解調用過程後,看看各個方法的具體實現。

	public void setLayoutManager(@Nullable LayoutManager layout) {
        if (layout == mLayout) {//如果本身持有一個layoutManager比對是否一致
            return;
        }
        stopScroll();//停止滑動
        // TODO We should do this switch a dispatchLayout pass and animate children. There is a good
        // chance that LayoutManagers will re-use views.
        if (mLayout != null) {//一開始就有一個layout,通常切換視圖佈局的時候不爲null,Grid變爲Linear或者瀑布流
            // end all running animations,停止所有正在運行的動畫
            if (mItemAnimator != null) {
                mItemAnimator.endAnimations();
            }
            //移除所有的視圖
            mLayout.removeAndRecycleAllViews(mRecycler);
            mLayout.removeAndRecycleScrapInt(mRecycler);
            mRecycler.clear();//清除複用數據

            if (mIsAttached) {
                mLayout.dispatchDetachedFromWindow(this, mRecycler);
            }
            mLayout.setRecyclerView(null);
            mLayout = null;
        } else {
            mRecycler.clear();//清除複用數據
        }
        // this is just a defensive measure for faulty item animators.
        mChildHelper.removeAllViewsUnfiltered();
        mLayout = layout;
        if (layout != null) {
            if (layout.mRecyclerView != null) {
                throw new IllegalArgumentException("LayoutManager " + layout
                        + " is already attached to a RecyclerView:"
                        + layout.mRecyclerView.exceptionLabel());
            }
            mLayout.setRecyclerView(this);//將RecyclerView綁定至LayoutManager
            if (mIsAttached) {
                mLayout.dispatchAttachedToWindow(this);
            }
        }
        mRecycler.updateViewCacheSize();//更新緩存View
        requestLayout();//重繪
    }

LayoutManager綁定了RecyclerView後:

	void setRecyclerView(RecyclerView recyclerView) {
            if (recyclerView == null) {
                mRecyclerView = null;
                mChildHelper = null;
                mWidth = 0;
                mHeight = 0;
            } else {
            //更新數據
                mRecyclerView = recyclerView;
                mChildHelper = recyclerView.mChildHelper;
                mWidth = recyclerView.getWidth();
                mHeight = recyclerView.getHeight();
            }
            mWidthMode = MeasureSpec.EXACTLY;//給定了高寬
            mHeightMode = MeasureSpec.EXACTLY;
        }

回收,這個方法真的隨時都在調用,從而使得將緩存裏的視圖的給remove掉,放進pool裏

 	void updateViewCacheSize() {
 			//layoutManager爲空否?展示在屏幕上的View個數
            int extraCache = mLayout != null ? mLayout.mPrefetchMaxCountObserved : 0;
            mViewCacheMax = mRequestedCacheMax + extraCache;//緩存視圖,mRequestedCacheMax 默認爲2

            // first, try the views that can be recycled  試圖回收可回收視圖
            for (int i = mCachedViews.size() - 1;//倒序查詢
                    i >= 0 && mCachedViews.size() > mViewCacheMax; i--) {
                recycleCachedViewAt(i);
            }
        }

回收緩存視圖

void recycleCachedViewAt(int cachedViewIndex) {
    if (DEBUG) {
        Log.d(TAG, "Recycling cached view at index " + cachedViewIndex);
    }
    //RecyclerView裏緩存的實際對象就是ViewHolder,mCachedViews實際是一個ArrayList
    ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
    if (DEBUG) {
        Log.d(TAG, "CachedViewHolder to be recycled: " + viewHolder);
    }
    //將ViewHolder添加進RecyclerViewPool,然後進行remove
    addViewHolderToRecycledViewPool(viewHolder, true);
    mCachedViews.remove(cachedViewIndex);
}

void addViewHolderToRecycledViewPool(@NonNull ViewHolder holder, boolean dispatchRecycled) {
    clearNestedRecyclerViewIfNotNested(holder);//清除嵌套,
    View itemView = holder.itemView;
    //androidx 源碼
    if (mAccessibilityDelegate != null) {//對ViewHolder添加標誌位
        AccessibilityDelegateCompat originalDelegate = mAccessibilityDelegate
                .mItemDelegate.getAndRemoveOriginalDelegateForItem(itemView);
        // Set the a11y delegate back to whatever the original delegate was.
        ViewCompat.setAccessibilityDelegate(itemView, originalDelegate);
    }
    //API28源代碼,添加flag
    //if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_SET_A11Y_ITEM_DELEGATE)) {
	//    holder.setFlags(0, ViewHolder.FLAG_SET_A11Y_ITEM_DELEGATE);
	//    ViewCompat.setAccessibilityDelegate(holder.itemView, null);
	//}
    if (dispatchRecycled) {
        dispatchViewRecycled(holder);//被回收了,進行回調通知,有listener、adapter...
    }
    holder.mOwnerRecyclerView = null;//重置所屬RecyclerView
    getRecycledViewPool().putRecycledView(holder);//放進RecyclerViewPool維護的數據緩存裏
}

至此setLayoutManager()方法到這裏結束,接着是setAdapter()方法:

	//設置適配器以提供相應的視圖
	//回收所有的視圖到pool中,如果pool只有一個適配器,那將被清除...
    public void setAdapter(@Nullable Adapter adapter) {
        // bail out if layout is frozen
        setLayoutFrozen(false);//追蹤下去,配置修改,並調用requestLayout().......
        setAdapterInternal(adapter, false, true);
        processDataSetCompletelyChanged(false);//將當前的視圖和緩存的視圖進行標記位重置,看下面的mark...方法
        requestLayout();//重繪
    }
    /**
     * Removes and recycles all views - both those currently attached, and those in the Recycler.
     * 移除並回收所有的View,包括正在使用和在Recycler裏緩存好的
     */
    void removeAndRecycleViews() {
        // end all running animations
        if (mItemAnimator != null) {
            mItemAnimator.endAnimations();
        }
        // Since animations are ended, mLayout.children should be equal to
        // recyclerView.children. This may not be true if item animator's end does not work as
        // expected. (e.g. not release children instantly). It is safer to use mLayout's child
        // count.
        if (mLayout != null) {
        	//layout清空數據
            mLayout.removeAndRecycleAllViews(mRecycler);
            mLayout.removeAndRecycleScrapInt(mRecycler);
        }
        // we should clear it here before adapters are swapped to ensure correct callbacks.
        mRecycler.clear();//清空數據
    }

    /**
     * Replaces the current adapter with the new one and triggers listeners.
     * @param adapter The new adapter
     * @param compatibleWithPrevious If true, the new adapter is using the same View Holders and
     *                               item types with the current adapter (helps us avoid cache
     *                               invalidation).爲true就使用一樣的holder和type,就避免了緩存失效
     * @param removeAndRecycleViews  If true, we'll remove and recycle all existing views. If
     *                               compatibleWithPrevious is false, this parameter is ignored.
     */
    private void setAdapterInternal(@Nullable Adapter adapter, boolean compatibleWithPrevious,
            boolean removeAndRecycleViews) {
        if (mAdapter != null) {
            mAdapter.unregisterAdapterDataObserver(mObserver);//註銷觀察者
            mAdapter.onDetachedFromRecyclerView(this);//解綁RecyclerView
        }
        if (!compatibleWithPrevious || removeAndRecycleViews) {
            removeAndRecycleViews();//清除所有的視圖
        }
        mAdapterHelper.reset();//重置
        final Adapter oldAdapter = mAdapter;//將當前的adapter留一下
        mAdapter = adapter;//設置新的
        if (adapter != null) {
            adapter.registerAdapterDataObserver(mObserver);//註冊
            adapter.onAttachedToRecyclerView(this);//綁定RecyclervIew
        }
        if (mLayout != null) {
            mLayout.onAdapterChanged(oldAdapter, mAdapter);//回調adapter改變了
        }
        //回調adapter改變了,將當前的Pool的數據clear掉。進而重新設置。
        mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious);
        mState.mStructureChanged = true;//結構已經改變
    }

對視圖進行標記位重置:

    void markKnownViewsInvalid() {
        final int childCount = mChildHelper.getUnfilteredChildCount();
        for (int i = 0; i < childCount; i++) {
            final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
            if (holder != null && !holder.shouldIgnore()) {//不爲空以及不應該被忽略
                holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID);//將標記變成更新|無效
            }
        }
        markItemDecorInsetsDirty();//對分割線的重新設置
        mRecycler.markKnownViewsInvalid();//對緩存的視圖也進行標記位修改
    }

至此,瞭解了LayoutManager和Adapter的相關邏輯。

那麼重要的是,RecyclerView是如何複用的呢?上面的代碼看的到mRecycler的出現,名字上就知道是回收的意思。那麼,是這個類對RecyclerView的數據進行回收複用麼?Look ↓↓↓↓↓↓↓

源碼

父類ViewGroup,實現了ScrollView、NestedScrollingChild2方法,AndroidX還實現了NestedScrollingChild3

int computeHorizontalScrollRange();//水平滾動範圍
int computeHorizontalScrollOffset();//水平滾動偏移量
int computeHorizontalScrollExtent();//水平滾動thmub的寬度
int computeVerticalScrollRange();//同理
int computeVerticalScrollOffset();//同理
int computeVerticalScrollExtent();//同理
//另外的不貼了,子子孫孫無窮盡也。。
//各種初始化
...
//列一些我覺得主要的東西,但下文不一定會涉及
private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver();//觀察者,adapter更新數據就能刷新視圖
final Recycler mRecycler = new Recycler();//複用
AdapterHelper mAdapterHelper;//處理adapter的更新
ChildHelper mChildHelper;//處理RecyclerView和LayoutManager的子View的關係
final ViewInfoStore mViewInfoStore = new ViewInfoStore();//動畫時候的信息store
final ArrayList<ItemDecoration> mItemDecorations = new ArrayList<>();//下劃線數組集合
private final ArrayList<OnItemTouchListener> mOnItemTouchListeners = new ArrayList<>();//touchListener集合

瞭解上面內容,對RecyclerView持有的各個對象基本瞭解後,看看重要的回收與複用。

回收與複用

RecyclerView的內部類Recycler進行管理

final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();  
ArrayList<ViewHolder> mChangedScrap = null;
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
private final List<ViewHolder>
        mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);
private int mRequestedCacheMax = DEFAULT_CACHE_SIZE;
int mViewCacheMax = DEFAULT_CACHE_SIZE;
RecycledViewPool mRecyclerPool;
private ViewCacheExtension mViewCacheExtension;
static final int DEFAULT_CACHE_SIZE = 2;

上面的一堆集合就是對ViewHolder進行緩存的地方,那麼,如何對ViewHolder進行管理的呢??
在Adapter裏,需要重寫onCreateViewHolder()方法,那麼這個方法在哪裏調用??

public final VH createViewHolder(@NonNull ViewGroup parent, int viewType) {
    try {
        TraceCompat.beginSection(TRACE_CREATE_VIEW_TAG);
        final VH holder = onCreateViewHolder(parent, viewType);
        if (holder.itemView.getParent() != null) {
            throw new IllegalStateException("ViewHolder views must not be attached when"
                    + " created. Ensure that you are not passing 'true' to the attachToRoot"
                    + " parameter of LayoutInflater.inflate(..., boolean attachToRoot)");
        }
        holder.mItemViewType = viewType;
        return holder;
    } finally {
        TraceCompat.endSection();
    }
}

是傳遞的數據進來的,哪裏調用的?再找找。

tryGetViewHolderForPositionByDeadline這個方法調用了創建視圖。

//在這一部分裏創建ViewHolder
if (holder == null) {
    long start = getNanoTime();
    if (deadlineNs != FOREVER_NS
            && !mRecyclerPool.willCreateInTime(type, start, deadlineNs)) {
        // abort - we have a deadline we can't meet
        return null;
    }
    holder = mAdapter.createViewHolder(RecyclerView.this, type);
    if (ALLOW_THREAD_GAP_WORK) {
        // only bother finding nested RV if prefetching
        RecyclerView innerView = findNestedRecyclerView(holder.itemView);//找到嵌套View
        if (innerView != null) {
            holder.mNestedRecyclerView = new WeakReference<>(innerView);//弱引用,回收
        }
    }
    long end = getNanoTime();
    mRecyclerPool.factorInCreateTime(type, end - start);
    if (DEBUG) {
        Log.d(TAG, "tryGetViewHolderForPositionByDeadline created new ViewHolder");
    }
}

這是創建,對於複用呢?在LayoutManager中對調用Recycler的各個方法,在LayoutManager繪製視圖的時候,通過position得到對應的ViewHolder

onLayoutChildren->fill->layoutChunk->next->recycler.getViewForPosition(mCurrentPosition)->tryGetViewHolderForPositionByDeadline

到了最後的方法就是獲取各個position上的ViewHolder
作爲緩存,那麼,通過Recycler持有的各個數據集,從而循環查詢判斷得到對應的ViewHolder。當數據集中不能拿到對應的ViewHolder的時候,纔會用上面的方法在Adapter中創建新的視圖。
第一步:

// 0) If there is a changed scrap, try to find from there
//通過mChangeScrap集合查找
if (mState.isPreLayout()) {
	holder = getChangedScrapViewForPosition(position);
	fromScrapOrHiddenOrCache = holder != null;
}
if (holder == null) {
//通過方法查找從mAttachScrap、mHiddenView、mCacheView得到對應的ViewHolder
//mHiddenView是mChildHelper進行管理的,也通過這個查找。。
    holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
    if (holder != null) {
        if (!validateViewHolderForOffsetPosition(holder)) {
            // recycle holder (and unscrap if relevant) since it can't be used
            if (!dryRun) {
                // we would like to recycle this but need to make sure it is not used by
                // animation logic etc.
                holder.addFlags(ViewHolder.FLAG_INVALID);
                if (holder.isScrap()) {
                    removeDetachedView(holder.itemView, false);
                    holder.unScrap();//不捨棄
                } else if (holder.wasReturnedFromScrap()) {
                    holder.clearReturnedFromScrapFlag();
                }
                recycleViewHolderInternal(holder);//預備回收
            }
            holder = null;//回收置空
        } else {
            fromScrapOrHiddenOrCache = true;
        }
    }
}
/*******************在這裏,上面的驗證回收置空,下面id查找***********************/
if (holder == null) {
//爲空從上述數據集裏通過id查找
    final int offsetPosition = mAdapterHelper.findPositionOffset(position);
    if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) {
        throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item "
                + "position " + position + "(offset:" + offsetPosition + ")."
                + "state:" + mState.getItemCount() + exceptionLabel());
    }
    final int type = mAdapter.getItemViewType(offsetPosition);
    // 2) Find from scrap/cache via stable ids, if exists    id查詢
    if (mAdapter.hasStableIds()) {
        holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
                type, dryRun);
        if (holder != null) {
            // update position,不爲空更新位置
            holder.mPosition = offsetPosition;
            fromScrapOrHiddenOrCache = true;
        }
    }
    //如果id沒有找到,從ViewCacheExtension中查詢,RecyclerView本身不會實現這個實例,需要自己通過
    //setViewCacheExtension()方法傳入實例
    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) {
                throw new IllegalArgumentException("getViewForPositionAndType returned"
                        + " a view which does not have a ViewHolder"
                        + exceptionLabel());
            } else if (holder.shouldIgnore()) {
                throw new IllegalArgumentException("getViewForPositionAndType returned"
                        + " a view that is ignored. You must call stopIgnoring before"
                        + " returning this view." + exceptionLabel());
            }
        }
    }
    //依然爲空,從RecyclerViewPool中拿到ViewHolder
    if (holder == null) { // fallback to pool
        if (DEBUG) {
            Log.d(TAG, "tryGetViewHolderForPositionByDeadline("
                    + position + ") fetching from shared pool");
        }
        //RecyclerViewPool內部維護了一個數據集,通過type對ViewHolder進行緩存,默認大小爲5
        holder = getRecycledViewPool().getRecycledView(type);
        if (holder != null) {
            holder.resetInternal();
            if (FORCE_INVALIDATE_DISPLAY_LIST) {
                invalidateDisplayListInt(holder);//強制刷新一遍visible
            }
        }
    }
    //以上緩存都沒有,只有新建了,以下代碼省略,在上面↑
    .........

至此,對ViewHolder的複用瞭解清楚了,那麼如何緩存的呢?就是LayoutManager創建的時候回調用recyclerView()方法,這時候就會對所擁有的ViewHolder進行cache緩存,當cache清理的時候,就會加入RecyclerViewPool中進行緩存。

OK,it’s over!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章