跳至主要內容

效能最佳實務

一般來說,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,而不是函式。

如需更多資訊,請查看


謹慎使用 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 毫秒或更短。

如果您的畫面在效能分析模式下的總渲染時間遠低於 16 毫秒,即使存在一些效能陷阱,您可能也不必擔心效能問題,但您仍然應該盡可能快速地建構和渲染畫面。為什麼?

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

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

陷阱

#

如果您需要調整應用程式的效能,或者 UI 不如您預期的流暢,DevTools 效能視圖可以提供幫助!

此外,您的 IDE 的 Flutter 外掛程式可能也很有用。在 Flutter 效能視窗中,啟用「顯示 widget 重建資訊」核取方塊。此功能可幫助您偵測畫面渲染和顯示時間是否超過 16 毫秒。在可能的情況下,外掛程式會提供相關提示的連結。

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

  • 避免使用 Opacity widget,尤其避免在動畫中使用。請改用 AnimatedOpacityFadeInImage。如需更多資訊,請查看 不透明度動畫的效能考量

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

  • 避免在動畫中進行裁切。如果可以,請在動畫之前預先裁切圖片。

  • 如果螢幕上看不到大部分子項,請避免使用具有具體子項 List 的建構子(例如 Column()ListView()),以避免建構成本。

  • 避免在 Widget 物件上覆寫 operator ==。雖然看起來可以透過避免不必要的重建來提供幫助,但在實務上,它會因為導致 O(N²) 行為而損害效能。此規則的唯一例外是葉節點 widget(沒有子項的 widget),在特定情況下,比較 widget 的屬性可能比重建 widget 有效率得多,並且 widget 的組態很少會變更。即使在這種情況下,通常最好依賴快取 widget,因為即使只有一次覆寫 operator ==,也會導致整體效能下降,因為編譯器不再能假設呼叫永遠是靜態的。

資源

#

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