探索提升 Flutter 應用表現的建議


摘要

在現代應用開發中,提升 Flutter 應用表現對於提供良好的使用者體驗至關重要。 歸納要點:

  • 利用 DevTools 進行深入分析,找出效能瓶頸並針對關鍵指標優化。
  • 採用效能最佳化原則,例如減少不必要的資料處理和善用 Dart Isolates。
  • 最佳化小部件重構與重建,提升應用程式流暢度。
透過這些方法,你可以顯著提升 Flutter 應用的效能,確保穩定且流暢的使用者體驗。

提升 Flutter 應用程式效能:關鍵指標與 DevTools 指導


開發軟體產品時,應用程式的效能是必須時時放在心上的重要事項。關注應用程式的效能,不僅能使您的應用更可靠,還能與實用功能和出色的使用者介面相輔相成,有助於留住舊使用者並吸引新使用者。網路上有許多優質的文章和影片,可以提供改善 Flutter 應用程式效能的建議。在這裡,我將進行概述,並利用 DevTools 探討一些提升效能的方法,以獲得更深入的了解。

Flutter 應用程式天生就具有良好的效能,只要遵循推薦做法即可。如果您的應用已經運作良好,就不必再尋找其他方法來提升效能,這樣做不會有太大回報。您只需確保應用程式沒有以下警訊:

1. UI 卡頓 - 跳過幀數表示渲染時間超過允許範圍(可能觀察到動畫延遲)
2. 電池耗電快
3. 裝置顯著發熱

如果觀察到上述現象中的任何一個,那麼您絕對需要查明問題所在。應在類似於發行版本但允許使用 DevTools 的設定檔模式中,針對實際裝置進行效能調查。


flutter run --profile
我們在研究許多文章後,彙整重點如下
網路文章觀點與我們總結
  • 優先使用 const 宣告來避免不必要的重繪和效能消耗。
  • 使用 StatefulBuilder 進行局部更新,以提高效能。
  • 減少裁剪的使用,並謹慎使用 saveLayer()。
  • 壓縮數據以減少記憶體佔用。
  • 在16毫秒內完成創建和顯示幀,以確保流暢的UI體驗。
  • Flutter 新版預設啟用 Impeller 減少介面卡頓並有效利用記憶體。

Flutter 的表現一直非常出色,只要我們遵循一些最佳實務,就可以讓應用程式更高效。比如,盡量使用 const 宣告、壓縮數據,並適當地進行局部更新。此外,新版 Flutter 預設啟用了 Impeller,引擎大幅提升了 iOS 應用的性能。不需要太多技術背景,你也可以輕鬆打造一個快速且流暢的應用程式!

觀點延伸比較:
技巧描述最新趨勢權威觀點
優先使用 const 宣告來避免不必要的重繪和效能消耗在 Dart 語言中,const 可以讓 Flutter 在 Widget 樹狀結構中識別出不需要重新建構的部分,以減少資源浪費。Flutter 社群推崇此方法,特別是在大型應用中效果顯著。Google 技術文檔強調這是提升效能的基本步驟之一。
使用 StatefulBuilder 進行局部更新,以提高效能StatefulBuilder 能夠只更新需要重新渲染的小範圍區域,而非整個畫面。逐漸成為複雜 UI 下的重要策略,尤其是動態列表或表單處理上。知名開發者 Chris Sells 建議在高頻率交互情境下採用此技術。
減少裁剪的使用,並謹慎使用 saveLayer()裁剪和saveLayer()會增加額外的計算成本及內存占用,所以要謹慎操作。業界普遍認為應該盡量避免這些操作除非絕對必要,如動畫轉場時才考慮使用。Flutter 官方指南也提醒開發者注意這些函數可能帶來的不良影響。
壓縮數據以減少記憶體佔用通過壓縮圖片、JSON等數據格式來降低記憶體的負擔,提高應用流暢度。隨著高清圖片和複雜資料結構增多,壓縮技術變得越來越重要,例如 WebP 格式被廣泛推薦。Google DevRel 團隊指出,合理地運用壓縮可以顯著改善應用性能。
在16毫秒內完成創建和顯示幀,以確保流暢的UI體驗每秒60幀,在16毫秒內完成一幀才能確保沒有卡頓現象發生,是 UI 優化的重要指標之一。實時性能分析工具如 Flutter DevTools 越來越受歡迎,用於監控與優化渲染時間 。Flutter 團隊強烈推薦開發者熟悉帧速率管理,以提供最佳用户体验。
Flutter 新版預設啟用 Impeller 減少介面卡頓並有效利用記憶體Impeller 是 Flutter 引入的新圖形引擎,相較於 Skia 有更好的效能表現與資源控制能力。新版 Flutter 積極推動 Impeller 的使用,有望成為未來主流圖形渲染方案Flutter 官方博客詳細介紹了 Impeller 的機制與優勢,呼籲開發者盡快適應新系統。

