前言
RecyclerView是谷歌官方出的一個用于大量數(shù)據(jù)展示的新控件悦冀,可以用來代替?zhèn)鹘y(tǒng)的ListView下愈,更加強大和靈活。
弄清楚RecyclerView是否有足夠的吸引力替換掉ListView,我從性能這一角度出發(fā)荐捻,研究RecyclerView和ListView二者的緩存機制,并得到了一些較有益的”結(jié)論”始藕,待我慢慢道來绽榛。
同時也希望能通過本文,讓大家快速了解RecyclerView與ListView在緩存機制上的一些區(qū)別胳徽,在使用上也更加得心應(yīng)手吧积锅。
ListView與RecyclerView緩存機制原理大致相似,如下圖所示:
過程中膜廊,離屏的ItemView即被回收至緩存乏沸,入屏的ItemView則會優(yōu)先從緩存中獲取,只是ListView與RecyclerView的實現(xiàn)細(xì)節(jié)有差異.(這只是緩存使用的其中一個場景爪瓜,還有如刷新等)
比較
緩存機制對比
層級不同:
RecyclerView比ListView多兩級緩存蹬跃,支持多個離ItemView緩存,支持開發(fā)者自定義緩存處理邏輯,支持所有RecyclerView共用同一個RecyclerViewPool(緩存池)蝶缀。
具體來說:
ListView(兩級緩存):
RecyclerView(四級緩存):
ListView和RecyclerView緩存機制基本一致:
mActiveViews和mAttachedScrap功能相似丹喻,意義在于快速重用屏幕上可見的列表項ItemView,而不需要重新createView和bindView翁都;
mScrapView和mCachedViews + mReyclerViewPool功能相似碍论,意義在于緩存離開屏幕的ItemView,目的是讓即將進(jìn)入屏幕的ItemView重用.
RecyclerView的優(yōu)勢在于a.mCacheViews的使用柄慰,可以做到屏幕外的列表項ItemView進(jìn)入屏幕內(nèi)時也無須bindView快速重用鳍悠;b.mRecyclerPool可以供多個RecyclerView共同使用,在特定場景下坐搔,如viewpaper+多個列表頁下有優(yōu)勢.客觀來說藏研,RecyclerView在特定場景下對ListView的緩存機制做了補強和完善。
緩存不同:
RecyclerView緩存RecyclerView.ViewHolder概行,抽象可理解為:
View + ViewHolder(避免每次createView時調(diào)用findViewById) + flag(標(biāo)識狀態(tài))蠢挡;ListView緩存View。
緩存不同凳忙,二者在緩存的使用上也略有差別业踏,具體來說:
ListView獲取緩存的流程:
RecyclerView獲取緩存的流程:
RecyclerView中mCacheViews(屏幕外)獲取緩存時,是通過匹配pos獲取目標(biāo)位置的緩存涧卵,這樣做的好處是勤家,當(dāng)數(shù)據(jù)源數(shù)據(jù)不變的情況下,無須重新bindView艺演,而同樣是離屏緩存却紧,ListView從mScrapViews根據(jù)pos獲取相應(yīng)的緩存,但是并沒有直接使用胎撤,而是重新getView(即必定會重新bindView)晓殊,相關(guān)代碼如下:
//AbsListView源碼:line2345
//通過匹配pos從mScrapView中獲取緩存
final View scrapView = mRecycler.getScrapView(position);
//無論是否成功都直接調(diào)用getView,導(dǎo)致必定會調(diào)用createView
final View child = mAdapter.getView(position, scrapView, this);
if (scrapView != null) {
if (child != scrapView) {
mRecycler.addScrapView(scrapView, position);
} else {
...
}
}
ListView中通過pos獲取的是view,即pos-->view伤提;
RecyclerView中通過pos獲取的是viewholder巫俺,即pos --> (view,viewHolder肿男,flag)介汹;
從流程圖中可以看出,標(biāo)志flag的作用是判斷view是否需要重新bindView舶沛,這也是RecyclerView實現(xiàn)局部刷新的一個核心嘹承。
局部刷新
由上文可知,RecyclerView的緩存機制確實更加完善如庭,但還不算質(zhì)的變化叹卷,RecyclerView更大的亮點在于提供了局部刷新的接口,通過局部刷新,就能避免調(diào)用許多無用的bindView骤竹。
結(jié)合RecyclerView的緩存機制帝牡,看看局部刷新是如何實現(xiàn)的:
以RecyclerView中notifyItemRemoved(1)為例,最終會調(diào)用requestLayout()蒙揣,使整個RecyclerView重新繪制靶溜,過程為:
onMeasure()-->onLayout()-->onDraw()
其中,onLayout()為重點懒震,分為三步:
- dispathLayoutStep1():記錄RecyclerView刷新前列表項ItemView的各種信息罩息,如Top,Left,Bottom,Right,用于動畫的相關(guān)計算个扰;
- dispathLayoutStep2():真正測量布局大小扣汪,位置,核心函數(shù)為layoutChildren()锨匆;
- dispathLayoutStep3():計算布局前后各個ItemView的狀態(tài),如Remove冬筒,Add恐锣,Move,Update等舞痰,如有必要執(zhí)行相應(yīng)的動畫.
其中土榴,layoutChildren()流程圖:
當(dāng)調(diào)用notifyItemRemoved時,會對屏幕內(nèi)ItemView做預(yù)處理响牛,修改ItemView相應(yīng)的pos以及flag(流程圖中紅色部分):
當(dāng)調(diào)用fill()中RecyclerView.getViewForPosition(pos)時玷禽,RecyclerView通過對pos和flag的預(yù)處理,使得bindview只調(diào)用一次.
需要指出呀打,ListView和RecyclerView最大的區(qū)別在于數(shù)據(jù)源改變時的緩存的處理邏輯矢赁,ListView是"一鍋端",將所有的mActiveViews都移入了二級緩存mScrapViews贬丛,而RecyclerView則是更加靈活地對每個View修改標(biāo)志位撩银,區(qū)分是否重新bindView。
RecyclerView 優(yōu)點
RecyclerView 相比 ListView 在基礎(chǔ)使用上的區(qū)別主要有如下幾點:
- ViewHolder 的編寫規(guī)范化了
- RecyclerView 復(fù)用 Item 的工作 Google 全幫你搞定豺憔,不再需要像 ListView 那樣自己調(diào)用 setTag
- RecyclerView 需要多出一步 LayoutManager 的設(shè)置工作
更加方便的實現(xiàn)自定義功能
Android 優(yōu)雅的為RecyclerView添加HeaderView和FooterView
Android 默認(rèn)提供的 RecyclerView 就能支持 線性布局额获、網(wǎng)格布局、瀑布流布局 三種(這里我們暫且不提代碼細(xì)節(jié)恭应,后文再說)抄邀,而且同時還能夠控制橫向還是縱向滾動。怎樣昼榛,從效果上足以碾壓 ListView 有木有境肾。
橫向滾動的ListView開源控件是不是可以不用再找了?對,你沒看錯准夷!
瀑布流效果的開源控件是不是可以不用再找了钥飞?對,你沒看錯衫嵌!
連橫向滾動的GridView都不用找了读宙!對,你沒看錯楔绞!
而 LayoutManager 只是一個抽象類而已结闸,系統(tǒng)已經(jīng)為我們提供了三個相關(guān)的實現(xiàn)類:
- LinearLayoutManager(線性布局效果)
- GridLayoutManager(網(wǎng)格布局效果)
- StaggeredGridLayoutManager(瀑布流布局效果)
RecyclerView 基礎(chǔ)使用關(guān)鍵點同樣有兩點:
- 繼承重寫 RecyclerView.Adapter 和 RecyclerView.ViewHolder;
- 設(shè)置布局管理器酒朵,控制布局效果
系統(tǒng)也為我們提供了兩個默認(rèn)的動畫實現(xiàn):SimpleItemAnimator 和 DefaultItemAnimator桦锄。而 RecyclerView 在不手動調(diào)用 setItemAnimator 的情況下,則默認(rèn)用了內(nèi)置的 DefaultItemAnimator 蔫耽。
RecyclerView緩存機制總結(jié)
主要靠三個內(nèi)部類來完成结耀,Recycler,ViewCacheExtension匙铡,RecyclerViewPool:
- 首先通過 recycler.getViewForPosition()方法图甜,該方法返回ViewHolder對象,通過源碼可以知道鳖眼,該方法會檢查mAttachedScrap和一級緩存列表mCachedViews黑毅,如果有則返回ViewHolder進(jìn)行復(fù)用。
- 然后調(diào)用ViewCacheExtension.getViewForPositionAndType()方法钦讳,注意這個方法是抽象方法矿瘦,需要開發(fā)者進(jìn)行重寫。
- 最后檢查RecyclerViewPool是否有ViewHolder愿卒。
注意:上述的三個步驟中缚去,只要有一個返回了ViewHolder,就不會在進(jìn)行后邊的步驟了掘猿。
最后:緩存的數(shù)量:默認(rèn)的一級緩存中病游,mCachedViews中可以緩存的ViewHolder的個數(shù)是2;默認(rèn)的緩存池中的緩存數(shù)量是 5稠通;所以在緩存時衬衬,會先檢測一級緩存是否滿了,如果沒滿就add進(jìn)去改橘,如果滿了就加入到三級緩存Recyclerpool
ListView 優(yōu)化
如果頁面不是復(fù)雜滋尉,也不是需要太多功能,只需要簡單的列表功能飞主,那就可以繼續(xù)使用ListView, 畢竟實現(xiàn)起來比RecyclerView簡單些狮惜。
如何優(yōu)化ListView的性能:
- 盡最大可能避免GC
- 滑動的時候不加載圖片
- 將ListView的scrollingCache和animateCache設(shè)置為false
- convertView重用高诺;
利用好 convertView 來重用 View,切忌每次 getView() 都新建碾篡。ListView 的核心原理就是重用 View虱而,如果重用 view 不改變寬高,重用View可以減少重新分配緩存造成的內(nèi)存頻繁分配/回收; - ViewHolder優(yōu)化开泽;
使用ViewHolder的原因是findViewById方法耗時較大牡拇,如果控件個數(shù)過多,會嚴(yán)重影響性能穆律,而使用ViewHolder主要是為了可以省去這個時間惠呼。通過setTag,getTag直接獲取View峦耘。 - 圖片加載優(yōu)化
如果ListView需要加載顯示網(wǎng)絡(luò)圖片剔蹋,我們盡量不要在ListView滑動的時候加載圖片,那樣會使ListView變得卡頓辅髓,所以我們需要在監(jiān)聽器里面監(jiān)聽ListView的狀態(tài)泣崩,如果ListView滑動(SCROLL_STATE_TOUCH_SCROLL)或者被猛滑(SCROLL_STATE_FLING)的時候,停止加載圖片洛口,如果沒有滑動(SCROLL_STATE_IDLE)律想,則開始加載圖片。 - onClickListener處理(通過接口回傳)
- 減少Item View的布局層級
這是所有l(wèi)ayout都必須遵循的绍弟,布局層級過深會直接導(dǎo)致View的測量與繪制浪費大量的時間 - adapter中的getView方法盡量少使用邏輯
不要在getView方法中做過于復(fù)雜的邏輯,可以想辦法抽離到別的地方著洼, - adapter中的getView方法盡量少做耗時操作
- adapter中的getView方法避免創(chuàng)建大量對象
- 將ListView的scrollingCache和animateCache設(shè)置為false
- 分頁加載數(shù)據(jù)
總結(jié)
是選擇ListView還是RecyclerView 可以根據(jù)項目中的功能來的樟遣,但是對于他們了解之后的優(yōu)化也是必要的。