Flutter 應用性能優化最佳實踐

Flutter 應用性能優化最佳實踐

通常來說,Flutter 技術構建的應用程序在默認情況下都是高性能的。所以你只需要避開常見的陷阱,就可以獲得優異的性能,而不需要使用複雜的分析工具對細節做優化。這些最佳建議將ben

1. 最佳實踐

如何設計一個能最有效地渲染頁面的 Flutter 應用程序?特別是如何確保底層框架生成的繪圖代碼儘可能高效?這裡有幾件需要你在設計應用時考慮的事情:

1.1 控制 build 方法的耗時

  • 避免在 <code>build/<code>方法中進行重複且耗時的工作,因為當父 Widget 重建時,子 Wdiget 的<code>build/<code>方法會被頻繁地調用。

  • 避免在一個超長的 <code>build/<code>方法中返回一個過於龐大的 Widget。把他們分拆成不同的 Widget,並進行封裝,另外他們要這樣改變:

    • 當在 State 上調用 <code>setState/<code>時,所有後代 Widget 都將重建。因此,將<code>setState/<code>的調用轉移到其 UI 實際需要更改的 Widget 子樹部分。如果改變的部分僅包含在 Widget 樹的一小部分中,請避免在 Widget 樹的更高層級中調用<code>setState/<code>。

    • 當重新遇到與前一幀相同的子 Widget 實例時,將停止遍歷。這種技術在框架內部大量使用,用於優化動畫不影響子樹的動畫。請參閱 TransitionBuilder 模式和使用此原則的 SlideTransition,以避免在動畫過程中重建其後代 Widget。

另見:

  • <code>StatefulWidget/<code>API 文檔的 Performance considerations 部分。

1.2 僅當需要的時候才應用效果

由於代價很大,請謹慎使用效果。一些效果的背後調用了性能代價很大的 <code>saveLayer/<code>方法。

為什麼 saveLayer 代價很大?

調用 <code>saveLayer/<code>會開闢一片離屏緩衝區。將內容繪製到離屏緩衝區可能會觸發渲染目標切換,這些切換在較早期的 GPU 中特別慢。

一些在使用效果時的通用規則:

  • 能不用 <code>Opacity/<code>Widget,就儘量不要用。有關將透明度直接應用於圖像的示例,請參見 Transparent image,這比使用 Opacity widget 更快。

  • Clipping 不會調用 <code>saveLayer/<code>(除非明確使用<code>Clip.antiAliasWithSaveLayer/<code>),因此這些操作沒有 Opacity 那麼耗時,但仍然很耗時,所以請謹慎使用。

其他會觸發 <code>saveLayer/<code>的 widget,可能也會代價高昂。

  • <code>ShaderMask/<code>

  • <code>ColorFilter/<code>

  • <code>Chip/<code>— 當<code>disabledColorAlpha != 0xff/<code>的時候,會調用<code>saveLayer/<code>

  • <code>Text/<code>—might cause call to<code>saveLayer/<code>if there’s an<code>overflowShader/<code>

[Text]— 當有 <code>overflowShader/<code>時,會調用<code>saveLayer/<code>

避免調用 <code>saveLayer/<code>的方式:

  • 要在圖像中實現淡入淡出,請考慮使用 FadeInImage 小部件,該小部件使用 GPU 的片段著色器應用漸變不透明度。瞭解更多詳情,請參見 <code>Opacity/<code>文檔。

  • 要創建帶圓角的矩形,而不是應用剪切矩形,請考慮使用很多 widget 都提供的 <code>borderRadius/<code>屬性。

1.3 對列表和網格列表懶加載

在構建大型網格或列表時,使用帶有回調的惰性方法。這樣,只有屏幕的可見部分是在開始時構建的。

請參閱:

  • 實用教程裡的 長列表的處理

文檔

來自社區的 AbdulRahman AlHamali 撰寫的 <code>Creating a ListView that loads one page at a time/<code>

<code>Listview.builder/<code>API

1.4 在 16ms 內渲染完成每一幀

由於構建和渲染有兩個獨立的線程,因此構建時間為 16ms,60Hz 顯示器上渲染時間為 16ms。如果需要考慮延遲,就要在 16ms 或更短 的時間內構建和顯示幀。請注意,這意味著構建需要少於 8ms,渲染也需要少於 8ms,總計 16ms 或更短。如果需要考慮丟幀(jankyness),那麼每個構建和渲染階段的 16ms 都可以。

如果在 profile 構建 狀態下,每一幀渲染時間低於 16ms,你可能不必擔心性能問題以及一些性能陷阱,但仍然應該致力於儘可能快地渲染每一幀。為什麼?

  • 將幀渲染時間降低到 16ms 以下可能在視覺上看不出來什麼變化,但可以延長電池壽命以及避免發熱問題。

  • 可能在你當前測試設備上運行良好,但請考慮在應用所支持的最低端設備上的情況。

  • 當 120fps 的設備普及之後,便需要在 8ms 之內完成每一幀的渲染來保證流暢平滑的體驗。

如果你想弄明白為什麼 60fps 會帶來平滑的視覺體驗,請看視頻 <code>Why 60fps?/<code>

2. 陷阱

如果你需要調整應用程序的性能,或者 UI 順暢度沒達到你的預期,那麼 IDE 的 Flutter plugin 可以提供幫助。在 Flutter Performance 窗口中,勾選 Show widget rebuild information 複選框。此功能可幫助你檢測幀的渲染和顯示時間是否超過 16ms。在可能的情況下,插件提供指向相關提示的鏈接。

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

  • 避免使用 <code>Opacity/<code>widget,尤其是在動畫中避免使用。請用<code>AnimatedOpacity/<code>或<code>FadeInImage/<code>進行代替。更多信息,請參閱<code>Performance considerations for opacity animation/<code>。

  • 使用 AnimatedBuilder 時,請避免在不依賴於動畫的 widget 的構造方法中構建 widget 樹。動畫的每次變動都會重建這個 widget 樹。而應該構建子樹的那一部分,並將其作為 child 傳遞給 AnimatedBuilder。

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

  • 如果大多數 children widget 在屏幕上不可見,請避免使用返回具體列表的構造函數(例如 <code>Column()/<code>或<code>ListView()/<code>),以避免構建成本。


分享到:


相關文章: