效能最佳化實務

一般來說,Flutter 應用程式預設效能良好,因此您只需要避免常見陷阱,就能獲得絕佳效能。這些最佳實務建議將協助您撰寫效能最佳的 Flutter 應用程式。

要如何設計 Flutter 應用程式才能最有效率地呈現場景?特別是,要如何確保架構產生的繪製程式碼盡可能有效率?已知有些繪製和配置操作很慢,但並非總是能避免。應遵循以下指南,謹慎使用這些操作。

將昂貴的操作降至最低

有些操作比其他操作更昂貴,表示它們會耗用更多資源。顯然地,您只希望在必要時使用這些操作。應用程式的使用者介面設計和實作方式會對其執行效率產生重大影響。

控制 build() 成本

設計使用者介面時,請記住以下事項

  • 避免在 build() 方法中進行重複且昂貴的工作,因為當祖先小工具重新建置時,build() 可能會被頻繁呼叫。
  • 避免使用單一且過大的小工具,其 build() 函式也很大。請根據封裝,以及它們的變更方式,將它們拆分成不同的多個小工具
    • 當對 State 物件呼叫 setState() 時,所有後代小工具都會重新建置。因此,將 setState() 呼叫定位到實際需要變更使用者介面的子樹部分。如果變更僅限於樹狀結構的一小部分,請避免在樹狀結構的高處呼叫 setState()
    • 重建所有後代的遍歷會在再次遇到與前一幀相同的子小工具實例時停止。此技術在架構內廣泛用於最佳化動畫,其中動畫不會影響子樹。請參閱 TransitionBuilder 模式和 SlideTransition 的原始碼,它使用此原則來避免在動畫時重建其後代。(「相同實例」使用 operator == 評估,但請參閱本頁末尾的陷阱部分,了解何時避免覆寫 operator ==。)
    • 盡可能在小工具上使用 const 建構函式,因為它們允許 Flutter 短路大部分重建工作。若要自動提醒您在可能時使用 const,請啟用 flutter_lints 套件中的建議 lints。如需更多資訊,請查看 flutter_lints 遷移指南
    • 若要建立可重複使用的 UI 片段,請優先使用 StatelessWidget,而不是函式。

如需更多資訊,請查看


明智地使用 saveLayer()

有些 Flutter 程式碼會使用 saveLayer(),這是一個昂貴的操作,用於在 UI 中實作各種視覺效果。即使你的程式碼沒有明確呼叫 saveLayer(),你使用的其他小工具或套件也可能在幕後呼叫它。你的應用程式可能呼叫 saveLayer() 的次數超過必要;過度呼叫 saveLayer() 可能會導致卡頓。

為什麼 saveLayer 這麼昂貴?

呼叫 saveLayer() 會配置一個離螢幕緩衝區,而將內容繪製到離螢幕緩衝區中可能會觸發渲染目標切換。GPU 希望像消防水管一樣執行,而渲染目標切換會強制 GPU 暫時重新導向該串流,然後再將其導回。在行動裝置 GPU 上,這對渲染效能特別具有破壞性。

什麼時候需要 saveLayer?

在執行階段,如果你需要動態顯示來自伺服器(例如)的各種形狀,每個形狀都有一些透明度,可能會(或可能不會)重疊,那麼你幾乎必須使用 saveLayer()

偵錯對 saveLayer 的呼叫

你如何得知你的應用程式直接或間接呼叫 saveLayer() 的頻率?saveLayer() 方法會在 DevTools 時間軸 上觸發一個事件;透過在 DevTools 效能檢視 中檢查 PerformanceOverlayLayer.checkerboardOffscreenLayers 切換,了解你的場景何時使用 saveLayer

將對 saveLayer 的呼叫減至最少

你可以避免呼叫 saveLayer 嗎?這可能需要重新思考你如何建立視覺效果

  • 如果呼叫來自您的程式碼,您可以減少或消除它們嗎?例如,您的 UI 可能重疊兩個形狀,每個形狀都具有非零透明度
    • 如果它們總是重疊相同的量、相同的方式、相同的透明度,您可以預先計算這個重疊的半透明物件的樣子,快取它,並使用它來代替呼叫 saveLayer()。這適用於您可以預先計算的任何靜態形狀。
    • 您可以重構繪圖邏輯以完全避免重疊嗎?
  • 如果呼叫來自您不擁有的套件,請聯絡套件擁有者並詢問為什麼需要這些呼叫。它們可以減少或消除嗎?如果不是,您可能需要尋找另一個套件,或自行撰寫。

其他可能會觸發 saveLayer() 且潛在成本高的小工具

  • ShaderMask
  • ColorFilter
  • Chip—如果 disabledColorAlpha != 0xff,可能會觸發呼叫 saveLayer()
  • Text—如果存在 overflowShader,可能會觸發呼叫 saveLayer()

最小化不透明度和剪裁的使用

不透明度是另一個昂貴的操作,剪裁也是。以下是一些您可能會覺得有用的提示

  • 僅在必要時使用 Opacity 小工具。請參閱 Opacity API 頁面中的 透明圖片 區段,以取得直接套用不透明度到圖片的範例,這比使用 Opacity 小工具更快。
  • 與其將簡單形狀或文字包覆在 Opacity 小工具中,通常直接使用半透明顏色繪製會比較快。(不過這只適用於待繪製形狀中沒有重疊部分的情況。)
  • 若要實作影像淡入,請考慮使用 FadeInImage 小工具,它會使用 GPU 的片段著色器套用漸進式不透明度。如需更多資訊,請查看 Opacity 文件。
  • 裁剪不會呼叫 saveLayer()(除非明確使用 Clip.antiAliasWithSaveLayer 要求),因此這些作業不像 Opacity 那麼耗費資源,但裁剪仍然很耗費資源,因此請謹慎使用。預設情況下,裁剪會停用 (Clip.none),因此您必須在需要時明確啟用它。
  • 若要建立帶有圓角的矩形,請考慮使用許多小工具類別提供的 borderRadius 屬性,而不是套用裁剪矩形。

