RecyclerView

RecyclerView的緩存和優(yōu)化

一:RecyclerView緩存的是啥

我們都知道ListView緩存的是ItemView费彼,而RecyclerView緩存的是RecyclerView.ViewHolder,這個(gè)ViewHolder中持有對(duì)應(yīng)的ItemView的所有信息猴仑,比如position、view、width、flag等从绘。

二:RecyclerView的四級(jí)緩存

一級(jí)緩存:屏幕內(nèi)緩存(mAttachedScrap)

屏幕內(nèi)緩存指在屏幕中顯示的ViewHolder吓坚,為了屏幕內(nèi) item 快速復(fù)用而存在,(RecyclerView/ListView具有兩次 onLayout() 過程撵幽,第二次onLayout() 中直接使用第一次 onLayout() 緩存的 View,而不必再創(chuàng)建)礁击。這些ViewHolder會(huì)緩存在mAttachedScrap盐杂、mChangedScrap中。

mChangedScrap 表示數(shù)據(jù)已經(jīng)改變的ViewHolder列表哆窿,需要重新綁定數(shù)據(jù)(調(diào)用onBindViewHolder),該層緩存目的是為了調(diào)用notifyItemChanged(pos)链烈,notifyItemRangeChanged(pos,count)后該位置信息發(fā)生改變的緩存。

mAttachedScrap 表示未與RecyclerView分離的ViewHolder列表挚躯,該層緩存目的是在調(diào)用notfyXxx時(shí)未改變的item测垛,以及影響RecyclerView重新繪制的情況。

二級(jí)緩存:屏幕外緩存(mCachedViews)

用來緩存移除屏幕之外的 ViewHolder秧均,默認(rèn)情況下緩存容量是2食侮,可以通過 setViewCacheSize 方法來改變緩存的容量大小。如果mCachedViews 的容量已滿目胡,根據(jù)FIFO規(guī)則會(huì)優(yōu)先移除舊ViewHolder锯七,把舊ViewHolder移入到緩存池RecycledViewPool 中。mCachedViews中攜帶了原來的ViewHolder的所有數(shù)據(jù)信息誉己,可以直接拿來復(fù)用眉尸。mCachedViews是根據(jù)position 來匹配相應(yīng)的 ViewHolder 的,這里的 position 指的是 RecyclerView 預(yù)測的巨双、可能進(jìn)入屏幕的 item 的 position噪猾,它是由當(dāng)前屏幕滑動(dòng)方向和可見的 item 位置來共同決定的。例如:屏幕向下滑動(dòng)筑累,那么可能進(jìn)入屏幕的 item 的 position 就是當(dāng)前可見第一個(gè) item 的 position - 1袱蜡;屏幕向上滑動(dòng),那么可能進(jìn)入屏幕的 item 的 position 就是當(dāng)前可見的最后一個(gè) item 的 position + 1慢宗。

舉個(gè)栗子:當(dāng)前屏幕內(nèi)第一個(gè)可見的item的position是1坪蚁,用戶進(jìn)行了一個(gè)下拉操作,那么當(dāng)前預(yù)測的position就相當(dāng)于(1-1=0)镜沽,也就是position=0的那個(gè)item要被拉回到屏幕敏晤,此時(shí)RecyclerView就從cache里面找position=0的數(shù)據(jù),如果找到了就拿來直接復(fù)用缅茉。

三級(jí)緩存:自定義緩存(ViewCacheExtension)

給用戶的自定義擴(kuò)展緩存嘴脾,需要用戶自己管理View 的創(chuàng)建和緩存,可通過RecyclerView.setViewCacheExtension()設(shè)置蔬墩。

四級(jí)緩存:緩存池(RecycledViewPool)

ViewHolder 緩存池译打,在mCachedViews中如果緩存已滿的時(shí)候(默認(rèn)最大值為2個(gè))耗拓,先把mCachedViews中舊的ViewHolder 存入到RecyclerViewPool。如果RecyclerViewPool緩存池已滿扶平,就不會(huì)再緩存帆离。從緩存池中取出的ViewHolder 蔬蕊,需要重新調(diào)用onBindViewHolder綁定數(shù)據(jù)结澄。

按照 ViewType 來查找ViewHolder

每個(gè) ViewType 默認(rèn)最多緩存 5 個(gè)

可以多個(gè) RecyclerView 共享RecycledViewPool

為啥要有第四緩: 可以由開發(fā)者主動(dòng)向內(nèi)填充數(shù)據(jù)RecycledViewPool#putRecycledView(ViewHolder),技術(shù)上可以實(shí)現(xiàn)多個(gè) RecyclerView 共用同一個(gè)RecyclerViewPool岸夯。

