1. 程式人生 > >Android ListView 與 RecyclerView 對比淺析--快取機制[轉]

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

轉自https://mp.weixin.qq.com/s?__biz=MzA3NTYzODYzMg==&mid=2653578065&idx=2&sn=25e64a8bb7b5934cf0ce2e49549a80d6&chksm=84b3b156b3c43840061c28869671da915a25cf3be54891f040a3532e1bb17f9d32e244b79e3f&mpshare=1&scene=1&srcid=0626Dl0bjG3664qJlKb7RSlq#rd

一,背景

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。

三.結論

  1. 在一些場景下,如介面初始化,滑動等,ListView和RecyclerView都能很好地工作,兩者並沒有很大的差異:

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

答案是否定的,從效能上看,RecyclerView並沒有帶來顯著的提升,不需要頻繁更新,暫不支援用動畫,意味著RecyclerView優勢也不太明顯,沒有太大的吸引力,ListView已經能很好地滿足業務需求。

  1. 資料來源頻繁更新的場景,如彈幕:http://www.jianshu.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