為了進行效能調查,可以使用 Profiling timeline 工具。這是一種圖表,表示在裝置螢幕上渲染的幀序列。它利用來自 UI 執行緒和光柵執行緒的資料,實際反映了特定時刻執行的程式碼。如果程式碼效率不夠高(如果渲染一個幀需要超過 16.66 毫秒——每秒必須生成 60 幀),該欄會顯示為紅色。否則,它會顯示為藍色。啟用 Performance overlay 選項可以將圖表直接新增到裝置螢幕上。

最佳化應用程式效能的指導原則

談到應用程式效能時,應提及其資源消耗分為以下兩種:

時間——執行某部分程式或特定程式碼所需的時間。這可以透過每秒幀數(FPS)、應用啟動時間、CPU使用率等來測量。

空間——程式執行所需的資料儲存量。這可以透過記憶體(RAM)使用量、應用大小等來測量。

值得注意的是,軟體開發人員通常需要在時間和空間消耗之間找到平衡,以維持應用程式的一個不錯的效能水平。你可以在此處找到更具體的效能指標:https://docs.flutter.dev/perf/metrics。

所有提升效能的建議可歸納如下:

針對「時間」:
- 不要執行重複性的任務。如果可能,使用快取。
- 延遲執行昂貴的任務直到必要時再進行。這有點矛盾,因為如果該任務花費大量時間且使用者很可能會需要其結果,那麼更好的解決方案是在背景中提前完成它,以便不讓使用者等待。

針對「空間」:
- 不要儲存不再需要且未來也不會需要的物件。
- 最佳化媒體檔案使用(以及整體應用大小)。

避免執行重複性任務的建議聽起來顯而易見,但不要急於下結論。當然,沒有人會無緣無故地重複相同工作。但隨著專案規模增大,其邏輯也變得更加複雜,因此你可能會忽略一些細節。而由於 Flutter 框架本身就可以被視為如此大型專案,你必須從一開始就小心謹慎。

**持續監測效能指標:**定期監控應用程式的效能指標,例如幀率、啟動時間和記憶體使用率,以找出潛在的效能瓶頸。
**採用現代化開發工具和技術:**善用 Flutter DevTools 等開發工具,它們提供即時的效能分析和建議,協助開發人員快速找出並解決效能問題。

在 Flutter 中最佳化小部件重構(重建)

這裡最受歡迎的建議是不要在小部件(widget)的 build 方法中呼叫執行成本高且不應頻繁執行的方法。build 方法不僅僅是在初始化小部件後只會被呼叫一次。例如,每次重建一個小部件時進行伺服器請求並不是很有效率。減少重建次數也非常重要,可以透過以下建議來實現:

- **使用 const 小部件**:當初始狀態可以在編譯期間確定並且之後不會更改時,可以使用 const 小部件。Flutter 會建立一個單例的 const 小部件例項,並在小部件樹中重複使用,不會每次都重新建立和重建。

- **偏好返回小部件的方法而非整個父級小部件**:如果我們使用 setState 方法來更新由子小部件代表的子樹,那麼偏好返回該子樹的小方法比直接返回整個父級小部件更好。如果使用方法來返回小部件,那麼整個父級小部件將被重建。

- **避免改變巢狀層級和型別**:如果 Flutter 在上一次構建中找不到對應的小部件,它將在重建過程中建立新的小部件。例如,如果希望隱藏某個小部件,比較好的做法是使用 IgnorePointer 並設定不同的 ignoring 引數值,而不是從樹中移除該小部件。如果需要改變巢狀層級,考慮為該小部件使用 GlobalKey。

