先從 getViewByPosition() 開(kāi)始,LayoutManager 會(huì)詢問(wèn) RecyclerView,請(qǐng)?jiān)?position 為8的位置給我一個(gè)View。 這是RecycleView所做的響應(yīng):
- 搜索 changed scrap
- 搜索 attached scrap(屏幕內(nèi))
- 搜索 未刪除的隱藏視圖
- 搜索 view cache(屏幕外)
- 如果適配器具有穩(wěn)定的 ID,用 ID 再次去搜索 attached scrap 和 view cache。
- 搜索 ViewCacheExtension
- 搜索 RecycledViewPool
如果在所有這些地方都找不到合適的 View,則會(huì)通過(guò)調(diào)用適配器的onCreateViewHolder()方法來(lái)創(chuàng)建一個(gè) View 。 然后,如有必要它通過(guò) onBindViewHolder()綁定 View,最后返回它。
/**
* Called when RecyclerView needs a new {@link ViewHolder} of the given type to represent
* an item.
* <p>
* This new ViewHolder should be constructed with a new View that can represent the items
* of the given type. You can either create a new View manually or inflate it from an XML
* layout file.
* <p>
* The new ViewHolder will be used to display items of the adapter using
* {@link #onBindViewHolder(ViewHolder, int, List)}. Since it will be re-used to display
* different items in the data set, it is a good idea to cache references to sub views of
* the View to avoid unnecessary {@link View#findViewById(int)} calls.
*
* @param parent The ViewGroup into which the new View will be added after it is bound to
* an adapter position.
* @param viewType The view type of the new View.
*
* @return A new ViewHolder that holds a View of the given view type.
* @see #getItemViewType(int)
* @see #onBindViewHolder(ViewHolder, int)
*/
@NonNull
public abstract VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType);
如你所見(jiàn),這里發(fā)生了很多事情,我們的目標(biāo)是弄清楚所有這些緩存的含義,它們?nèi)绾喂ぷ饕约盀槭裁葱枰鼈?,我們將逐一介紹它們 。
通常認(rèn)為 RecyclerView 有四級(jí)緩存,RecyclerView 的緩存是通過(guò) Recycler 類(lèi)來(lái)完成的,方法的入口:
/**
* Obtain a view initialized for the given position.
*
* This method should be used by {@link LayoutManager} implementations to obtain
* views to represent data from an {@link Adapter}.
* <p>
* The Recycler may reuse a scrap or detached view from a shared pool if one is
* available for the correct view type. If the adapter has not indicated that the
* data at the given position has changed, the Recycler will attempt to hand back
* a scrap view that was previously initialized for that data without rebinding.
*
* @param position Position to obtain a view for
* @return A view representing the data at <code>position</code> from <code>adapter</code>
*/
@NonNull
public View getViewForPosition(int position) {
return getViewForPosition(position, false);
}
緩存的內(nèi)容是 ViewHolder,緩存的地方,是 Recycler 的幾個(gè) list:
/**
* A Recycler is responsible for managing scrapped or detached item views for reuse.
*
* <p>A "scrapped" view is a view that is still attached to its parent RecyclerView but
* that has been marked for removal or reuse.</p>
*
* <p>Typical use of a Recycler by a {@link LayoutManager} will be to obtain views for
* an adapter's data set representing the data at a given position or item ID.
* If the view to be reused is considered "dirty" the adapter will be asked to rebind it.
* If not, the view can be quickly reused by the LayoutManager with no further work.
* Clean views that have not {@link android.view.View#isLayoutRequested() requested layout}
* may be repositioned by a LayoutManager without remeasurement.</p>
*/
public final class 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;
...省略
}
第一級(jí)緩存
mAttachedScrap: 用于緩存顯示在屏幕上的 item 的 ViewHolder?!皊crapped”視圖是仍附加到其父 RecyclerView 的視圖,但已標(biāo)記為可刪除或重復(fù)使用。用于緩存顯示在屏幕上的 item 的 ViewHolder??梢钥吹竭@個(gè)變量是個(gè)存放 ViewHolder 對(duì)象的ArrayList ,而且是沒(méi)有容量限制的,它是屬于 Scrap 的一種,這里的數(shù)據(jù)是不做修改的,不會(huì)重新走Adapter的綁定方法的。
mChangedScrap: 跟 ViewHolder 的數(shù)據(jù)發(fā)生變化時(shí)有關(guān)吧。這個(gè)變量和 mAttachedScrap 是一樣的,唯一不同的是,它存放的是發(fā)生變化的 ViewHolder ,如果使用到這里緩存的 ViewHolder 是要重新走 Adapter 的綁定方法的。
第二級(jí)緩存
mCachedViews:劃出屏幕外的 item,這個(gè) list 的默認(rèn)大小是2。這個(gè)就重要得多了,滑動(dòng)過(guò)程中的回收和復(fù)用都是先處理的這個(gè) List,這個(gè)集合里存的 ViewHolder 的原本數(shù)據(jù)信息都在,所以可以直接添加到 RecyclerView 中顯示,不需要再次重新 onBindViewHolder()。這個(gè)變量同樣是一個(gè)存放 ViewHolder 對(duì)象的 ArrayList ,但是這個(gè)不同于上面的兩個(gè)里面存放的是顯示在屏幕上的視圖,它里面存放的是已經(jīng) remove 掉的視圖,已經(jīng)和 RecyclerView 分離關(guān)系的視圖,但是它里面的 ViewHolder 依然保存著之前的信息(綁定的數(shù)據(jù)以及位置信息等),而且它的容量是有限的默認(rèn)是2(不同的API可能會(huì)有差異),同樣它的大小也是可以修改的,合理的改變它的大小,可以減少 ViewHolder 數(shù)據(jù)綁定的次數(shù)。
第三級(jí)緩存
mViewCacheExtension:自定義緩存,RecyclerView 默認(rèn)是沒(méi)有實(shí)現(xiàn)的, ViewCacheExtension 是一個(gè)幫助程序類(lèi),用于提供附加的視圖緩存層,該緩存可以由開(kāi)發(fā)者控制。
第四級(jí)緩存
mRecyclerPool:這個(gè)也很重要,但存在這里的 ViewHolder 的數(shù)據(jù)信息會(huì)被重置掉,相當(dāng)于 ViewHolder 是一個(gè)重新新建的一樣,所以需要重新調(diào)用 onBindViewHolder 來(lái)綁定數(shù)據(jù)。這個(gè)變量是一個(gè)類(lèi)和上面三個(gè)不一樣,這里面保存的 ViewHolder 不僅僅是 remove 掉的視圖,而且是“恢復(fù)出廠設(shè)置”的視圖,任何綁定過(guò)的痕跡都沒(méi)有了,如果想用這里的緩存的 ViewHolder 那就要重新走 Adapter 的綁定方法,所以盡量不要讓 ViewHolder 進(jìn)入這一層。因?yàn)?RecyclerView 是支持多布局的,所以 mRecyclerPool 的緩存是按照 itemType 來(lái)分開(kāi)存儲(chǔ)的,來(lái)看一下它的結(jié)構(gòu):
- 首先我們看到一個(gè)常量‘DEFAULT_MAX_SCRAP’默認(rèn)值為5,這個(gè)就是一個(gè)緩存池的默認(rèn)緩存數(shù)。它不是整個(gè)緩存池的總數(shù),它是每個(gè)對(duì)應(yīng) itemType 類(lèi)型的默認(rèn)緩存數(shù),當(dāng)然你可以針對(duì)不同的類(lèi)型修改其緩存數(shù)的大小,適當(dāng)?shù)男薷木彺鏀?shù)的大小可以減少 ViewHolder 的創(chuàng)建數(shù)量。你可以像這樣更改它:
recyclerView.getRecycledViewPool()
.setMaxRecycledViews(SOME_VIEW_TYPE, POOL_CAPACITY);
這是非常重要的靈活性。如果屏幕上有數(shù)十個(gè)相同類(lèi)型的項(xiàng)目,這些項(xiàng)目經(jīng)常同時(shí)更改,請(qǐng)為該視圖類(lèi)型增大池。并且,如果您知道某些視圖類(lèi)型的項(xiàng)目非常稀有,以至于它們?cè)谄聊簧巷@示的數(shù)量永遠(yuǎn)不會(huì)超過(guò)一個(gè),請(qǐng)為該視圖類(lèi)型設(shè)置池大小1。否則,遲早池中將充滿其中的5個(gè)項(xiàng)目,而其中4個(gè)項(xiàng)目只會(huì)閑置在那兒,這會(huì)浪費(fèi)內(nèi)存。
getRecyclerView()、putRecycledView()、clear()方法是公共的,因此你可以操縱池的內(nèi)容。手動(dòng)使用 putRecycledView(),例如事先準(zhǔn)備一些 ViewHolders,不過(guò)這不是一個(gè)好想法。你只能在適配器的 onCreateViewHolder()方法中創(chuàng)建 ViewHolder,否則 ViewHolders 可能會(huì)以 RecyclerView 所不希望的狀態(tài)出現(xiàn)。另一個(gè)很酷的功能是,與 getRecycledViewPool()一起有一個(gè) setRecycledViewPool(),因此你可以將單個(gè)池重用于多個(gè)RecycleViews。最后,我會(huì)注意到每種視圖類(lèi)型的池都是堆棧(后進(jìn)先出)。。
- 我們看到一個(gè)靜態(tài)內(nèi)部類(lèi) ScrapData ,我們還看到了 mMaxScrap 并且前面的常量賦值給了它,這就解釋了上面提到的,這個(gè)緩存數(shù)量是對(duì)應(yīng)不同 itemType 類(lèi)型的緩存數(shù),再看一下 mScrapHeap 同樣是一個(gè)緩存 ViewHolder 的 ArrayList ,這就說(shuō)明ScrapData 類(lèi)是 mScrapHeap 對(duì) ViewHolder 進(jìn)行緩存,并且數(shù)組的最大值為5的類(lèi)的一個(gè)封裝。
- 最后我們看到了 mScrap 這個(gè)變量,它是一個(gè)存儲(chǔ)我們上面提到的 ScrapData 類(lèi)的對(duì)象的 SparseArray,這樣就解釋了 RecyclerPool 是不同 itemType 的 ViewHolder 按 itemType 類(lèi)型分類(lèi)緩存起來(lái)的。
mCachedViews 的數(shù)量達(dá)到上限之后,會(huì)把 ViewHolder 存入 mRecyclerPool。mRecyclerPool 用 SparseArray 來(lái)緩存進(jìn)入這一級(jí)的 ViewHolder:
/**
* RecycledViewPool lets you share Views between multiple RecyclerViews.
* <p>
* If you want to recycle views across RecyclerViews, create an instance of RecycledViewPool
* and use {@link RecyclerView#setRecycledViewPool(RecycledViewPool)}.
* <p>
* RecyclerView automatically creates a pool for itself if you don't provide one.
*/
public static class RecycledViewPool {
private static final int DEFAULT_MAX_SCRAP = 5;
/**
* Tracks both pooled holders, as well as create/bind timing metadata for the given type.
*
* Note that this tracks running averages of create/bind time across all RecyclerViews
* (and, indirectly, Adapters) that use this pool.
*
* 1) This enables us to track average create and bind times across multiple adapters. Even
* though create (and especially bind) may behave differently for different Adapter
* subclasses, sharing the pool is a strong signal that they'll perform similarly, per type.
*
* 2) If {@link #willBindInTime(int, long, long)} returns false for one view, it will return
* false for all other views of its type for the same deadline. This prevents items
* constructed by {@link GapWorker} prefetch from being bound to a lower priority prefetch.
*/
static class ScrapData {
final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
int mMaxScrap = DEFAULT_MAX_SCRAP;
long mCreateRunningAverageNs = 0;
long mBindRunningAverageNs = 0;
}
SparseArray<ScrapData> mScrap = new SparseArray<>();
private int mAttachCount = 0;
...省略
}
現(xiàn)在,讓我們解決將 ViewHolders 扔入池中的時(shí)機(jī)問(wèn)題。 有5種情況:
- 在滾動(dòng)過(guò)程中,視圖超出了 RecyclerView 的范圍。
- 數(shù)據(jù)已更改,因此視圖不再可見(jiàn)。 消失動(dòng)畫(huà)結(jié)束時(shí),會(huì)添加到池中。
- 視圖緩存中的項(xiàng)目已更新或刪除。
- 在搜索 ViewHolder 時(shí),在 scrap 或 mCachedViews 中找到了我們想要的位置,但由于視圖類(lèi)型或 ID 錯(cuò)誤(如果適配器具有穩(wěn)定的 ID ),結(jié)果證明不合適。
- LayoutManager 在布局前添加了一個(gè)視圖,但未在布局后添加該視圖。
前兩種情況非常明顯。 但是,要注意的一件事是,第2種情況不僅通過(guò)刪除有問(wèn)題的項(xiàng)目來(lái)觸發(fā),而且還可以通過(guò)例如插入其他項(xiàng)目來(lái)觸發(fā),從而將給定項(xiàng)目推出了界限。
最后說(shuō)下:緩存優(yōu)化
第一種優(yōu)化方法:
進(jìn)入 RecyclerPool 的 ViewHolder 會(huì)被重置,會(huì)從新執(zhí)行 bindViewHolder,所以從效率上來(lái)講,很費(fèi)性能。所以為了避免進(jìn)入這一層緩存,可以在在第三層自定義緩存自己實(shí)現(xiàn),也就是自定義 mViewCacheExtension 。在這里自己維護(hù)一個(gè) viewType 對(duì)應(yīng) View 的 SparseArray 。這樣可以避免因?yàn)槎喾N type 導(dǎo)致的 holder 重建。
/**
* ViewCacheExtension is a helper class to provide an additional layer of view caching that can
* be controlled by the developer.
* <p>
* When {@link Recycler#getViewForPosition(int)} is called, Recycler checks attached scrap and
* first level cache to find a matching View. If it cannot find a suitable View, Recycler will
* call the {@link #getViewForPositionAndType(Recycler, int, int)} before checking
* {@link RecycledViewPool}.
* <p>
* Note that, Recycler never sends Views to this method to be cached. It is developers
* responsibility to decide whether they want to keep their Views in this custom cache or let
* the default recycling policy handle it.
*/
public abstract static class ViewCacheExtension {
/**
* Returns a View that can be binded to the given Adapter position.
* <p>
* This method should <b>not</b> create a new View. Instead, it is expected to return
* an already created View that can be re-used for the given type and position.
* If the View is marked as ignored, it should first call
* {@link LayoutManager#stopIgnoringView(View)} before returning the View.
* <p>
* RecyclerView will re-bind the returned View to the position if necessary.
*
* @param recycler The Recycler that can be used to bind the View
* @param position The adapter position
* @param type The type of the View, defined by adapter
* @return A View that is bound to the given position or NULL if there is no View to re-use
* @see LayoutManager#ignoreView(View)
*/
@Nullable
public abstract View getViewForPositionAndType(@NonNull Recycler recycler, int position,
int type);
}
注意 getViewForPositionAndType 返回的是 view 而不是 ViewHolder,然后會(huì)通過(guò)view 的 layoutParams 拿到 ViewHolder。
例如可以這么寫(xiě):
SparseArray<View> specials = new SparseArray<>();
...
recyclerView.getRecycledViewPool().setMaxRecycledViews(SPECIAL, 0);
recyclerView.setViewCacheExtension(new RecyclerView.ViewCacheExtension() {
@Override
public View getViewForPositionAndType(RecyclerView.Recycler recycler,
int position, int type) {
return type == SPECIAL ? specials.get(position) : null;
}
});
...
class SpecialViewHolder extends RecyclerView.ViewHolder {
...
public void bindTo(int position) {
...
specials.put(position, itemView);
}
}
第二種優(yōu)化方法:
可以增大 mCachedViews 的緩存數(shù)量,改成你需要的量。