三:RecyclerView的緩存策略

按四級(jí)緩存的策略查找麻献,沒有找到就創(chuàng)建(如果取到直接丟給rv來展示,如果取不到最終才會(huì)執(zhí)行熟悉的onCreateViewHolder和onBindViewHolder方法)猜扮,其中只有RecyclerViewPool找到時(shí)才會(huì)調(diào)用onBindViewHolder勉吻,流程如下:

四:RecyclerView的緩存過程

場景一:

先看圖的左邊(此時(shí)假設(shè)cache 與 pool 中沒有東西),當(dāng)向下滑動(dòng)時(shí)旅赢,3 最先進(jìn)入 mCachedViews齿桃,隨后是 4 與 5,5 會(huì)將 3 擠出來煮盼,3 就會(huì)跑到 pool 中去了短纵。

再看圖的右邊,繼續(xù)向下滑動(dòng)時(shí)僵控,4 被 6 擠出來香到,放到了 pool 中,同時(shí) 8 需要顯示报破,那么就會(huì)先從 pool 中取悠就,發(fā)現(xiàn)正好有一個(gè) 3,那么就會(huì)取出來充易,將 3 重新顯示到屏幕上梗脾。

場景二:

如果向下滑到 7 顯示出來之后,不再繼續(xù)向下盹靴,而是往上滑動(dòng)藐唠,那么又會(huì)怎么樣呢?

看圖的右邊鹉究,很明顯宇立,5 從 cache 中被取出來直接復(fù)用,不用重新綁定自赔,7 被放入了 cache 中妈嘹。

五:RecyclerView的優(yōu)化

RecyclerView做性能優(yōu)化要說復(fù)雜也復(fù)雜,比如說布局優(yōu)化绍妨、緩存润脸、預(yù)加載等等柬脸。

優(yōu)化的點(diǎn)有很多,在這些看似獨(dú)立的點(diǎn)之間毙驯,其實(shí)存在一個(gè)樞紐:Adapter倒堕。

因?yàn)樗械腣iewHolder的創(chuàng)建和內(nèi)容的綁定都需要經(jīng)過Adaper的兩個(gè)函數(shù)onCreateViewHolder和onBindViewHolder。

因此我們性能優(yōu)化的本質(zhì)就是要**減少這兩個(gè)函數(shù)的調(diào)用時(shí)間和調(diào)用的次數(shù)**爆价。

1.從減少方法的調(diào)用次數(shù)來看:

(1)setItemViewCaches(int)

RecyclerView可以設(shè)置自己所需要的ViewHolder的CacheViews緩存數(shù)量垦巴,默認(rèn)大小是2。CacheViews中的緩存只能position相同才可得用铭段,且不會(huì)重新bindView骤宣,CacheViews滿了后移除到RecyclerPool中,并重置ViewHolder序愚,如果對(duì)于可能來回滑動(dòng)的RecyclerView憔披,把CacheViews的緩存數(shù)量設(shè)置大一些,可以減少bindView的次數(shù)爸吮,加快布局顯示芬膝。

注:此方法是拿空間換時(shí)間,要充分考慮應(yīng)用內(nèi)存問題形娇,根據(jù)應(yīng)用實(shí)際使用情況設(shè)置大小锰霜。

(2).setRecyclerViewPoll(復(fù)用poll緩存)

RecyclerView設(shè)置一個(gè)ViewHolder的對(duì)象池,這個(gè)池稱為RecycledViewPool埂软,這個(gè)對(duì)象池可以節(jié)省創(chuàng)建ViewHolder的開銷锈遥,更能避免GC,即便你不給它設(shè)置勘畔,它也會(huì)自己創(chuàng)建一個(gè)所灸。

如果多個(gè)RecycledView 的 Adapter 是一樣的,比如嵌套的 RecyclerView 中存在一樣的 Adapter炫七,可以通過設(shè)置RecyclerView.setRecycledViewPool(pool)來共用一個(gè)RecycledViewPool爬立。

RecycledViewPool使用:先從某個(gè)RecyclerView對(duì)象中獲得它創(chuàng)建的RecycledViewPool對(duì)象,或者是自己實(shí)現(xiàn)一個(gè)RecycledViewPool對(duì)象万哪,然后設(shè)置個(gè)接下來創(chuàng)建的每一個(gè)RecyclerView即可侠驯。

應(yīng)用場景:

a).針對(duì)item中包含rv的情況下才適用,如果rv的item都是普通的布局就不需要復(fù)用poll

