Android架構師之深入理解RecyclerView復用和緩存機制詳解
本文轉載自微信公眾號「Android開發編程」,作者Android開發編程。轉載本文請聯系Android開發編程公眾號。
前言
學習源碼,研究源碼編程思想,是程序開發者進階的必經之路
大家都知道RecyclerView有回收復用機制,那么回收復用機制是如何作用的?
今天我們就用源碼來講解,一起學習
一、Recycler介紹
RecyclerView是通過內部類Recycler管理的緩存,那么Recycler中緩存的是什么?我們知道RecyclerView在存在大量數據時依然可以滑動的如絲滑般順暢,而RecyclerView本身是一個ViewGroup,那么滑動時避免不了添加或移除子View(子View通過RecyclerView#Adapter中的onCreateViewHolder創建),如果每次使用子View都要去重新創建,肯定會影響滑動的流 暢性,所以RecyclerView通過Recycler來緩存的是ViewHolder(內部包含子View),這樣在滑動時可以復用子View,某些條件下還可以復用子View綁定的數據。所以本質上緩存是為了減少重復繪制View和綁定數據的時間,從而提高了滑動時的性能
- 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;
Recycler緩存ViewHolder對象有4個等級,優先級從高到底依次為:
1、ArrayList mAttachedScrap --- 緩存屏幕中可見范圍的ViewHolder
2、ArrayList mCachedViews ---- 緩存滑動時即將與RecyclerView分離的ViewHolder,按子View的position或id緩存,默認最多存放2個
3、ViewCacheExtension mViewCacheExtension --- 開發者自行實現的緩存
4、RecycledViewPool mRecyclerPool --- ViewHolder緩存池,本質上是一個SparseArray,其中key是ViewType(int類型),value存放的是 ArrayList< ViewHolder>,默認每個ArrayList中最多存放5個ViewHolder。
二、緩存機制分析詳解
RecyclerView滑動時會觸發onTouchEvent#onMove,回收及復用ViewHolder在這里就會開始。我們知道設置RecyclerView時需要設置LayoutManager,LayoutManager負責RecyclerView的布局,包含對ItemView的獲取與復用。以LinearLayoutManager為例,當RecyclerView重新布局時會依次執行下面幾個方法:
onLayoutChildren():對RecyclerView進行布局的入口方法
fill(): 負責對剩余空間不斷地填充,調用的方法是layoutChunk()
layoutChunk():負責填充View,該View最終是通過在緩存類Recycler中找到合適的View的
上述的整個調用鏈:onLayoutChildren()->fill()->layoutChunk()->next()->getViewForPosition(),getViewForPosition()即是是從RecyclerView的回收機制實現類Recycler中獲取合適的View,
下面主要就來從看這個Recycler#getViewForPosition()的實現。
- @NonNull
- public View getViewForPosition(int position) {
- return getViewForPosition(position, false);
- }
- View getViewForPosition(int position, boolean dryRun) {
- return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
- }
他們都會執行tryGetViewHolderForPositionByDeadline函數,繼續跟進去:
//根據傳入的position獲取ViewHolder
- ViewHolder tryGetViewHolderForPositionByDeadline(int position,
- boolean dryRun, long deadlineNs) {
- ...省略
- boolean fromScrapOrHiddenOrCache = false;
- ViewHolder holder = null;
- //預布局 屬于特殊情況 從mChangedScrap中獲取ViewHolder
- if (mState.isPreLayout()) {
- holder = getChangedScrapViewForPosition(position);
- fromScrapOrHiddenOrCache = holder != null;
- }
- if (holder == null) {
- //1、嘗試從mAttachedScrap中獲取ViewHolder,此時獲取的是屏幕中可見范圍中的ViewHolder
- //2、mAttachedScrap緩存中沒有的話,繼續從mCachedViews嘗試獲取ViewHolder
- holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
- ...省略
- }
- if (holder == null) {
- final int offsetPosition = mAdapterHelper.findPositionOffset(position);
- ...省略
- final int type = mAdapter.getItemViewType(offsetPosition);
- //如果Adapter中聲明了Id,嘗試從id中獲取,這里不屬于緩存
- if (mAdapter.hasStableIds()) {
- holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
- type, dryRun);
- }
- if (holder == null && mViewCacheExtension != null) {
- 3、從自定義緩存mViewCacheExtension中嘗試獲取ViewHolder,該緩存需要開發者實現
- final View view = mViewCacheExtension
- .getViewForPositionAndType(this, position, type);
- if (view != null) {
- holder = getChildViewHolder(view);
- }
- }
- if (holder == null) { // fallback to pool
- //4、從緩存池mRecyclerPool中嘗試獲取ViewHolder
- holder = getRecycledViewPool().getRecycledView(type);
- if (holder != null) {
- //如果獲取成功,會重置ViewHolder狀態,所以需要重新執行Adapter#onBindViewHolder綁定數據
- holder.resetInternal();
- if (FORCE_INVALIDATE_DISPLAY_LIST) {
- invalidateDisplayListInt(holder);
- }
- }
- }
- if (holder == null) {
- ...省略
- //5、若以上緩存中都沒有找到對應的ViewHolder,最終會調用Adapter中的onCreateViewHolder創建一個
- holder = mAdapter.createViewHolder(RecyclerView.this, type);
- }
- }
- boolean bound = false;
- if (mState.isPreLayout() && holder.isBound()) {
- holder.mPreLayoutPosition = position;
- } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
- final int offsetPosition = mAdapterHelper.findPositionOffset(position);
- //6、如果需要綁定數據,會調用Adapter#onBindViewHolder來綁定數據
- bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
- }
- ...省略
- return holder;
- }
通過mAttachedScrap、mCachedViews及mViewCacheExtension獲取的ViewHolder不需要重新創建布局及綁定數據;通過緩存池mRecyclerPool獲取的ViewHolder不需要重新創建布局,但是需要重新綁定數據;如果上述緩存中都沒有獲取到目標ViewHolder,那么就會回調Adapter#onCreateViewHolder創建布局,以及回調Adapter#onBindViewHolder來綁定數據
總結
RecyclerView 滑動場景下的回收復用涉及到的結構體兩個:
- mCachedViews 和 RecyclerViewPool
- mCachedViews 優先級高于 RecyclerViewPool,回收時,最新的 ViewHolder 都是往 mCachedViews 里放,如果它滿了,那就移出一個扔到 ViewPool 里好空出位置來緩存最新的 ViewHolder。
- 復用時,也是先到 mCachedViews 里找 ViewHolder,但需要各種匹配條件,概括一下就是只有原來位置的卡位可以復用存在 mCachedViews 里的 ViewHolder,如果 mCachedViews 里沒有,那么才去 ViewPool 里找。
- 在 ViewPool 里的 ViewHolder 都是跟全新的 ViewHolder 一樣,只要 type 一樣,有找到,就可以拿出來復用,重新綁定下數據即可。