Android ListView與RecyclerView對比淺析--緩存機制

一,背景
RecyclerView是谷歌官方出的一個用于大量數據展示的新控件,可以用來代替傳統的ListView,更加強大和靈活。
最近,自己負責的業務,也遇到這樣的一個問題,關于是否要將ListView替換為RecyclerView?
秉承著實事求是的作風,弄清楚RecyclerView是否有足夠的吸引力替換掉ListView,我從性能這一角度出發,研究RecyclerView和ListView二者的緩存機制,并得到了一些較有益的”結論”,待我慢慢道來。
同時也希望能通過本文,讓大家快速了解RecyclerView與ListView在緩存機制上的一些區別,在使用上也更加得心應手吧。
PS:相關知識: ListView與RecyclerView緩存機制原理大致相似,如下圖所示:



過程中,離屏的ItemView即被回收至緩存,入屏的ItemView則會優先從緩存中獲取,只是ListView與RecyclerView的實現細節有差異.(這只是緩存使用的其中一個場景,還有如刷新等)
PPS:本文不貼出詳細代碼,結合源碼食用更佳!
二. 正文
2.1 緩存機制對比

  1. 層級不同:
    RecyclerView比ListView多兩級緩存,支持多個離ItemView緩存,支持開發者自定義緩存處理邏輯,支持所有RecyclerView共用同一個RecyclerViewPool(緩存池)。
    具體來說: ListView(兩級緩存):



    RecyclerView(四級緩存):



    ListView和RecyclerView緩存機制基本一致:
    1). mActiveViews和mAttachedScrap功能相似,意義在于快速重用屏幕上可見的列表項ItemView,而不需要重新createView和bindView;
    2). mScrapView和mCachedViews + mReyclerViewPool功能相似,意義在于緩存離開屏幕的ItemView,目的是讓即將進入屏幕的ItemView重用.
    3). RecyclerView的優勢在于a.mCacheViews的使用,可以做到屏幕外的列表項ItemView進入屏幕內時也無須bindView快速重用;b.mRecyclerPool可以供多個RecyclerView共同使用,在特定場景下,如viewpaper+多個列表頁下有優勢.客觀來說,RecyclerView在特定場景下對ListView的緩存機制做了補強和完善。
  2. 緩存不同:
    1). RecyclerView緩存RecyclerView.ViewHolder,抽象可理解為: View + ViewHolder(避免每次createView時調用findViewById) + flag(標識狀態); 2). ListView緩存View。
    緩存不同,二者在緩存的使用上也略有差別,具體來說: ListView獲取緩存的流程:



    RecyclerView獲取緩存的流程:



    1). RecyclerView中mCacheViews(屏幕外)獲取緩存時,是通過匹配pos獲取目標位置的緩存,這樣做的好處是,當數據源數據不變的情況下,無須重新bindView:

    而同樣是離屏緩存,ListView從mScrapViews根據pos獲取相應的緩存,但是并沒有直接使用,而是重新getView(即必定會重新bindView),相關代碼如下:
//AbsListView源碼:line2345
//通過匹配pos從mScrapView中獲取緩存
final View scrapView = mRecycler.getScrapView(position);
//無論是否成功都直接調用getView,導致必定會調用createView
final View child = mAdapter.getView(position, scrapView, this);
if (scrapView != null) {
    if (child != scrapView) {
        mRecycler.addScrapView(scrapView, position);
    } else {
        ...
    }
}

2). ListView中通過pos獲取的是view,即pos–>view; RecyclerView中通過pos獲取的是viewholder,即pos –> (view,viewHolder,flag); 從流程圖中可以看出,標志flag的作用是判斷view是否需要重新bindView,這也是RecyclerView實現局部刷新的一個核心.
2.2 局部刷新
由上文可知,RecyclerView的緩存機制確實更加完善,但還不算質的變化,RecyclerView更大的亮點在于提供了局部刷新的接口,通過局部刷新,就能避免調用許多無用的bindView.



(RecyclerView和ListView添加,移除Item效果對比)
結合RecyclerView的緩存機制,看看局部刷新是如何實現的: 以RecyclerView中notifyItemRemoved(1)為例,最終會調用requestLayout(),使整個RecyclerView重新繪制,過程為: onMeasure()–>onLayout()–>onDraw()
其中,onLayout()為重點,分為三步: 1. dispathLayoutStep1():記錄RecyclerView刷新前列表項ItemView的各種信息,如Top,Left,Bottom,Right,用于動畫的相關計算; 2. dispathLayoutStep2():真正測量布局大小,位置,核心函數為layoutChildren(); 3. dispathLayoutStep3():計算布局前后各個ItemView的狀態,如Remove,Add,Move,Update等,如有必要執行相應的動畫.
其中,layoutChildren()流程圖:




當調用notifyItemRemoved時,會對屏幕內ItemView做預處理,修改ItemView相應的pos以及flag(流程圖中紅色部分):

當調用fill()中RecyclerView.getViewForPosition(pos)時,RecyclerView通過對pos和flag的預處理,使得bindview只調用一次.

需要指出,ListView和RecyclerView最大的區別在于數據源改變時的緩存的處理邏輯,ListView是”一鍋端”,將所有的mActiveViews都移入了二級緩存mScrapViews,而RecyclerView則是更加靈活地對每個View修改標志位,區分是否重新bindView。
三.結論
在一些場景下,如界面初始化,滑動等,ListView和RecyclerView都能很好地工作,兩者并沒有很大的差異:

文章的開頭便拋出了這樣一個問題,微信Android客戶端卡券模塊,大部分UI都是以列表頁的形式展示,實現方式為ListView,是否有必要將其替換成RecyclerView呢?


答案是否定的,從性能上看,RecyclerView并沒有帶來顯著的提升,不需要頻繁更新,暫不支持用動畫,意味著RecyclerView優勢也不太明顯,沒有太大的吸引力,ListView已經能很好地滿足業務需求。
數據源頻繁更新的場景,如彈幕:http://www.lxweimin.com/p/2232a63442d6等RecyclerView的優勢會非常明顯;

進一步來講,結論是: 列表頁展示界面,需要支持動畫,或者頻繁更新,局部刷新,建議使用RecyclerView,更加強大完善,易擴展;其它情況(如微信卡包列表頁)兩者都OK,但ListView在使用上會更加方便,快捷。
Ps:僅從一個角度做了對比,盲人摸象,有誤跪求指正。
四.參考資料

  1. ListView
    a. android-23源碼 b. Android ListView工作原理解析,帶你從源碼的角度徹底理解:http://blog.csdn.net/guolin_blog/article/details/44996879 c. android自己動手寫ListView學習其原理:http://blog.csdn.net/androiddevelop/article/details/8734255
  2. RecyclerView
    a. RecyclerView-v7-23.4.0源碼 b. RecyclerView剖析:http://blog.csdn.net/qq_23012315/article/details/50807224 c. RecyclerView剖析:http://blog.csdn.net/qq_23012315/article/details/51096696
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容