b).Tabs+ViewPager+RecyclerView

c).一個(gè)豎直的RecyclerView包含多行可分別左右滑動(dòng)的RecyclerView

(3).RecyclerView.getRecycledViewPool().setMaxRecycledViews(0, 20)

當(dāng)我們調(diào)用notifyDataSetChanged() 或者notifyItemRangeChanged(i, c) (c這個(gè)范圍非常大的時(shí)候)奕巍,那么很多 ViewHolder 都會(huì)最終被放入到 pool 中吟策,因?yàn)?pool 只能放置5 個(gè),那么多余的就會(huì)被丟棄的止,等待回收檩坚。最重要的是會(huì)重新 create 與 bind 對(duì)性能影響比較大。如果你的列表能夠容納很多行,而且使用notifyDataSetChanged 方法比較頻繁匾委,那么你應(yīng)該考慮設(shè)置一下容量大小拖叙。

(4).使用局部刷新

調(diào)用了notifyDataSetChanged方法,RecyclerView 不知道到底發(fā)生了什么赂乐,所以它只能認(rèn)為所有的東西都發(fā)生了變化薯鳍,即將所有的 ViewHolder 都放入到 pool 中。會(huì)導(dǎo)致整個(gè)頁面范圍內(nèi)的ViewHolder重新調(diào)用onBindViewHolder方法這樣就重復(fù)做了一次bind操作挨措。這時(shí)我們換用notifyItemRemoved方法挖滤。可以看到运嗜,這時(shí)只會(huì)由于第一個(gè)移除壶辜,導(dǎo)致新的一個(gè)position=8進(jìn)入并展示悯舟,所以只有position=8調(diào)用了onBindViewHodler方法担租,而其他的已經(jīng)綁定的ViewHolder不需要重新綁定。

2.從減少方法執(zhí)行的時(shí)間來看:

(1).布局優(yōu)化 降低item的布局層次抵怎,使用ConstraintLayout奋救。

(2).去除冗余的setItemClick事件,一般都是在onBind方法中設(shè)置監(jiān)聽反惕,但是onBindView調(diào)用時(shí)機(jī)很多尝艘,會(huì)導(dǎo)致在RecyclerView滑動(dòng)過程中創(chuàng)建很多對(duì)象,這時(shí)可以全局創(chuàng)建一個(gè)姿染。

(3).避免在onBindView中進(jìn)行耗時(shí)的操作背亥。

其他方面的優(yōu)化:

(1).設(shè)置高度固定

如果item高度固定,可以使用RecyclerView.setHasFixedSize(true)來避免requestLayout浪費(fèi)資源悬赏。

notify一系列方法會(huì)執(zhí)行到下面這個(gè)方法

區(qū)別就在于當(dāng)設(shè)置setHasFixedSize會(huì)走if分支狡汉,而沒有設(shè)置則進(jìn)入到else分支,else分支直接會(huì)調(diào)用到requestLayout方法闽颇,該方法會(huì)導(dǎo)致視圖樹進(jìn)行重新繪制盾戴,onMeasure,onLayout最終都會(huì)被執(zhí)行到兵多,根據(jù)上述源碼可以得到一個(gè)優(yōu)化的地方在于尖啡,當(dāng)item嵌套了rv并且rv沒有設(shè)置wrap_content屬性時(shí),我們可以對(duì)該rv設(shè)置setHasFixedSize剩膘,這么做的一個(gè)最大的好處就是嵌套的rv不會(huì)觸發(fā)requestLayout衅斩,從而不會(huì)導(dǎo)致外層的rv進(jìn)行重繪。

(2).增加RecyclerView預(yù)留的額外空間

額外空間:顯示范圍之外怠褐,應(yīng)該額外緩存的空間

new LinearLayoutManager(this) {

??? @Override

??? protected int getExtraLayoutSpace(RecyclerView.State state) {

??????? return size;

??? }

};

一屏只能顯示一個(gè)元素的時(shí)候畏梆,第一次滑動(dòng)到第二個(gè)元素會(huì)卡頓。

RecyclerView?(以及其他基于adapter的view,比如ListView具温、GridView等)使用了緩存機(jī)制重用子 view(簡而言之就是蚕涤,系統(tǒng)只將屏幕可見范圍之內(nèi)的元素保存在內(nèi)存中,在滾動(dòng)的時(shí)候不斷的重用這些內(nèi)存中已經(jīng)存在的view铣猩,而不是新建view)揖铜。