審慎實作格線和清單

格線和清單的實作方式可能會導致應用程式的效能問題。本節說明建立格線和清單時的一個重要最佳做法,以及如何判斷應用程式是否使用過多的配置通行證。

偷懶吧!

建立大型格線或清單時,請使用帶有回呼的延遲建立方法。這可確保在啟動時只建立畫面上可見的部分。

如需更多資訊和範例,請查看

避免內在

有關內在傳遞如何導致您網格和清單出現問題的資訊,請參閱下一部分。


最小化由內在操作造成的配置傳遞

如果您已經編寫過許多 Flutter 程式,您可能熟悉配置和約束在建立您的 UI 時如何運作。您甚至可能記住了 Flutter 的基本配置規則:約束向下傳遞。大小向上傳遞。父層設定位置。

對於某些小工具,尤其是網格和清單,配置流程可能會很昂貴。Flutter 致力於僅對小工具執行一次配置傳遞,但有時需要第二次傳遞(稱為內在傳遞),這可能會降低效能。

什麼是內在傳遞?

例如,當您希望所有儲存格都具有最大或最小儲存格的大小(或需要輪詢所有儲存格的類似計算)時,就會發生內在傳遞。

例如,考慮一個大型 Card 網格。網格應具有大小均勻的儲存格,因此配置程式碼會執行傳遞,從網格的根部(在小工具樹中)開始,要求網格中的每個卡片(不只是可見的卡片)傳回其內在大小,也就是小工具偏好的大小,假設沒有約束。有了這些資訊,架構會決定一個均勻的儲存格大小,並再次拜訪所有網格儲存格,告訴每個卡片要使用什麼大小。

除錯內在傳遞

若要判斷是否有過多的內在傳遞,請在 DevTools 中啟用追蹤版面配置選項(預設為停用),並查看應用程式的堆疊追蹤,以了解執行多少次版面配置傳遞。啟用追蹤後,內在時間軸事件會標記為「$runtimeType 內在」。

避免內在傳遞

有幾種選項可以避免內在傳遞

  • 預先將儲存格設定為固定大小。
  • 選擇特定儲存格作為「錨點」儲存格,所有儲存格的大小都會根據此儲存格調整。撰寫自訂的渲染物件,先定位子錨點,然後配置周圍的其他子物件。

若要更深入了解版面配置運作方式,請查看版面配置與渲染部分,該部分位於Flutter 架構概觀


在 16 毫秒內建置並顯示畫面

由於建置和渲染有兩個獨立執行緒,因此在 60Hz 顯示器上,建置有 16 毫秒,渲染有 16 毫秒。如果延遲是個問題,請在 16 毫秒或更短的時間內建置並顯示畫面。請注意,這表示在 8 毫秒或更短的時間內建置,並在 8 毫秒或更短的時間內渲染,總計 16 毫秒或更短。

如果你的畫面在設定檔模式中總計渲染時間遠低於 16 毫秒,即使有些效能陷阱適用,你可能不必擔心效能,但你仍應該盡可能快地建置並渲染畫面。為什麼?

  • 將畫面渲染時間降低到 16 毫秒以下可能不會產生視覺差異,但它改善電池續航力和熱能問題。
  • 它可能在你的裝置上執行良好,但請考慮你所鎖定的最低裝置效能。
  • 隨著 120fps 裝置越來越普及,你會希望在 8 毫秒內(總計)渲染畫面,以提供最流暢的體驗。

如果你想知道為什麼 60fps 能帶來流暢的視覺體驗,請查看影片 為什麼是 60fps?

陷阱

如果你需要調整應用程式的效能,或者使用者介面不如你預期的那麼流暢,DevTools 效能檢視 可以提供協助!

此外,IDE 的 Flutter 外掛程式可能也很有用。在 Flutter 效能視窗中,啟用顯示小工具重建資訊核取方塊。此功能可協助你偵測畫面在 16 毫秒以上渲染和顯示的時間。在可能的情況下,外掛程式會提供相關提示的連結。

下列行為可能會對應用程式的效能產生負面影響。

  • 避免使用 Opacity 小工具,特別是在動畫中避免使用。改用 AnimatedOpacityFadeInImage。如需更多資訊,請查看 不透明度動畫效能考量

  • 在使用 AnimatedBuilder 時,避免在建構函式中放置不依賴於動畫來建構小工具的子樹。此子樹會在動畫的每個刻度重建。相反地,一次建構子樹的那個部分,並將其作為子項傳遞給 AnimatedBuilder。如需更多資訊,請查看 效能最佳化

  • 避免在動畫中裁剪。如果可能,請在動畫之前預先裁剪影像。

  • 避免使用具有具體 List 子項的建構函數(例如 Column()ListView()),如果大多數子項在螢幕上不可見,則避免建置成本。

  • 避免在 Widget 物件上覆寫 operator ==。雖然它看起來似乎有助於避免不必要的重建,但實際上它會損害效能,因為它會導致 O(N²) 行為。此規則的唯一例外是葉面小工具(沒有子項的小工具),在這種特定情況下,比較小工具的屬性可能比重建小工具有效率得多,而且小工具很少會變更組態。即使在這種情況下,通常最好依賴快取小工具,因為即使覆寫一次 operator == 也可能導致全面的效能降低,因為編譯器無法再假設呼叫始終是靜態的。

資源

如需更多效能資訊,請查看下列資源