概述
在應(yīng)用開發(fā)中,當(dāng)頁面內(nèi)的列表結(jié)構(gòu)較為復(fù)雜且每個列表項(xiàng)包含的組件較多時,容易導(dǎo)致嵌套層級過深,進(jìn)而增加組件的負(fù)載,延長繪制時間。在轉(zhuǎn)場或列表滑動時,列表項(xiàng)可能會一次性加載大量數(shù)據(jù),導(dǎo)致性能問題。此時,可以采用分幀渲染技術(shù),將原本在一幀內(nèi)加載的數(shù)據(jù)分散到多幀中逐步加載,從而減輕單幀的渲染壓力。不過,分幀渲染需要開發(fā)者精確計(jì)算每幀加載的數(shù)據(jù)量,操作較為復(fù)雜,因此建議僅在性能瓶頸明顯且必要時使用。
實(shí)現(xiàn)原理
(一)原理說明
在單幀內(nèi)繪制多個特性各不相同的組件時,會同時創(chuàng)建大量的GraphicsPipelines,導(dǎo)致后續(xù)整個Flush階段的耗時增加,從而使得單幀渲染時間過長。針對這種單幀內(nèi)組件負(fù)載重、數(shù)據(jù)量大、繪制耗時長的問題,開發(fā)者可以根據(jù)實(shí)際的業(yè)務(wù)邏輯、頁面布局和數(shù)據(jù)量,提前規(guī)劃需要通過多少幀完成加載,通過幀回調(diào)監(jiān)聽來動態(tài)修改狀態(tài)變量或向數(shù)據(jù)結(jié)構(gòu)中補(bǔ)充數(shù)據(jù),計(jì)算和設(shè)置每一幀需要處理的渲染數(shù)據(jù),確保每一幀只處理預(yù)設(shè)的數(shù)據(jù)量。由于已經(jīng)設(shè)置了幀回調(diào)監(jiān)聽,頁面組件在加載數(shù)據(jù)時,只需通過狀態(tài)變量或數(shù)據(jù)結(jié)構(gòu)即可實(shí)現(xiàn)按幀分批加載數(shù)據(jù)。這種方式將原本需要在一幀內(nèi)加載的數(shù)據(jù)分散到多幀中處理,有效減少了首幀的渲染壓力,避免了首幀卡頓現(xiàn)象的發(fā)生。如下圖所示,將一幀數(shù)據(jù)拆分到三幀示例:
(二)具體實(shí)現(xiàn)
在高負(fù)載場景下使用分幀渲染的關(guān)鍵操作是把數(shù)據(jù)拆分到每一幀中加載,但這個過程中加載新的數(shù)據(jù)時可能會將已有數(shù)據(jù)再次繪制,因此需要搭配合理的頁面布局來避免重繪。可以通過if或ForEach兩種方法來實(shí)現(xiàn)布局,兩種方法的更新機(jī)制如下:
if更新機(jī)制是根據(jù)狀態(tài)判斷條件,如果分支沒有變化,不會對條件渲染語句進(jìn)行更新。
ForEach非首次渲染會檢查新生成的鍵值是否在上次渲染中已經(jīng)存在。如果鍵值不存在,則會創(chuàng)建一個新的組件;如果鍵值存在,則不會創(chuàng)建新的組件,而是直接渲染該鍵值所對應(yīng)的組件。
因此在分幀逐步加載數(shù)據(jù)時使用上述兩種方法不會引起重繪。并且在頁面布局時可以給分幀渲染的外部容器組件設(shè)置寬高,這樣組件本身不會觸發(fā)重新進(jìn)行Measure的過程,對組件的寬高不會重新測算,避免因外部容器大小改變引起重繪,詳情可參考合理使用布局。
保證頁面不會重繪后,在實(shí)際開發(fā)過程中為了逐步增加頁面數(shù)據(jù),可以使用ArkTS中提供的displaySync(可變幀率)API接口,通過Vsync信號控制數(shù)據(jù)刷新的時機(jī),來實(shí)現(xiàn)繪制內(nèi)容幀率的控制。先通過頁面UI中aboutToAppear()添加幀回調(diào)監(jiān)聽并開啟監(jiān)聽,Vsync信號變化時觸發(fā)幀回調(diào)執(zhí)行應(yīng)用邏輯,計(jì)算每幀加載的數(shù)據(jù),改變ViewModel數(shù)據(jù)。ViewModel數(shù)據(jù)改變后驅(qū)動頁面或組件執(zhí)行build(),使用if或ForEach分幀迭代渲染繪制UI并控制刷新范圍。最后可以在aboutToDisappear()里停止幀回調(diào)監(jiān)聽。
具體操作流程如下圖:
轉(zhuǎn)場場景
由于業(yè)務(wù)需求,從當(dāng)前頁面進(jìn)入一個新頁面時,會有轉(zhuǎn)場動畫播放,并且在動畫首幀中加載新頁面所需要的數(shù)據(jù)。如果數(shù)據(jù)量較多,那么動畫首幀的響應(yīng)時延就會變長,導(dǎo)致后面動畫幀延遲播放的情況。從一個頁面到新頁面轉(zhuǎn)場流程圖如下:
(一)解決思路
既然轉(zhuǎn)場時一次性加載大量的數(shù)據(jù)會導(dǎo)致卡頓情況,那么采用分幀渲染將數(shù)據(jù)拆分成多份并分批次進(jìn)行加載就是一種解決思路。
轉(zhuǎn)場場景分幀:轉(zhuǎn)場時會在動畫首幀加載新頁面的數(shù)據(jù),采用分幀策略就是將首幀加載的數(shù)據(jù)拆分,將數(shù)據(jù)拆分到后面的幀加載,新頁面打開后List列表只展示兩個列表項(xiàng),因此在首幀加載顯示兩條數(shù)據(jù),其余緩存數(shù)據(jù)可以在第二幀加載。該方法的優(yōu)點(diǎn)是減少動畫首幀的響應(yīng)時間,缺點(diǎn)是轉(zhuǎn)場動畫完成時延變長。
轉(zhuǎn)場場景效果圖如下:
在分幀前會在轉(zhuǎn)場動畫的首幀將層疊組件和列表可見區(qū)域與緩存區(qū)域的數(shù)據(jù)全部加載,而分幀后在首幀加載層疊組件和列表前兩項(xiàng)的數(shù)據(jù),在第二幀加載緩存區(qū)域的列表數(shù)據(jù)。分幀前后示意圖如下:
(二)常規(guī)情況
在組件即將出現(xiàn)時回調(diào)aboutToAppear()接口,將數(shù)據(jù)放入productData中,并通過瀑布流加載。編譯運(yùn)行后,可以通過Trace圖看到,轉(zhuǎn)場動畫的首幀耗時21ms左右,這是因?yàn)樵邳c(diǎn)擊進(jìn)入頁面時將數(shù)據(jù)全部放入瀑布流,在235970幀中需要計(jì)算每個子組件的尺寸,導(dǎo)致了響應(yīng)時間增長。
(三)優(yōu)化方案
在aboutToAppear()接口中添加displaySync的幀回調(diào),并將數(shù)據(jù)拆分進(jìn)行加載。此時,aboutToAppear()接口中并沒有一次性加載全部數(shù)據(jù),而是將數(shù)據(jù)拆分,在幀回調(diào)中分成2次進(jìn)行加載,編譯運(yùn)行后,通過Trace圖可以看到,動畫首幀的耗時是12ms。相較于優(yōu)化前的代碼,不再是首幀占據(jù)大量的時間,而是將耗時分?jǐn)偟搅撕竺娴膭赢嫀小.?dāng)數(shù)據(jù)量更大時,可以將數(shù)據(jù)進(jìn)行更多次拆分,將不會直接出現(xiàn)在屏幕上的數(shù)據(jù)放到第二幀或者第三幀中進(jìn)行加載,降低首幀的響應(yīng)時延。
對使用分幀前后進(jìn)行分析,得到的數(shù)據(jù)如下表所示:
使用分幀 | 使用分幀前 | 使用分幀后 |
---|---|---|
首幀耗時 | 21ms | 12ms |
第二幀耗時 | 4ms | 13ms |
在使用分幀后動畫首幀與第二幀分別是12ms和13ms,如果依然沒有達(dá)到期望的幀率,可以繼續(xù)將數(shù)據(jù)拆分。
滑動場景
在日歷應(yīng)用中,需要在一個List里面加載每個月的全部天數(shù),包括公歷和農(nóng)歷日期,這樣在一個ItemView復(fù)用組件中就會有很多數(shù)據(jù)加載,當(dāng)列表滑動的時候,通過組件復(fù)用的aboutToReuse()接口設(shè)置新的數(shù)據(jù),就會導(dǎo)致ItemView內(nèi)所有組件一起刷新,可能會引起掉幀卡頓現(xiàn)象。
(一)解決思路
由于一次性加載大量數(shù)據(jù)、刷新大量組件會導(dǎo)致卡頓丟幀,那么減少一次性加載的數(shù)據(jù)量就是一種解決方法。但是由于業(yè)務(wù)需求,需要加載的數(shù)據(jù)總量和繪制的組件數(shù)量是不能減少的,那么就可以考慮采用分幀渲染。
滑動場景分幀:滑動日歷列表,復(fù)用ItemView組件,更新每月天數(shù)包含陰歷和陽歷,一次更新所有天數(shù),數(shù)據(jù)量大,可以使用分幀策略,將每月日期數(shù)據(jù)進(jìn)行拆分,一幀只更新5天數(shù)據(jù),在使用ForEach循環(huán)每月的天數(shù)時,因?yàn)橐淮沃桓?天數(shù)據(jù),F(xiàn)orEach會根據(jù)key值更新對應(yīng)的天數(shù),從而避免在一幀中更新所有數(shù)據(jù)。該方法優(yōu)點(diǎn)是可以將數(shù)據(jù)拆分在多幀中加載,缺點(diǎn)是操作比較麻煩,需要開發(fā)者根據(jù)實(shí)際情況計(jì)算一幀中加載的數(shù)據(jù)量,維護(hù)較為復(fù)雜。
滑動場景效果圖如下:
分幀前后示意圖如下:
(二)?常規(guī)情況
通常情況下,會在aboutToReuse()中設(shè)置新的數(shù)據(jù),并一次性繪制所有的組件。通過組件復(fù)用,在ItemView的aboutToReuse()接口中,將一個月的數(shù)據(jù)直接設(shè)置到狀態(tài)變量monthItem中,這樣下面的Flex就會收到狀態(tài)變量變更的消息通知,從而刷新組件中的數(shù)據(jù)。編譯運(yùn)行后,進(jìn)入日歷頁面,然后滑動列表到最底端,分析下圖。
選中Actual Timeline(render_service)標(biāo)簽中的146272后,可以通過箭頭看到它所關(guān)聯(lián)到的位置是Actual Timeline(example.display)標(biāo)簽中的209136和209137,即RenderService層出現(xiàn)的異常情況是由應(yīng)用層中前面兩幀里面的操作引起的。
通過箭頭2的標(biāo)簽可以看到,在209135中調(diào)用了aboutToReuse接口,此時系統(tǒng)開始了組件復(fù)用的繪制操作,在aboutToReuse接口將一個月的所有數(shù)據(jù)全部放入了當(dāng)前被復(fù)用的組件中,并更新了所有用于顯示日期的Text組件中的數(shù)據(jù)(箭頭3,diffIndexArray.lenght:35,表示有35個不同的元素),這就導(dǎo)致209136需要計(jì)算35個子組件的尺寸(箭頭1),從而引起146272的繪制時間延長。
在列表數(shù)據(jù)量較少時,其實(shí)并不會引起掉幀現(xiàn)象,因?yàn)槊看窝娱L幀的時間都很短,對幀率的影響較小,但是在列表數(shù)據(jù)較多時,就會因?yàn)檠娱L幀過多,發(fā)生掉幀現(xiàn)象。
(三)優(yōu)化方案
通過displaySync中的幀回調(diào)方法,將數(shù)據(jù)拆分到每一幀中進(jìn)行加載和繪制,只需要在幀回調(diào)中修改自定義子組件ItemView中加載數(shù)據(jù)的方式。
首先,需要在ItemView中第一次使用時創(chuàng)建displaySync對象,設(shè)置期望幀率,添加幀回調(diào)的監(jiān)聽,然后進(jìn)行啟動。
然后,在監(jiān)聽中添加更新數(shù)據(jù)的代碼。這里將每個月的數(shù)據(jù)更新拆分開來,第一步用來更新月份數(shù)據(jù)和計(jì)算總的執(zhí)行步驟,最后一步將計(jì)數(shù)數(shù)據(jù)清空, 方便下一次數(shù)據(jù)的寫入,其余需要執(zhí)行步驟的多少根據(jù)每次加載數(shù)據(jù)量會有所改變。
最后,在aboutToReuse接口中將數(shù)據(jù)放入數(shù)組中,用于幀回調(diào)中開始執(zhí)行數(shù)據(jù)更新。
分析下面trace圖,在211618中,開始調(diào)用aboutToReuse接口,由于只是將數(shù)據(jù)放入一個temp數(shù)組中,并沒有更新復(fù)用組件中的數(shù)據(jù),所以這一幀并沒有發(fā)生延長現(xiàn)象。
在211619中開始逐步更新復(fù)用組件中的數(shù)據(jù),在第一幀中更新月份和周的數(shù)據(jù),但是由于前一幀(211618)中并沒有更新當(dāng)前復(fù)用組件中的數(shù)據(jù),所以在211619中并不需要繪制組件,所以此幀耗時依舊很短。
結(jié)合代碼可以看到,在211620中放入了5天的日期數(shù)據(jù),由于前一幀(211619)只是設(shè)置了2條數(shù)據(jù),并且只有1條會更新,所以這一幀的繪制時間也不會超時。
和前一幀(211620)一樣,此幀(211621)中更新了5天的日期數(shù)據(jù),并且會重新測量上一幀中更新數(shù)據(jù)的5個Text組件尺寸(箭頭1),而其余的組件由于數(shù)據(jù)并沒有變動,所以測量被略過了(箭頭2)。
后面的幀是類似的,每次只會放入5天的數(shù)據(jù),并且更新上一幀中設(shè)置的數(shù)據(jù)所關(guān)聯(lián)的Text組件。由于每次更新的組件數(shù)量較少,每幀基本上都能在規(guī)定的時間內(nèi)(1秒120幀,即8ms一幀)繪制完成,所以延長幀就會較少。這樣不論列表中數(shù)據(jù)多還是少,都不會引起掉幀現(xiàn)象的發(fā)生。
對使用分幀前后進(jìn)行分析,得到的數(shù)據(jù)如下表所示:
使用分幀 | 使用分幀前 | 使用分幀后 |
---|---|---|
渲染幀率 | 113fps | 120fps |
丟幀率 | 5.8% | 0% |
在使用displaySync時不建議將ExpectedFrameRateRange中的expected、min、max都設(shè)置為120,否則會干擾系統(tǒng)的可變幀率機(jī)制運(yùn)行,產(chǎn)生不必要的負(fù)載,進(jìn)而影響到整機(jī)的性能和功耗,詳情請參考場景策略建議。
總結(jié)
通過上述示例代碼和優(yōu)化過程可以看出,在列表中使用組件復(fù)用時,如果一次性加載所有數(shù)據(jù),可能會導(dǎo)致掉幀問題。雖然在數(shù)據(jù)量較少時,單幀繪制時間的延長不會明顯影響性能,但隨著數(shù)據(jù)量增加,這種單幀耗時的增加會變得顯著,進(jìn)而引發(fā)掉幀。因此,開發(fā)者可以根據(jù)實(shí)際業(yè)務(wù)需求,合理采用分幀策略對數(shù)據(jù)進(jìn)行拆分,將原本集中在一幀內(nèi)處理的任務(wù)分散到多幀中執(zhí)行。這種方式可以有效減少單幀的渲染壓力,降低延長幀的發(fā)生概率,從而避免因掉幀導(dǎo)致的性能問題。
-
渲染
+關(guān)注
關(guān)注
0文章
75瀏覽量
11112 -
高負(fù)載
+關(guān)注
關(guān)注
0文章
5瀏覽量
6013 -
應(yīng)用開發(fā)
+關(guān)注
關(guān)注
0文章
63瀏覽量
9669
原文標(biāo)題:HarmonyOS應(yīng)用高負(fù)載場景分幀渲染
文章出處:【微信號:HarmonyOS_Dev,微信公眾號:HarmonyOS開發(fā)者】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
如何評價光柵化渲染中光線在場景中的折返?
HarmonyOS實(shí)戰(zhàn)開發(fā)-合理選擇條件渲染和顯隱控制
HarmonyOS卡片開發(fā)-JS/JAVA場景能力簡析
HDC2021技術(shù)分論壇:酷炫3D效果在瘦設(shè)備上也能實(shí)現(xiàn)?
圖形測試分析毫無頭緒?HarmonyOS圖形棧測試技術(shù)幫你解決
打造HarmonyOS智能全場景,7大BUFF為您助力!
HarmonyOS/OpenHarmony應(yīng)用開發(fā)-ArkTS語言渲染控制概述
HarmonyOS/OpenHarmony應(yīng)用開發(fā)-ArkTS語言渲染控制if/else條件渲染
華為開發(fā)者HarmonyOS零基礎(chǔ)入門:15分鐘玩轉(zhuǎn)harmonyOS服務(wù)卡片

HarmonyOS測試技術(shù)與實(shí)戰(zhàn)-UI和渲染分離

華為開發(fā)者分論壇HarmonyOS學(xué)生公開課-HarmonyOS應(yīng)用的可流轉(zhuǎn)演示

華為開發(fā)者分論壇HarmonyOS學(xué)生公開課-10分鐘成為HarmonyOS開發(fā)者

華為開發(fā)者分論壇HarmonyOS學(xué)生公開課-如何學(xué)習(xí)HarmonyOS應(yīng)用開發(fā)?

HarmonyOS 3D渲染引擎介紹

評論