這個(gè)機(jī)制在我們這里會(huì)導(dǎo)致一個(gè)問題,啟動(dòng)應(yīng)用之后达皿,在屏幕可見范圍內(nèi)天吓,我們只有一張卡片可見(估計(jì)作者的屏幕比較小)峦椰,當(dāng)我們滾動(dòng)的時(shí) 候龄寞,RecyclerView找不到可以重用的view了,它將創(chuàng)建一個(gè)新的汤功,因此在滑動(dòng)到第二個(gè)feed的時(shí)候就會(huì)有一定的延時(shí)物邑,但是第二個(gè)feed之 后的滾動(dòng)是流暢的,因?yàn)檫@個(gè)時(shí)候RecyclerView已經(jīng)有能重用的view了滔金。

getExtraLayoutSpace將返回LayoutManager應(yīng)該預(yù)留的額外空間(顯示范圍之外色解,應(yīng)該額外緩存的空間)。

(3).swapAdapter

我們使用RecyclerView時(shí)候餐茵,一般是setAdapter一次科阎,之后通過調(diào)用adapter.notify()來更新數(shù)據(jù)和UI(不討論差量更新)。一個(gè)界面中由一個(gè)RecyclerView承載所有內(nèi)容忿族,但是可以通過界面內(nèi)tab_button來切換內(nèi)容類別的情況锣笨,用于內(nèi)容數(shù)據(jù)量較大,希望來回切換能流暢迅速道批。因此這里我采用了多個(gè)adapter來記錄不同的類別數(shù)據(jù)错英,來回切換只要調(diào)用setAdapter(Adapter adapter)即可這是一個(gè)和setAdapter類似的方法,不過針對(duì)于界面view結(jié)構(gòu)類似或者相同屹徘,需要頻繁設(shè)置adapter的時(shí)候走趋,做了優(yōu)化,能夠再切換的時(shí)候復(fù)用相同的viewHolder噪伊,減少一定的開銷簿煌。

(4).DiffUtil一個(gè)神奇的工具類

DiffUtil是配合rv進(jìn)行差異化比較的工具類,通過對(duì)比前后兩個(gè)data數(shù)據(jù)集合鉴吹,DiffUtil會(huì)自動(dòng)給出一系列的notify操作姨伟,避免我們手動(dòng)調(diào)用notifiy的繁瑣.

但DiffUtil不太好用,有弊端:

(1).必須準(zhǔn)備兩個(gè)數(shù)據(jù)集

(2).實(shí)現(xiàn)callback接口豆励,areContentsTheSame是最難實(shí)現(xiàn)的夺荒,涉及到對(duì)比同type的item內(nèi)容是否一致瞒渠,比較效率的問題

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市技扼,隨后出現(xiàn)的幾起案子伍玖,更是在濱河造成了極大的恐慌,老刑警劉巖剿吻,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件窍箍,死亡現(xiàn)場離奇詭異,居然都是意外死亡丽旅,警方通過查閱死者的電腦和手機(jī)椰棘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來榄笙,“玉大人邪狞,你說我怎么就攤上這事∶┳玻” “怎么了帆卓?”我有些...
    開封第一講書人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長乡翅。 經(jīng)常有香客問我鳞疲,道長罪郊,這世上最難降的妖魔是什么蠕蚜? 我笑而不...
    開封第一講書人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮悔橄,結(jié)果婚禮上靶累,老公的妹妹穿的比我還像新娘。我一直安慰自己癣疟,他們只是感情好挣柬,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著睛挚,像睡著了一般邪蛔。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上扎狱,一...
    開封第一講書人閱讀 51,287評(píng)論 1 301
  • 那天侧到,我揣著相機(jī)與錄音,去河邊找鬼淤击。 笑死匠抗,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的污抬。 我是一名探鬼主播汞贸,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了矢腻?” 一聲冷哼從身側(cè)響起门驾,我...
    開封第一講書人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎多柑,沒想到半個(gè)月后猎唁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡顷蟆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年诫隅,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片帐偎。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡逐纬,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出削樊,到底是詐尸還是另有隱情豁生,我是刑警寧澤,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布漫贞,位于F島的核電站甸箱,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏迅脐。R本人自食惡果不足惜芍殖,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望谴蔑。 院中可真熱鬧豌骏,春花似錦、人聲如沸隐锭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽钦睡。三九已至蒂窒,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間荞怒,已是汗流浹背洒琢。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留挣输,地道東北人纬凤。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像撩嚼,于是被迫代替她去往敵國和親停士。 傳聞我的和親對(duì)象是個(gè)殘疾皇子挖帘,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354

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