RecyclerView 数据预取
更快處理任務(wù),使?jié)L動和滑動更流暢
在我小時候,媽媽為了治療我的拖延癥,總是告訴我:“如果你現(xiàn)在打掃你的房間,就不用以后再打掃了?!钡覐臎]這樣做。我知道最好能拖延就盡量拖延。一個原因是:如果我現(xiàn)在打掃了,房間還會變臟,那時候我就必須再打掃一遍了。另外,如果我把這件事放下足夠久,媽媽可能會忘了它的。
拖延對我來說總是有效。但我永遠(yuǎn)不用處理保持幀率的問題,不像我的朋友 RecyclerView 一樣。
問題
在一次滾動或慣性滑動中,RecyclerView 需要在新條目抵達屏幕時予以展示。這些新條目需要與數(shù)據(jù)相綁定(如果緩存中沒有相應(yīng)條目的話,還需要創(chuàng)建一個)。接下來,它們還需要被展開并畫出來。如果所有這些都是被懶加載的,在需要展示之前才做,UI 線程就會在工作完成時陷入停頓。接下來渲染可以繼續(xù)并且滾動(或者說滑動,但我打算用滾動來指代它們,以簡化討論)可以平滑地繼續(xù),直到下一個條目進入視野范圍。
一次典型的 RecyclerView 內(nèi)容滾動中的各個渲染階段(在?Lollipop?版本時的情況)。在UI線程,我們處理輸入事件和動畫,完成布局,并且記錄繪圖操作。接下來渲染線程把指令送往GPU。 在一次滾動的大多數(shù)幀中,RecyclerView 可以沒問題地完成它需要做的事,因為不需要處理新的內(nèi)容。在這些幀中,UI 線程處理輸入事件和動畫,完成布局,記錄繪圖操作。接下來它把繪圖信息與渲染線程同步(在 Lollipop 版本時的情況,之前的版本在 UI 線程完成所有工作),渲染線程把指令送往 GPU。
新條目使得輸入階段耗時更長,因為新的 view 需要被創(chuàng)建、綁定并布局。這推遲了渲染階段的開始,從而導(dǎo)致它可能在幀的邊界之后結(jié)束。在此情況下,就會發(fā)生掉幀。當(dāng)一個新的條目來到屏幕中時,輸入階段就需要完成更多工作,以綁定(可能還要創(chuàng)建)正確的 view。這推遲了 UI 線程其余的工作,以及渲染線程接下來的工作。如果這些不能在幀邊界內(nèi)完成的話,就會發(fā)生卡頓。
輸入階段的調(diào)用棧表明:新的條目進入視野范圍會導(dǎo)致一大塊時間被用于創(chuàng)建和綁定新的 view。 如果我們可以在其它地方完成這些工作,而不推遲所有其它事情,不就很好嗎??
在 view 可以被渲染之前,創(chuàng)建和綁定必須完成。這會在相應(yīng)的幀中消耗 UI 線程的寶貴時間。然而,UI 線程在前一幀中有大量時間無所事事。?Chris Craik(Android UI Toolkit 組的工程師)在用?Systraces?查看 RecyclerView 滾動時發(fā)現(xiàn)了這一點。他特別注意到,我們在需要使用一個條目時,會花費大量時間準(zhǔn)備它。而在一幀之前,UI 線程花了大量時間休眠,因為它很早就完成了任務(wù)。
解決方案
將創(chuàng)建和綁定工作移到前一幀,使 UI 線程能夠與渲染線程同時工作,從而避免接下來在渲染線程繪制結(jié)果之前同步完成這些工作。 顯然,這是優(yōu)化耗時的好時機。Chris 重新安排了默認(rèn) RecyclerView 布局時事件發(fā)生的順序,它現(xiàn)在在一個條目即將進入視野時預(yù)取數(shù)據(jù),這樣我們可以在空閑期完成工作,避免拖到大家都在等待結(jié)果時才完成。 完成這些工作基本上沒有任何代價,因為 UI 線程在兩幀之間的空隙不做任何工作。我們可以使用這些空閑時間來完成將來的工作,并使得未來的幀出現(xiàn)得更快,因為困難的部分已經(jīng)被完成了。
細(xì)節(jié),細(xì)節(jié)
這個系統(tǒng)的工作方式是,在 RecyclerView 開始一個滾動時安排一個 Runnable。這個 Runnable 負(fù)責(zé)根據(jù) layout manager 和滾動的方向預(yù)取即將進入視野的條目。預(yù)取不限于一個單獨的條目。它可以同時取出多個條目,例如在使用 GridLayoutManager 且新的一行馬上要出現(xiàn)的時候。在 25.1 版本中,預(yù)取操作被分為單獨的創(chuàng)建/綁定操作,從而比對整組條目做操作更容易被納入 UI 線程的空隙中。
有趣的是,系統(tǒng)必須預(yù)測操作需要多少時間,以及它們是否可以被放入空隙中。畢竟,如果預(yù)取把當(dāng)前幀推遲到截止時間之后,我們?nèi)匀粫虻魩杏X到卡頓,只是和不預(yù)取時原因不同而已。系統(tǒng)處理這些細(xì)節(jié)的方式是追蹤每種 view 類型的平均創(chuàng)建/綁定時間,從而使未來創(chuàng)建/綁定時間的合理預(yù)測成為可能。
對嵌套 RecyclerView(每一個條目自身都是 RecyclerView 的容器)完成這些工作更加復(fù)雜,因為綁定內(nèi)部 RecyclerView 并不涉及任何子控件的分配——RecyclerView 在被綁定和布局時按需取得子控件。預(yù)取系統(tǒng)仍然可以預(yù)先準(zhǔn)備內(nèi)層的 RecyclerView 內(nèi)部的子控件,但它必須知道有多少。這就是 25.1 版本中 LinearLayoutManager 新 API?setInitialItemPrefetchCount()的意義。它告訴系統(tǒng),在滾動時需要預(yù)取多少條目來充滿 RecyclerView。
警告
你需要注意這些危險:
-預(yù)取數(shù)據(jù)可能做一些最終不被需要的工作。因為我們在預(yù)取 view 時,有可能會采取太激進的策略,這樣 RecyclerView 就可能不會滾動到我們預(yù)取的條目。這意味著我們的預(yù)取工作可能會被浪費(雖然這些工作是被并行完成的,應(yīng)該不會浪費太多時間。另外,浪費是不太可能發(fā)生的,因為我們在需要數(shù)據(jù)之前不久才去預(yù)取,而且滾動不太可能在兩幀之間停止或反轉(zhuǎn))。 -渲染線程:渲染線程是 Lollipop 版本引入的性能特性,它可以讓一個不同的線程分擔(dān)渲染工作,并且支持其他的一些改進,例如把不可變的動畫(如漣漪、環(huán)形展現(xiàn)等)完全放在渲染線程,使其不受 UI 線程停頓的影響。這意味著運行 Lollipop 之前的版本的設(shè)備將不會受益于這個優(yōu)化,因為我們無法并行完成這些工作。
我要一些 —— 去哪兒拿?
預(yù)取優(yōu)化是在?Support Library v25中引入,在?v25.1.0中改進的。所以第一步是下載?最新版本的支持庫。
如果你使用 RecyclerView 提供的默認(rèn) layout manager,你將自動獲得這種優(yōu)化。然而,如果你使用嵌套 RecyclerView 或者自己寫 layout manager,你需要改變你的代碼來利用這個特性。
對于嵌套 RecyclerView 而言,要獲取最佳的性能,在內(nèi)部的 LayoutManager 中調(diào)用 LinearLayoutManager 的setInitialItemPrefetchCount()方法(25.1版本起可用)。例如,如果你豎直方向的list至少展示三個條目,調(diào)用 setInitialItemPrefetchCount(4)。
如果你實現(xiàn)了自己的 LayoutManager,你需要重寫?LayoutManager.collectAdjacentPrefetchPositions()方法。該方法在數(shù)據(jù)預(yù)取開啟時被 RecyclerView 調(diào)用(LayoutManager 的默認(rèn)實現(xiàn)什么都不做)。第二,在嵌套的內(nèi)層 RecyclerView 中,如果你想讓你的 LayoutManager 預(yù)取數(shù)據(jù),你同樣應(yīng)當(dāng)實現(xiàn)?LayoutManager.collectInitialPrefetchPositions()。
和以前一樣,優(yōu)化你的創(chuàng)建和綁定步驟,做盡可能少的工作,是值得的。運行的最快的代碼是根本不需要運行的代碼;即使框架可以通過數(shù)據(jù)預(yù)取并行工作,它仍然消耗時間,而且耗時較長的條目創(chuàng)建仍然可以導(dǎo)致卡頓。例如,一棵最小的 view 樹總比一棵復(fù)雜的更容易創(chuàng)建和綁定。本質(zhì)上,綁定應(yīng)該和調(diào)用 setter 一樣方便,一樣快。即使你用目前的代碼就可以在一幀的時間限制中完成工作,進一步優(yōu)化意味著它將更可能在低端的用戶機型上運行良好。此外,在高端設(shè)備上為這些常用場景節(jié)約性能,總是對電池有益的。如果你已經(jīng)盡可能縮短了創(chuàng)建和綁定的時間,預(yù)取將會幫助你縮短兩幀之間的剩余時間。
如果你想要見到實際的優(yōu)化,在默認(rèn)或自定義的 LayoutManager 中,你可以切換?LayoutManager.setItemPrefetchEnabled()并比較結(jié)果。你應(yīng)該能夠從視覺上直觀地看到差異;它確實如此顯著,特別是在條目需要大量時間創(chuàng)建和綁定的情況下。但如果你想知道在表面下發(fā)生過什么,在預(yù)取打開和關(guān)閉時運行Systrace, 或者打開?GPU profiling。
Systrace 顯示數(shù)據(jù)預(yù)取在UI線程空閑時預(yù)取數(shù)據(jù)。
GOTO 結(jié)尾
查看?最新的 Support Library并和能預(yù)取數(shù)據(jù)的 RecyclerView 一起玩耍。同時,我將繼續(xù)不清理我的房間。
原文發(fā)布時間為:2017年2月14日
本文來自云棲社區(qū)合作伙伴掘金,了解相關(guān)信息可以關(guān)注掘金網(wǎng)站。
總結(jié)
以上是生活随笔為你收集整理的RecyclerView 数据预取的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 1.4. Open Source and
- 下一篇: View 事件传递体系知识梳理(1)