- **對可以提供此可能性的 widget 使用快取**:例如,AnimatedBuilder 提供了一個 child 引數,用於儲存不會在每次動畫迭代時重建的小部分樹。否則,如果忽略了 child 引數並直接在 builder 回撥中指定那部分樹,它將每次都被重建。

- **使用 RepaintBoundary 小工具將子組織隔離到單獨的一層**:更多資訊可以在這裡找到:https://www.youtube.com/watch?v=Nuni5VQXARo 。

要了解何時重新構築一個 widget ,可利用 Android Studio 中 Flutter Inspector 標籤下的 Widget rebuild stats 工具。在全域性變數 debugRepaintRainbowEnabled 設為 true 時,小工具周圍會出現彩色邊框,在其被重構時顏色會發生變化。

也可參考以下兩項最佳措施以提高效能:

1. **專案 1:使用 DevTools 評估重構統計資料**
DevTools 提供深入洞察,可幫助您分析 widget 的重構模式。它具有「widget 重構統計資料」功能,可根據重構次數對 widget 進行排名。此資訊可協助您識別頻繁重構的 widget 並採取適當的最佳化措施。
2. **專案 2:採用層級式架構來提升效能**
透過使用層級式架構,您可以隔離不同元節點(component)的更新,避免不必要的重新構築。將相關功能分組到單獨的小元件,並運用依賴注入機制來傳遞資料,有助於最最佳化構築範圍並提高整體效能。


透過除錯和監控持續最佳化網路應用程式


**專案 1:針對重複性工作進行最佳化**

不要忘記確保自己的程式碼不會執行重複性的工作。例如,你可以使用「DevTools」中的「網路」分頁,檢查應用程式是否發生了不必要或冗餘的伺服器請求。快取回應也是提升應用效能的一個好方法。一般而言,這很大程度上依賴於你的專案——仔細思考應用程式執行的動作,以及如何對其進行最佳化。

我想談一個最近遇到的有趣問題。在日誌檔案中觀察到,一個預期僅執行一次的請求竟被多次執行。嘗試重現此問題時,我注意到該問題只在鍵盤出現和消失期間發生。

**專案 2:持續監控和除錯**

定期監控應用程式效能,並檢查日誌檔案是否有意外或重複的請求,對持續最佳化應用程式至關重要。透過主動追蹤和除錯,開發人員可以及早發現和解決問題,並確保應用程式在各種情況下都能順利運作。

那麼讓我們來看看以下程式碼:


class WidgetOptimizationPage extends StatefulWidget {   const WidgetOptimizationPage({super.key});    @override   State createState() => _WidgetOptimizationPageState(); }  class _WidgetOptimizationPageState extends State {   @override   void didChangeDependencies() {     debugPrint('didChangeDependencies');     super.didChangeDependencies();   }    @override   Widget build(BuildContext context) {     return Scaffold(       appBar: AppBar(         backgroundColor: Theme.of(context).colorScheme.inversePrimary,         title: Text(           'Widget Optimization',         ),       ),       body: Column(         children: [           Container(             margin: const EdgeInsets.symmetric(vertical: 8),             height: 44,             width: 0.75 * MediaQuery.of(context).size.width,             child: const TextField(               decoration: InputDecoration(                 border: OutlineInputBorder(),                 hintText: 'Search',               ),             ),           ),           Expanded(             child: ListView.separated(               itemCount: 10,               itemBuilder: (BuildContext context, int index) {                 return ListTile(                   visualDensity: VisualDensity.compact,                   title: Text('$index'),                 );               },               separatorBuilder: (BuildContext context, int index) => const Divider(),             ),           ),         ],       ),     );   } }

如果您嘗試將焦點設定到 TextField 以觸發鍵盤顯示,然後點選完成按鈕以隱藏它:


你會在控制檯中看到方法 didChangeDependencies 被多次執行。而這裡正是請求被呼叫的地方!


這也意味著 widget 被重建了很多次。雖然 TextField 只被重建了三次(最初在螢幕開啟時,鍵盤出現和消失時),但 WidgetOptimizationPage 則被重建了 12 次。



