聊聊android列表檢視的快取設計

okgays發表於2019-01-20

我們都知道在手機應用中顯示列表資料是最常見的一種使用場景,比如新聞、微博、朋友圈等,但是由於移動裝置的效能有限(尤其是記憶體),當我們在繪製列表檢視時不可能將成百上千條資料一下子全部繪製到介面上,否則在低配手機上必然會引起應用卡頓甚至OOM,從而導致應用體驗很差。在這種情況下我們該如何對應用進行優化呢?android中提供了listview和recyclerview兩個列表檢視控制元件來支援大量資料在介面上的高效顯示,他們的核心思想都是通過快取來優化介面處理過程中的效能問題。那麼接下來我們就簡單分析下listview和recyclerview的快取設計。

為什麼需要快取

前面提到移動裝置存在效能瓶頸,假設我們在一個頁面上要載入顯示1000條資料,如果直接new 1000個view例項然後顯示這種方式在低配手機上很可能會造成介面卡頓甚至OOM,而且由於手機的螢幕尺寸有限而且一般都比較小,使用者在使用過程中只能同看到螢幕內的幾個view,而其餘的不可見的那些view,對於使用者來說其實是“無用”的,那麼view這裡就是我們可以優化的一個點了。我們可以根據手機螢幕內最多可容納的view個數來建立很少的view例項用於顯示在介面上,然後再使用一個小容量的快取來儲存不可見的view例項方便複用,這樣我們就可以大大的節省應用佔用的記憶體空間,從而優化使用者的體驗。當然這種優化方式也是基於使用者的特殊使用場景,如果換一個使用場景(假設螢幕無限大,當然這種場景實際是不存在的),那麼這種方式其實就無效了,其實想說的就是,沒有一種優化方案是萬能的,每一種優化都只有在基於特定場景下才能發揮最大的作用。

基於快取的android列表檢視

android提供了listview和recyclerview來展示列表檢視,他們都利用了之前提到的快取設計思想來優化大資料量的載入和顯示。listview在較早的android版本中就出現了,其使用了二級快取來避免建立不必要的view例項,當然快取設計的加入在改善了程式執行效率的同時也勢必會增加程式邏輯處理的複雜度,但是相比效能的提升這種改造成本是值得的。recyclerview是在android5.0版本之後出現的,其採用了四級快取設計,通常我們使用的都只有三級。相比listview,其對效能的優化粒度更細,因此保證了效率更高,加之其設計和使用上更加靈活,所以目前大部分開發者選擇recyclerview來實現列表資料和展示。接來下分別對這兩個控制元件的快取設計進行分析,本文僅僅描述快取設計思路不涉及原始碼分析,想要深入瞭解請查閱末尾的參考文件。

ListView的快取設計

聊聊android列表檢視的快取設計
通常為了進一步提高效率,我們會在ListView的adapter內部定義一個ViewHolder,這個ViewHolder跟RecyclerView的ViewHolder有點類似。其內部會儲存每個ItemView的childView,這樣我們在使用複用的ItemView(其實也就是ConvertView)重新整理資料時,不用重新呼叫findViewById來定位childView了,這樣可以提高執行效率,因為findViewById也是會按照廣度優先遍歷ItemView下的子結點,如果itemView下的層級結構比較深,開銷也不可忽視。

RecyclerView的快取設計

聊聊android列表檢視的快取設計

ListView和RecyclerView對比

通過上面兩張圖我們可以看出,ListView和RecyclerView的快取設計都包含了create和bind的過程,當快取沒有命中時通過create構建新的物件,當快取命中時通過bind來複用物件。 在設計上,RecyclerView由於將佈局(LayoutManager)、分割線(ItemDecoration)和動畫(ItemAnimator)分離出來使得其對列表檢視的控制要比ListView更加靈活。

在效率上,RecyclerView相比ListView要更加高效體現在其對快取的控制上更加精細,主要體現在以下兩點:

  • 減少bind方法的呼叫頻次。當檢視滾動,快取命中的情況下,ListView會將快取中UI繫結的資料清空並重新初始化,而RecyclerView將快取細分為兩種,一種是mAttachedScrap,另一種是RecyclerViewPool。首先匹配mAttachedScrap,當mAttachedScrap中存在時直接使用其UI以及UI繫結的資料,不用重新初始化。這種通過增加少量記憶體開銷從而減少bind方法的呼叫的方式,可以進一步提供檢視的載入和顯示效率。此外,RecyclerView還提供了區域性重新整理的方式來進一步減少了bind方法的呼叫頻次,從而進一步優化了效能。
  • 多個RecyclerView之間的快取共享,RecyclerView的RecyclerViewPool可以由開發者自己指定,靈活控制快取的容量大小,這在某些場景下,比如使用viewpager來實現多個類似列表頁面的展示,是非常有用的,我們可以將多個頁面的RecyclerViewPool進行復用從而進一步降低快取的記憶體開銷。

在使用上,四級快取以及控制上的靈活設計,導致RecyclerView的實現比ListView複雜了許多,使用門檻上也更高了一些。所以,當我們在實現列表檢視時需要根據實際的業務場景來進行選擇。如果是一些比較簡單的型別單一的靜態列表頁,使用ListView會更方便,程式碼量更少,當然如果你對RecyclerView封裝的比較好,也可以做到使用很少的程式碼就能滿足需求。如果是型別複雜或者需要頻繁動態更新的列表頁,還是推薦RecyclerView來實現。

總結

不論是ListView還是RecyclerView,他們的快取設計思想都值得我們借鑑和學習。從預載入到懶載入,從全域性重新整理到區域性重新整理,這種時間換空間和空間換時間的優化思路不僅可以用在列表檢視中,也可以用在其他的場景中。盤點一下我們在應用開發中的各種業務場景,朝著這個方向,相信你總能找出優化的點來。

參考文件

Android ListView 與 RecyclerView 對比淺析--快取機制

視覺化 ListView 快取機制,手摸手帶你打通任督二脈

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

RecyclerView剖析

相關文章