手摸手第二彈,視覺化 RecyclerView 快取機制

揪克發表於2018-01-15

歡迎關注本人公眾號,掃描下方二維碼或搜尋公眾號 id: mxszgg

手摸手第二彈,視覺化 RecyclerView 快取機制

前言

開題前,筆者還是要說幾句先,依舊和前文一樣,文章內不涉及原始碼講解,預設各位讀者對原始碼有一定的瞭解,撰文的原因也如同前文,因為筆者認為當下在 ListView/RecyclerView 的原始碼講解的文章中,大都是對著原始碼噼裡啪啦,實在有些晦澀難懂,於是筆者想將部分資料視覺化,手摸手帶領讀者去了解一下快取機制的實現,另推薦閱讀騰訊 Bugly 的《Android ListView與RecyclerView對比淺析--快取機制》一文。

1.前文地址:《視覺化 ListView 快取機制,手摸手帶你打通任督二脈》

2.本文專案地址:RecyclerViewVisualization 或直接下載 apk

希望閱讀本文前請先閱讀前文,本文所涉及的一些關鍵字在上文有所提及。

一緩

手摸手開啟 app:

這裡寫圖片描述

RecyclerView 中的一緩 mAttachedScrap 與 ListView 中的一緩 mActiveViews 功能是基本相似的,為了螢幕內 item 快速複用而存在(RecyclerView/ListView 具有兩次 onLayout() 過程,第二次 onLayout() 中直接使用第一次 onLayout() 快取的 View,而不必再建立)。

二緩

實際上,二緩 mCachedViews 加上四緩 RecyclerViewPool 合在一起與 ListView 的二緩 mScrapedViews 意義相同,為了即將給即將入屏的 item 複用而存在。下面來細談下二緩:

  • ArrayList 型別
  • 預設 size 為 2
  • size 可變
  • 複用演算法是從尾部倒序匹配 ViewHolder position 與傳入的 position 是否相等,匹配成功則返回
  • 為了優化上一步,下一個可能出現的 item 將會被置於尾部

二緩是通過 position 來匹配相應的 ViewHolder 的,這裡的 position 指的是 RecyclerView 預測的、可能進入螢幕的 item 的 position,它是由當前螢幕滑動方向和可見的 item 位置來共同決定的。例如:螢幕向下滑動,那麼可能進入螢幕的 item 的 position 就是當前可見第一個 item 的 position - 1;螢幕向上滑動,那麼可能進入螢幕的 item 的 position 就是當前可見的最後一個 item 的 position + 1。這樣說起來可能有些模糊,舉個例子:

這裡寫圖片描述

以上述狀態來說,如果螢幕下滑,那麼預測下一個可能出現在螢幕上的 item 的 position 可能是 4(也就是 Item1『E(layoutPosition:4)』);而如果螢幕上滑,預測的下一個出現在螢幕上的 item 的 position 是 0(也就是 Item『A(layoutPosition:0)』)。然後通過將 position 用於與 mCacheViews 中的 ViewHolder 的 layoutPosition 做比較,如果相同則返回該 ViewHolder。

來看動圖:

1.螢幕上滑:

這裡寫圖片描述

可以看到 target mCacheView position 由 0 變成了 4。與此同時,mCachedViews 將可能出現在螢幕上的 item 的位置從原有的位置調整為 ArrayList 的最後一位。

2.螢幕下滑:

這裡寫圖片描述

螢幕下滑的話,target mCachedView position 由 4 變成了 0。與此同時,mCachedViews 內部也會做相應的調整。

四緩

四緩(RecycledViewPool)性質:

  • 內部維護了一個 SparseArray

  • SparseArray key 為 ViewHolder 的 ViewType,這說明每一套 ViewHolder 都具有自己的快取資料

  • SparseArray value 為 ScrapData 型別,ScrapData 就是關鍵的快取資料了,其資料結構簡略如下:

      static class ScrapData {
          ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
          int mMaxScrap = 5;
          // ...
      }
    複製程式碼

由此可見,針對每一種 ViewHolder,RecycledViewPool 都會維護一個預設大小為 5 的 ArrayList 來用做快取它們,當然,這裡還需要提及的一點是,ArrayList 的預設大小被限制為 5,但是這個值是可以通過 RecycledViewPool#setMaxRecycledViews(viewType, max) 來替換的,比如想換成大一點的 10、20,都是可以的(這也是該資料型別為 ArrayList 而不是陣列的原因之一)。

前面說到——“實際上,二緩 mCachedViews 加上四緩 RecyclerViewPool 合在一起與 ListView 的二緩 mScrapedViews 意義相同,為了即將給即將入屏的 item 複用而存在。”,可能有小夥伴疑惑了,既然意義相同,為何不是隻有二緩就足夠了,還要多一個四緩來更復雜?緣由在於:可以由開發者主動向內填充資料(RecycledViewPool#putRecycledView(ViewHolder),技術上可以實現多個 RecyclerView 共用同一個 RecyclerViewPool(RecyclerView#setRecycledViewPool(RecycledViewPool))。這兩點在筆者看來,是在某種業務場景下選擇 RecyclerView 還是 ListView 的一個重要緣由所在。至於這兩點的實踐,第一點筆者已經新增在 Demo 中了(含彩蛋),第二點筆者就不在此處擴充套件了,各位讀者可以自行新增入 RecyclerViewVisualization Demo 中,相應的資料也都會被展示到螢幕上~

其他

談談 BindView(與 ListView 對比)

一個 View 被完整的展示到螢幕上,應該經過建立 View 和給 View 新增資料(BindView)兩個過程,所以實際上快取機制不僅僅針對於 View 要做快取,最好還能對新增資料的這個過程再優化下,畢竟 setText()、setImage() 也可能是一個耗時操作。RecyclerView 就針對此做了優化,我們知道,ListView 實際上快取的是 View,而 RecyclerView 實際上快取的是 ViewHolder,這就意味著 ListView 雖然可以複用 View,但是給 View 新增資料這個過程就不能複用了,而如果是複用 ViewHolder 的話,不僅複用了 View,同時將給 View 新增資料的這個過程也被“快取”起來了,而 RecyclerView 就是這麼幹的 ——

這裡寫圖片描述

我們可以看到,但凡是被二緩快取起來的 ViewHolder 再被展示到螢幕上,是不會觸發 BindViewHolder 這個過程的。

ps:當然,這得基於資料來源不變,如果資料來源改變,肯定得重新給 View 新增資料。

談談 BindView(區域性重新整理)

RecyclerView 相比於 ListView 還提供了區域性重新整理的介面,這讓 RecyclerView 在效能上又有了一個亮點:

1.區域性重新整理:

這裡寫圖片描述

2.全域性重新整理:

這裡寫圖片描述

可以看到,區域性重新整理能夠只針對改變的 View 進行 bind view,而全域性重新整理會針對被影響到的所有的 View 都進行 bind view。所以,在日常使用中,是不是應該多考慮使用區域性重新整理代替全域性重新整理呢?

相關文章