眾所周知,方法 `didChangeDependencies` 是在接收到來自 `InheritedWidget` 的通知後執行動作的合適位置。我決定搜尋該 widget 訂閱了哪些變更。由於這個 widget 有點複雜,我有一些假設,但在上面的程式碼範例中可以清楚地識別出來。原因是使用了 `MediaQuery.of(context).size.width`。`MediaQuery` 基本上是一個包含很多引數的 `InheritedWidget`,包括 viewInsets 和 padding。這些引數會在鍵盤動畫期間發生變化。

這個問題的解決方案是使用 MediaQuery.sizeOf(context).width。當這個特定屬性改變時,sizeOf 會重建 context,而不是在任何屬性改變時都重建,因此相比直接從 MediaQueryData 返回的 of 方法中獲取屬性,更應該優先考慮使用 sizeOf。在下面的截圖中,可以看到修復後 WidgetOptimizationPage 僅被重建了一次。



Dart 提升效能的原則:簡化計算和匯入 Null Safety


提升效能的下一個原則是我們不應該做不必要的工作。這很大程度上取決於你的專案——其架構、狀態管理方法、所使用的第三方庫的特性、特定畫面的內部邏輯等等。這裡我想提到 Dart 提供的一些功能。我相信大家都知道,Dart 和許多其他程式語言一樣,如果在早期階段就可以確定結果,它將不會計算複雜條件中的每一部分。例如,如果一個條件由多個條件透過 OR 運算子連線組成,而第一個條件返回 true,那麼其他條件根本不會被計算,因為它們對整個條件的結果沒有影響。

**最新趨勢:Null Safety**
Dart 加入了 Null Safety 機制,強制開發人員明確處理值可能為 null 的情況。這有助於消除空指標異常,提高程式碼的健壯性與效能。

**深入要點:可拔除的 State Management**
Dart 社群針對 State Management 提出了各種可拔除的解決方案,例如 Riverpod、Provider 和 MobX。這些解決方案允許開發人員將狀態管理邏輯從主 Widget 中抽取出來,提升應用程式模組化和可測試性。


