知識(shí)回顧
前言
項(xiàng)目迭代開發(fā)一定程度后,性能優(yōu)化是重中之重,其中包括了包體積,UI 渲染、交互等多個(gè)方面。
通過 Flutter 應(yīng)用的混淆為入口,我們主要探討了UI 渲染的優(yōu)化。
其中就會(huì)涉及到一個(gè)非常關(guān)健的概念 ——「FPS,F(xiàn)rame Per Second」即「每秒展示幀數(shù)」,它代表了應(yīng)用的流暢度。
我們知道,動(dòng)畫和物體動(dòng)態(tài)的運(yùn)動(dòng)都是由在一段時(shí)間內(nèi)一系列連續(xù)變化的靜態(tài)幀構(gòu)成的。
在考慮應(yīng)用的渲染性能時(shí),我們就是在試圖分析應(yīng)用每秒渲染的幀數(shù)。
從物理角度看,對(duì)于連續(xù)的一系列圖像幀,人腦會(huì)根據(jù)眼睛發(fā)出的視覺信號(hào)做出反應(yīng),一個(gè)個(gè)靜態(tài)幀的切換到達(dá)一定速度后,就可以欺騙我們的大腦,讓我們以為它們是連續(xù)的,F(xiàn)PS 就是圖像幀切換的速度單位。
因此有人說,物體運(yùn)動(dòng)的概念其實(shí)就是一種思維的束縛。
當(dāng) FPS 達(dá)到 10-12 時(shí),大腦便可以感知運(yùn)動(dòng),此時(shí)并不流暢,達(dá)到 24 FPS 時(shí),人眼就能看到流暢的運(yùn)動(dòng)了,但是在電影和視頻中,則至少需要每秒 60 幀的速度才可以使人的大腦輕松感知到流暢地運(yùn)動(dòng)。
1000ms / 60 frames = 16.666 ms/frame
我們需要在 16.66 毫秒內(nèi)完成整個(gè)幀的計(jì)算,布局和渲染,否則不流暢,就需要掏出我們的 24K 合金雙攝眼,找到優(yōu)化點(diǎn),讓應(yīng)用保持流暢。
源碼分析
1. 渲染過程
Flutter 應(yīng)用的每一幀都由框架層和引擎層互相協(xié)作完成。
最初,某些外部事件(如手勢(shì),網(wǎng)絡(luò)等)或者異步任務(wù)會(huì)導(dǎo)致屏幕更新,該消息消息頁(yè)會(huì)通知到引擎層。
Flutter 框架層會(huì)攔截了該請(qǐng)求,執(zhí)行 Tickers 相關(guān)的任務(wù)(如動(dòng)畫)。
這些任務(wù)也可能會(huì)重新發(fā)出一個(gè)請(qǐng)求,以供以后的幀渲染。(如動(dòng)畫暫停后再繼續(xù),需要在以后的階段接收另一個(gè) Begin 幀)。
然后,引擎層就可以開始做屏幕渲染工作了,但在開始之前,F(xiàn)lutter 框架依然會(huì)攔截該請(qǐng)求,并根據(jù)當(dāng)前的組件結(jié)構(gòu)和尺寸大小計(jì)算出更新布局、繪制相關(guān)的所有數(shù)據(jù)。
完成這些任務(wù)后,如果最終確定真的要在屏幕上繪制一些東西,它就會(huì)將需要渲染的新數(shù)據(jù)發(fā)送到 Flutter Engine,做最終的屏幕更新。
整個(gè)過程都在 Flutter 的 UI 線程中運(yùn)行,如若阻塞,就會(huì)卡頓。
通常,應(yīng)用開發(fā)者不需要關(guān)心引擎層的邏輯,但并不意味著我們不需要關(guān)心渲染性能。
引擎層的功能其實(shí)也是單一的,他只是拿到框架層的數(shù)據(jù)去做渲染而已。但是框架層是由我們控制的,我們所寫的每一個(gè)組件都在框架層之上。
如何將傳遞給引擎層的更新數(shù)據(jù)做到最優(yōu),就是渲染優(yōu)化時(shí)我們需要考慮的問題。
這些更新數(shù)據(jù)就是由 Flutter 中重要的三棵樹生成的,建議不熟悉的讀者去回看之前的這篇文章。
我們需要做的就是讓 Flutter 中重建組件的個(gè)數(shù)盡量少。
2. 分析工具
在 Android Studio 中,找到 Flutter Performance (View > Tool Windows > Flutter Performance),就可以直接看到正在重建的 widget 數(shù)量。
這里,勾選 Show widget rebuild information 復(fù)選框,此功能也能夠幫助你檢測(cè)幀的渲染和顯示時(shí)間是否超過 16ms。
3. 優(yōu)化方法
合理使用const關(guān)鍵詞
const 您可以通過將其附加到Widget的構(gòu)造函數(shù)來(lái)抑制Widget的重建(它與Widget緩存時(shí)的狀態(tài)相同)。
構(gòu)建組件時(shí)使用 const 關(guān)鍵詞,可以抑制 widget 的重建。
const 在 Dart 中用于聲明常量,應(yīng)用到 widget 中就相當(dāng)于告訴 Flutter,“我這個(gè)組件不會(huì)碎狀態(tài)更新而改變了?!保虼诉_(dá)到了減少重建的效果。
使用 const 也需要注意如下幾點(diǎn):
當(dāng)const 修飾類的構(gòu)造函數(shù)時(shí),它要求該類的所有成員都必須是final的。
const 變量只能在定義的時(shí)候初始化。
合理利用 const 關(guān)鍵詞,可以在很大程度上優(yōu)化應(yīng)用的性能
合理使用組件
Flutter 實(shí)現(xiàn)的一些效果背后可能會(huì)使用 saveLayer() 這個(gè)代價(jià)很大的方法。
為什么 saveLayer 代價(jià)很大?
調(diào)用 saveLayer() 會(huì)開辟一片離屏緩沖區(qū)。將內(nèi)容繪制到離屏緩沖區(qū)可能會(huì)觸發(fā)渲染目標(biāo)切換,這些切換在較早期的 GPU 中特別慢。
——來(lái)自 flutter.cn,https://flutter.cn/docs/testing/best-practices
如下這幾個(gè)組件,底層都會(huì)觸發(fā) saveLayer() 的調(diào)用,同樣也都會(huì)導(dǎo)致性能的損耗:
ShaderMask
ColorFilter
Chip,當(dāng) disabledColorAlpha != 0xff 的時(shí)候,會(huì)調(diào)用 saveLayer()。
Text,如果有 overflowShader,可能調(diào)用 saveLayer() ,
官方也給了我們一些非常需要注意的優(yōu)化點(diǎn):
由于 Opacity 會(huì)使用屏幕外緩沖區(qū)直接使目標(biāo)組件中不透明,因此能不用 Opacity Widget,就盡量不要用。有關(guān)將透明度直接應(yīng)用于圖像的示例,請(qǐng)參見 Transparent image,比使用 Opacity widget 更快,性能更好。
要在圖像中實(shí)現(xiàn)淡入淡出,請(qǐng)考慮使用 FadeInImage 小部件,該小部件使用 GPU 的片段著色器應(yīng)用漸變不透明度。
很多場(chǎng)景下,我們確實(shí)沒必要直接使用 Opacity 改變透明度,如要作用于一個(gè)圖片的時(shí)候可以直接使用透明的圖片,或者直接使用 Container:Container(color: Color.fromRGBO(255, 0, 0, 0.5))
Clipping 不會(huì)調(diào)用 saveLayer()(除非明確使用 Clip.antiAliasWithSaveLayer),因此這些操作沒有 Opacity 那么耗時(shí),但仍然很耗時(shí),所以請(qǐng)謹(jǐn)慎使用。
要?jiǎng)?chuàng)建帶圓角的矩形,而不是應(yīng)用剪切矩形,請(qǐng)考慮使用很多 widget 都提供的 borderRadius屬性。
管理著色器編譯垃圾
有時(shí)候,應(yīng)用中的動(dòng)畫首次運(yùn)行時(shí)會(huì)看起來(lái)非??D,但是運(yùn)行多次之后便可以正常運(yùn)行,這可能就是由于著色器編譯混亂導(dǎo)致的。
在圖形渲染,著色器相當(dāng)于是在 GPU 運(yùn)行的一組代碼。想要達(dá)到 60fps,需要在 16 毫秒內(nèi)繪制一個(gè)平滑的幀,但是在編譯著色器時(shí),它花費(fèi)的時(shí)間可能比應(yīng)該花費(fèi)的時(shí)間更多,可能會(huì)接近幾百毫秒,并且會(huì)導(dǎo)致丟失數(shù)十個(gè)幀,將 fps 從 60 降至 6。
解決方法
Flutter 1.20 之后,F(xiàn)lutter 為開發(fā)者提供了非常方便的一組命令行工具,由此開發(fā)人員可以使用 Skia Shader Language 格式收集最終用戶可能需要的著色器, 一旦將 SkSL 著色器打包到應(yīng)用程序中,當(dāng)用戶打開應(yīng)用程序時(shí),就會(huì)自動(dòng)進(jìn)行預(yù)編譯。
運(yùn)行應(yīng)用,添加 --cache-sksl 參數(shù)捕獲 SkSL 中的著色器:
flutter run --profile --cache-sksl
flutter run --profile --cache-sksl --purge-persistent-cache
該參數(shù)可能會(huì)刪除 SkSL 著色器捕獲的較舊的非 SkSL著色器緩存,因此只能在第一次運(yùn)行時(shí)使用 --cache-sksl。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-611779.html
在不同平臺(tái)上,可以執(zhí)行以下命令,使用 SkSL 預(yù)熱功能構(gòu)建應(yīng)用程序:文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-611779.html
flutter build apk — bundle-sksl-path flutter_01.sksl.json
到了這里,關(guān)于【Android常見問題(五)】- Flutter項(xiàng)目性能優(yōu)化的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!