跳到主內容

效能最佳實踐

如何確保您的 Flutter 應用效能良好。

通常,Flutter 應用預設情況下效能良好,因此您只需要避免常見的陷阱即可獲得出色的效能。這些最佳實踐建議將幫助您編寫儘可能高效能的 Flutter 應用。

您如何設計 Flutter 應用以最有效地渲染您的場景?特別是,您如何確保框架生成的繪畫程式碼儘可能高效?某些渲染和佈局操作已知速度較慢,但並非總是可以避免。應謹慎使用它們,並遵循以下指導原則。

儘量減少代價高昂的操作

#

有些操作比其他操作更昂貴,這意味著它們消耗更多的資源。顯然,您只想在必要時才使用這些操作。您設計和實現應用的 UI 的方式會對應用的執行效率產生重大影響。

控制 build() 的成本

#

在設計 UI 時需要注意以下幾點

  • 避免在 build() 方法中進行重複且代價高昂的工作,因為當祖先小部件重建時,build() 可能會被頻繁呼叫。
  • 避免使用過大的單個小部件,其 build() 函式很大。根據封裝以及它們的變化方式將它們拆分為不同的小部件
    • 當在 State 物件上呼叫 setState() 時,所有後代小部件都會重建。因此,將 setState() 呼叫定位到 UI 實際需要更改的子樹的一部分。如果更改僅限於樹的一小部分,請避免在高層樹中呼叫 setState()
    • 遍歷以重建所有後代會在重新遇到與上一幀相同的子小部件例項時停止。此技術在框架內部被廣泛用於最佳化動畫,其中動畫不會影響子樹。請參閱 TransitionBuilder 模式和 SlideTransition 的原始碼,它使用此原理來避免在動畫時重建其後代。(“相同的例項”使用 operator == 進行評估,但請參閱本頁末尾的陷阱部分,瞭解何時避免重寫 operator ==。)
    • 儘可能使用小部件的 const 建構函式,因為它們允許 Flutter 跳過大部分重建工作。要自動提醒您儘可能使用 const,請啟用 flutter_lints 包中推薦的 lint。有關更多資訊,請檢視 flutter_lints 遷移指南
    • 要建立可重用的 UI 片段,請優先使用 StatelessWidget 而不是函式。

有關更多資訊,請檢視


使用 StringBuffer 以提高字串構建效率

#

當您需要從多個部分構建字串時,尤其是在迴圈內,使用 + 運算子可能效率低下,因為它在每次連線時都會建立一個新的 String 物件。更好的方法是使用 StringBuffer,它收集所有字串,並在呼叫 toString() 時才將它們連線起來。

在新的標籤頁上觀看 YouTube 影片:“StringBuffer (每週技巧)”


謹慎使用 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 intrinsics'。

避免固有傳遞

#

你有幾種避免固有傳遞的方法

  • 預先將單元格設定為固定大小。
  • 選擇一個特定的單元格作為“錨點”單元格——所有單元格都將相對於此單元格調整大小。編寫一個自定義 RenderObject,先定位子錨點,然後圍繞它佈局其他子項。

要更深入地瞭解佈局的工作原理,請檢視 佈局和渲染部分,以及 Flutter 架構概述


在 16 毫秒內構建和顯示幀

#

由於構建和渲染有兩個獨立的執行緒,在 60Hz 顯示器上,你有 16 毫秒用於構建,16 毫秒用於渲染。如果延遲是一個問題,請在 16 毫秒或更短的時間內構建並顯示一幀。請注意,這意味著構建時間在 8 毫秒或更短,渲染時間在 8 毫秒或更短,總共 16 毫秒或更短。

如果你的幀在 profile 模式下渲染的總時間遠低於 16 毫秒,你可能不必擔心效能,即使應用了一些效能陷阱,但你仍然應該努力盡快構建和渲染一幀。為什麼?

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

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

陷阱

#

如果你需要調整應用的效能,或者 UI 不如你預期的那樣流暢,DevTools Performance 檢視可以幫助你!

此外,你的 IDE 的 Flutter 外掛也可能很有用。在 Flutter Performance 視窗中,啟用 顯示小部件重建資訊複選框。此功能可幫助你檢測幀的渲染和顯示時間是否超過 16 毫秒。在可能的情況下,該外掛會提供指向相關提示的連結。

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

  • 避免使用 Opacity 小部件,尤其是在動畫中避免使用它。使用 AnimatedOpacityFadeInImage 代替。有關更多資訊,請檢視 不透明度動畫的效能注意事項

  • 在使用 AnimatedBuilder 時,避免在構建器函式中放置一個子樹,該子樹構建不依賴於動畫的小部件。該子樹將在動畫的每個刻度上重建。相反,一次性構建該子樹的一部分,並將其作為子項傳遞給 AnimatedBuilder。有關更多資訊,請檢視 效能最佳化

  • 避免在動畫中進行剪下。如果可能,在動畫化之前預先剪下影像。

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

  • 避免重寫 Widget 物件的 operator ==。雖然這似乎可以幫助避免不必要地重建,但在實踐中它會損害效能,因為它會導致 O(N²) 行為。此規則的唯一例外是葉子小部件(沒有子項的小部件),在比較小部件的屬性可能比重建小部件更有效,並且小部件很少更改配置的情況下。即使在這些情況下,通常最好依賴於快取小部件,因為即使對 operator == 進行一次重寫,也可能導致編譯器無法假定該呼叫始終是靜態,從而導致整體效能下降。

資源

#

有關更多效能資訊,請檢視以下資源