Android ListView與RecyclerView對比淺析

前言

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)化也是必要的。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末身笤,一起剝皮案震驚了整個濱河市豹悬,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌液荸,老刑警劉巖瞻佛,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異娇钱,居然都是意外死亡伤柄,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門文搂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來适刀,“玉大人,你說我怎么就攤上這事煤蹭”屎恚” “怎么了取视?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長常挚。 經(jīng)常有香客問我作谭,道長,這世上最難降的妖魔是什么奄毡? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任折欠,我火速辦了婚禮,結(jié)果婚禮上秧倾,老公的妹妹穿的比我還像新娘怨酝。我一直安慰自己,他們只是感情好那先,可當(dāng)我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布农猬。 她就那樣靜靜地躺著,像睡著了一般售淡。 火紅的嫁衣襯著肌膚如雪斤葱。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天揖闸,我揣著相機與錄音揍堕,去河邊找鬼。 笑死汤纸,一個胖子當(dāng)著我的面吹牛衩茸,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播贮泞,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼楞慈,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了啃擦?” 一聲冷哼從身側(cè)響起囊蓝,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎令蛉,沒想到半個月后聚霜,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡珠叔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年蝎宇,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片祷安。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡夫啊,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出辆憔,到底是詐尸還是另有隱情撇眯,我是刑警寧澤报嵌,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站熊榛,受9級特大地震影響锚国,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜玄坦,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一血筑、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧煎楣,春花似錦豺总、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至困曙,卻和暖如春表伦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背慷丽。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工蹦哼, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人要糊。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓纲熏,卻偏偏與公主長得像,于是被迫代替她去往敵國和親锄俄。 傳聞我的和親對象是個殘疾皇子赤套,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容