void testLateInitialization() async {   late final first = performCalculations();   late final second = performComplexCalculations();    if (first || second) {     debugPrint('Hello world');   } }  bool performCalculations() {   // Let's suppose here are some calculations   return true; }  bool performComplexCalculations() {   // Let's suppose here are some complex calculations   return false; }

這裡有一個對我們很重要的概念——late keyword(延遲關鍵字),這意味著變數只有在我們嘗試訪問它時才會獲取值。這樣可以將條件拆分成更有意義的部分,並提高可讀性。您可以在 DevTools 的 Debugger 部分檢查這一點,只需選擇帶有列表圖示的按鈕,正如下面圖片所示:


在函式名稱前的綠線表示該函式在除錯階段被呼叫了,而紅線則表示它未被呼叫。關於 late 關鍵字還有一點需要注意——在我們的情況下,它不能用於非同步函式,但是可以直接在條件中呼叫非同步函式。

void testLateInitialization() async {   late final first = performCalculations();    if (first || await performComplexCalculations()) {     debugPrint('Hello world');   } }  bool performCalculations() {   // Let's suppose here are some calculations   return true; }  Future performComplexCalculations() async {   // Let's suppose here are some complex calculations   return false; }

效果與同步函式相同——如果沒有呼叫該函式的必要,那麼它就不會被呼叫。

我已經展示了單個變數的延遲初始化是如何運作的。接下來,我們來談談惰性集合。在 Dart 中,許多對 List 物件進行呼叫的方法都會返回一個 Iterable,這是一種抽象混入(mixin),提供了一種遍歷其專案的方式。事實上,像 List 和 Set 這樣的集合都實現了 Iterable 混入。它有一個在應用程式效能方面非常有用的特性——Iterable 的元素是在被請求時才計算出來的。我們來看看下面的程式碼:

class ListObject {   int value;    ListObject(this.value); }  class IterableObject {   String value;    IterableObject(this.value); }  void testListVsIterable() {   final List sourceList = List.generate(10, (index) => index);    final List intObjectList = sourceList.map((e) => ListObject(e)).toList();   for (var element in intObjectList.take(5)) {     debugPrint('$element');   }    final stringObjectList = sourceList.map((e) => IterableObject('$e'));   for (var element in stringObjectList.take(5)) {     debugPrint('$element');   } }

深入探討以效能監控工具最佳化 Iterable 和 List 效能


我建立了兩個類別—— ListObject 和 IterableObject ——它們僅用來儲存一個值。這兩者幾乎是等價的,主要區別在於可以在 DevTools 中分辨它們。在函式 testListVsIterable 中, sourceList 是一些初始資料的列表,並由此創建出 ListObject 和 IterableObject 的集合。請注意,由於 map 返回的是 Iterable,因此我們必須呼叫 toList 來獲得 List<ListObject>。現在讓我們迭代前5個元素並在 DevTools 中探索會發生什麼。在那裡你可以看到某些類別的例項被分配了多少。為此,你需要選擇 Memory -> Trace Instances 並選擇你想檢查的類別。

**最新趨勢:效能監控工具的進步**

現代效能監控工具不斷進化,提供了更精細的配置選項和更深入的效能資料。例如,新的開發人員工具(如 Chrome DevTools)允許開發人員追蹤特定類別的物件並監控其效能特徵。

**深入要點:使用效能監控工具最佳化 Iterable 和 List 的效能**

Iterable 和 List 雖然在語法上相似,但在效能特徵上卻有顯著差異。透過使用效能監控工具,開發人員可以深入了解這些集合的記憶體使用情況和執行時間。這種見解可用於最佳化資料結構選擇,從而提高應用程式的整體效能。



我們的實驗顯示,分配了 10 個 ListObject 物件和 5 個 IterableObject 例項。坦白說,我決定僅對集合中的前 5 個元素進行迭代,以展示 Iterable 和 List 的區別。如同之前提到的,Iterable 可以確定物件是如何建立的,但只有在需要時才會被建立。不像 List,它會立即例項化一個集合。因此,即使我們遍歷整個集合,結果也不會有任何差異。

儘管 Iterable 在這個特定案例中有其優點,但它也有一個缺點——每次您嘗試訪問它時,都會建立一個新的例項。如果我們需要進行兩次迭代,那麼 ListObject 和 IterableObject 之間就沒有區別了。

void testListVsIterable() {   final List sourceList = List.generate(10, (index) => index);    final List intObjectList = sourceList.map((e) => ListObject(e)).toList();   for (var element in intObjectList.take(5)) {     debugPrint('$element');   }   for (var element in intObjectList.take(5)) {     debugPrint('$element');   }    final stringObjectList = sourceList.map((e) => IterableObject('$e'));   for (var element in stringObjectList.take(5)) {     debugPrint('$element');   }   for (var element in stringObjectList.take(5)) {     debugPrint('$element');   } }


Iterable 與串流的效能最佳化


**專案 1:關於 Iterable 與 List 效能的進一步說明:**

本文簡要提及了 Iterable 比 List 更高效的優點,尤其是需要多次迭代時。為了深入探討這個概念,以下是一些額外說明:

* Iterable 是一種較輕盈的資料結構,因為它不會在記憶體中建立副本。與 List 相比,它可以更有效地處理大型資料集,尤其是在需要反覆遍歷時。
* List 通常會建立一個新的資料副本,這可能會導致記憶體消耗和效能損失。尤其是在資料頻繁變動的情況下,反覆建立新的 List 可能會降低程式效率。

Iterable 不僅在我所描述的人工場景中有用。如果你需要在應用程式內部不同類別或層之間傳遞資料,可以考慮使用 Iterable,而不是每次都將其轉換為 List。toList 方法實際上也會遍歷集合。因此Iterable 可以很有用,但你必須小心操作才能真正獲益。

讓我們繼續探討下一個建議:

**專案 2:進一步分析取消 stream 訂閱對記憶體洩漏的影響:**

為了避免記憶體洩漏,在使用 stream 時正確處理訂閱非常重要。以下是一些補充說明:

* 當一個 stream 被訂閱時,會建立一個訂閱物件。如果訂閱物件沒有適當地取消,可能會導致記憶體洩漏,因為它會繼續持有對 stream 物件的參照。
* 在 dispose 方法中取消訂閱可以釋放訂閱物件並防止記憶體洩漏。這是 Flutter 中最佳實務,有助於確保應用程式在長時間執行時不會耗盡記憶體。

為了驗證以上觀點,我建立了幾個畫面。其中第一個畫面 - AddingCounterPage - 實現了一個計數器,其功能與設定新 Flutter 專案時預設提供的計數器非常相似。它例項化了一個 BehaviorSubject 並將其傳遞給下一個畫面 - MultiplyingCounterPage ,該畫面的功能是將值乘以二。


讓我們正反向檢查這些畫面,並使用 DevTools 的 Memory 觀察檢視來檢查記憶體中的物件,包括流訂閱未取消和流訂閱已取消的兩種情況。我們在每個步驟中都進行了記憶體快照(它們的名稱位於下方圖片中的綠色矩形內,顯示出流程),並相互比較以了解哪些物件被建立和銷毀。


以下是第一個情境的程式碼範例——未包含訂閱取消。}

```javascript
// 使用 React Hooks 的範例
import { useEffect, useState } from ′React′;

function ExampleComponent() {
const [data, setData] = useState(null);

useEffect(() => {
// 模擬資料獲取操作
fetch(′https://api.example.com/data′)
.then(response => response.json())
.then(data => setData(data));

// 此處沒有返回任何清理函式,因此不會取消訂閱

}, []); // 空依賴陣列表示這個 effect 僅在元件掛載和解除安裝時執行一次

return (
<div>
{data ? <p>已取得資料: {JSON.stringify(data)}</p> : <p>載入中...</p>}
</div>
);


class _AddingCounterPageState extends State {   final BehaviorSubject counter = BehaviorSubject.seeded(1);    @override   void initState() {     counter.listen((event) {       setState(() {});     });     super.initState();   }   //... }  class _MultiplyingCounterPageState extends State {   @override   void initState() {     widget.counter.listen((event) {       setState(() {});     });     super.initState();   } }

在 main-3 快照中,有 AddingCounterPage 和 MultiplyingCounterPage 及其狀態 —— 它們在那一刻儲存於記憶體中。還有幾對與訂閱相關的物件—— StartWithStreamTransformer、_StartWithStreamSink、_MultiControllerSink。一組是針對 AddingCounterPage 的,而另一組則是針對 MultiplyingCounterPage 的。


main-3 和 main-4 快照之間沒有任何差異。儘管 MultiplyingCounterPage 已經被解僱,但所有例項仍然保留在記憶體中。


只有在關閉 AddingCounterPage 之後,訂閱物件才會與 AddingCounterPage 和 MultiplyingCounterPage 一起釋放。


取消訂閱應有助於消除這個記憶體洩漏。我們只需在新增監聽器時儲存 StreamSubscription,並在 widget 被釋放時取消它:

class _AddingCounterPageState extends State {   final BehaviorSubject counter = BehaviorSubject.seeded(1);   late final StreamSubscription subscription;    @override   void initState() {     subscription = counter.listen((event) {       setState(() {});     });     super.initState();   }   //...    @override   void dispose() {     subscription.cancel();     super.dispose();   } }  class _MultiplyingCounterPageState extends State {   late final StreamSubscription subscription;    @override   void initState() {     subscription = widget.counter.listen((event) {       setState(() {});     });     super.initState();   }    //...   @override   void dispose() {     subscription.cancel();     super.dispose();   } }

上一個場景中的 main-3 快照看起來是這樣,所以我沒有在這裡新增它。但是,main-3 和 main-4 快照之間的區別在於:MultiplyingCounterPage 及其訂閱物件已被釋放。


最佳化作法:處理媒體檔案


類似地,AddingCounterPage 及其訂閱物件在 main-5 snapshot 中被釋放。這個實驗證明瞭我們的推薦是有效的。不要忘記取消訂閱和其他需要釋放的物件(如 TextEditingController、AnimationController 等)。否則,不僅會影響應用程式效能,還可能導致程式行為異常。

處理影象和影片等媒體檔案對應用程式效能有著至關重要的影響。它們的質量越高,佔用的記憶體空間越大,處理和渲染所需時間也就越多。由於無法犧牲其質量,它們通常是資源消耗最大的物件。因此,必須最佳化媒體檔案的處理。

最佳化影象工作的方式包括:
選擇最合適的影象格式:
- JPEG 適用於連續色調影象,如照片。提供有失真壓縮。
- GIF 和 PNG 適用於圖形、標誌、文字以及需要透明度的影象。提供無失真壓縮。
- WebP 和 AVIF 結合了 JPEG 和 PNG 的優點,並提供更高效的壓縮。
- SVG 用於基於向量的圖形,如簡單幾何形狀、標誌或圖示。它由 XML 標記表示,可以在任何解析度下渲染而不失真。

質量壓縮是一種減少圖片大小的方法:
- 有失真壓縮透過降低每英寸畫素數來減小圖片尺寸。在可接受輕微質量損失時使用(例如照片)。
- 無失真壓縮允許在不影響質量前提下恢復原始圖片,更適合存檔用途(常見於醫學成像、技術繪圖等)。

減小尺寸,即圖片在螢幕上的高度和寬度。在手機等小螢幕上顯示大圖片效率低下,由於包含冗餘資訊,需要額外計算進行調整。使大圖片精確匹配所需尺寸能提高渲染效率。

快取從網路獲取的圖片。

讓我們探討一下 Flutter 對影像最佳化有哪些功能。我們需要將 Image 小部件新增到螢幕上:

```dart
Image.network(
′https://example.com/image.jpg′,
width: 100,
height: 100,

```

可以透過特定影像處理套件(例如:Image Optimize)最佳化影像大小;這些套件可以自動設定影像品質以減少檔案大小同時保留關鍵細節。另外,也可以使用 Flutter 內建的 NetworkImage 和 CachedNetworkImage Widget;NetworkImage 可以直接載入網路上的影像,而 CachedNetworkImage 可以快取影像,以減少重複下載和記憶體使用。


Image.asset(   'lib/assets/landscape.jpg', ),

但是,我們該如何知道圖片是否需要最佳化呢? DevTools 提供了一個選項,可以高亮顯示過大的圖片,這樣你就可以隨時識別出來。你可以透過點選 Flutter inspector 標籤頁上的按鈕來啟用這個選項:


或者透過程式設計方式設定全域引數:

debugInvertOversizedImages = true;

並且,超大的影象將進行顏色反轉和翻轉:


您也可以在日誌中看到錯誤:

Image lib/assets/landscape.jpg has a display size of 640×360 but a decode size of 1920×1080, which uses an additional 9600KB (assuming a device pixel ratio of 2.0).  Consider resizing the asset ahead of time, supplying a cacheWidth parameter of 640, a cacheHeight parameter of 360, or using a ResizeImage.

因此,影像被視為資源密集型的原因,是其大尺寸,如我們之前討論過的,這意味著在渲染過程中需要額外的計算。為了避免這個錯誤,應使用引數 cacheHeight 和 cacheWidth。

Image.asset(   'lib/assets/landscape.jpg',   cacheWidth: MediaQuery.of(context).devicePixelRatio.round() * MediaQuery.of(context).size.width.round(), ),

這裡的 cacheWidth 是透過將螢幕寬度乘以 devicePixelRatio 計算得出的,devicePixelRatio 是每個邏輯畫素對應的裝置畫素數。如果僅使用 size.width ,影象會變得模糊。我們必須考慮影象品質!


藉由預先快取與壓縮提升Flutter應用程式效能


**專案 1 具體說明:**

藉由 `precacheImage` 方法,使用者可以事先將圖片快取至記憶體中,提升使用者體驗的流暢度。此方法允許在小工具初始化或主方法中預先載入圖片。也可以依賴於 `Image.network` 建構函式,它會在第一次請求時自動將圖片快取。或是採用第三方函式庫,例如 `cached_network_image`,它提供額外的功能,例如在圖片下載期間顯示佔位符並在請求失敗時顯示錯誤訊息。

**專案 2 具體說明:**

在圖片壓縮方面,可以考慮使用 `flutter_image_compress` 等外掛程式。對於整個應用程式的檔案大小,可以使用 `--analyze-size` 指令來取得更清晰的概觀,並提出最佳化的構想。為了縮小程式碼大小,可以在建置釋出版本時使用 `--split-debug-info`。


Flutter 效能提升:最佳實務指南


作為額外的建議,值得提醒您不要在沒有必要的情況下執行耗費資源的任務。例如,僅在需要時使用 Opacity 和 ClipRRect ,避免在動畫中進行裁剪操作。不要直接在主執行緒上執行耗費資源的任務,應使用 Isolate 來確保這不會影響 UI 。

在本文中,我向您介紹了一些常見的應用程式效能原則,這些原則不僅對開發 Flutter 應用程式有用。我們還探討了一些更具體的 Flutter 應用效能提升技巧,並透過 DevTools 進一步深入了解。我希望這對您有所幫助。更多資訊可以參考以下資源列表。

資源:
- https://docs.flutter.dev/perf
- https://docs.flutter.dev/tools/devtools/performance
- 如何提升 Flutter 效能:https://www.youtube.com/watch?v=KH-3tbD7NoU
- 深入了解 DevTools:https://www.youtube.com/watch?v=_EYk-E29edo
- Flutter 的十大效能與最佳化技巧:https://medium.com/@slawomirprzybylski/top-10-performance-optimization-tips-in-flutter-3a4f3f31202b
- 挑戰 Flutter 極限:每位開發者都應知道的效能提示:https://medium.com/@panuj330/pushing-flutter-to-the-limit-performance-tips-every-developer-should-know-87e3d835cd49
- 提升 Flutter 應用程式效能!:https://medium.com/@parthbhanderi01/raising-the-bar-for-flutter-app-performance-52418f7fa604
- 使用 RepaintBoundary 提升您的 Flutter 應用程式效能:https://www.youtube.com/watch?v=Nuni5VQXARo
- 透過最佳化網路圖片節省記憶體使用量:https://medium.com/make-anandroid/save-your-memoryusage-byoptnetwork-imageinflutter-cbc9f8af47cd

**提升 Flutter App 效能的最佳實務:**
1. 善用 Isolate :透過 Isolate 將繁重任務移出主執行緒,確保不會影響 UI 效能。
2. 最佳化網路圖片:使用 Cache 與調整圖片尺寸,減少記憶體使用量、提升載入速度

**典型查詢意圖:**
- 如何提升 Flutter App 效能?
- 避免影響 Flutter UI 效能的最佳實務?

**深入要點:**
- Isolate 的運作原理與優勢。
- 網路圖片最佳化技巧,如縮圖、使用 Cache 等。


參考來源

提高Flutter性能的小技巧!(一) - Yii Chen

盡量使用const · 宣告 const ,避免重新繪製與造成多餘的效能消耗,讓Flutter 僅重建應該更新的元件 · 使用相同的Widget 時,可以節省記憶體 ...

來源: Medium

效能最佳化實務 - Flutter 文件

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

來源: flutter.dev.org.tw

Flutter 3.13最佳化iOS Impeller渲染引擎效能,Android版今年稍晚預覽

從Flutter 3.10開始,新建立的iOS應用程式便會預設使用Impeller,使介面卡頓情況減少,且在記憶體使用上更有效率。 Flutter 3.13上的Impeller更是經過 ...

來源: iThome

提高Flutter性能的小技巧!(三) - Yii Chen

提高Flutter性能的小技巧! · 使用StatefulBuilder 進行局部更新 · 適當的壓縮數據 · 謹慎使用saveLayer() · 減少裁剪的使用 · 在16毫秒內完成創建和顯示幀( ...

來源: Medium

行動開發的革命性工具— Flutter | 雲端互動 ...

這個設計減少了UI層在不同程式間轉換,使得UI效能可以更接近原生。這個優勢在滑動和播放動畫時尤為明顯。 2. 優秀的動畫設計. Flutter 中的動畫功能強大且易於使用, ...

來源: Cloud Interactive

Flutter 3.10預設iOS使用高效Impeller渲染引擎,前端UI效能更順暢

Flutter釋出新版,能針對以此設計而成的iOS應用程式預設啟用Impeller著色引擎,藉此降低記憶體用量,減輕GPU和CPU負擔.

來源: iThome

17 个提高性能的Flutter 最佳实践

与其他混合平台相比, Flutter 性能够快吗?答案是肯定的,但是出于这种考虑,让我们来看看一些令人惊叹的性能和优化实践。

來源: duCafeCat

什麼是Flutter? – Flutter 應用程式介紹

主機裝置理解此程式碼,從而確保實現快速有效的效能。 ... Dart 針對建置UI 進行了最佳化,並且在Flutter 中使用了Dart 的許多優勢。 ... 這樣一來,使用者將在跨平台的Flutter ...


JK

專家

相關討論

❖ 相關專欄