[TOC]
# 前言
Flutter 作為目前最火爆的移動端跨平臺框架,能夠幫助開發者通過一套代碼庫高效地構建多平臺的精美應用,并支持移動、Web、桌面和嵌入式平臺。對于 Android 來說,Flutter 能夠創作媲美原生的高性能應用,但是,在較為復雜的 App 中,使用 Flutter 開發也很難避免產生各種各樣的性能問題。在這篇文章中,我將和你一起全方位地深入探索 Flutter 性能優化的疆域。
# 一、檢測手段
### 準備
以 profile 模式啟動應用,如果是混合 Flutter 應用,在 flutter/packages/flutter\_tools/gradle/flutter.gradle 的 buildModeFor 方法中將 debug 模式改為 profile即可。
### 為什么要在分析模式下來調試應用性能?
分析模式在發布模式的基礎之上,為分析工具提供了少量必要的應用追蹤信息。
### 那,為什么要在發布模式的基礎上來調試應用性能?
與調試代碼可以在調試模式下檢測 Bug 不同,性能問題需要在發布模式下使用真機進行檢測。這是因為,相比發布模式而言,**調試模式增加了很多額外的檢查(比如斷言),這些檢查可能會耗費很多資源,而更重要的是,調試模式使用 JIT 模式運行應用,代碼執行效率較低。這就使得調試模式運行的應用,無法真實反映出它的性能問題**。
而另一方面,**模擬器使用的指令集為 x86,而真機使用的指令集是 ARM**。這兩種方式的二進制代碼執行行為完全不同,因此,模擬器與真機的性能差異較大,例如,針對一些 x86 指令集擅長的操作,模擬器會比真機快,而另一些操作則會比真機慢。這也同時意味著,你無法使用模擬器來評估真機才能出現的性能問題。
## 1、Flutter Inspector
Flutter Inspector有很多功能,但你應該把注意力花在更有用的功能學習上,例如:**“Select Widget Mode” 和 “Repaint Rainbow”**。
### Select Widget Mode
點擊 “Select Widget Mode” 圖標,可以在手機上查看當前頁面的布局框架與容器類型。

#### 作用
**快速查看陌生頁面的布局實現方式**。
### Repaint Rainbow
點擊 “Repaint Rainbow” 圖標,它會?**為所有 RenderBox 繪制一層外框,并在它們重繪時會改變顏色**。

#### 作用
**幫你找到 App 中頻繁重繪導致性能消耗過大的部分**。
例如:一個小動畫可能會導致整個頁面重繪,這個時候使用 RepaintBoundary Widget 包裹它,可以將重繪范圍縮小至本身所占用的區域,這樣就可以減少繪制消耗。
#### 使用場景
例如?**頁面的進度條動畫刷新時會導致整個布局頻繁重繪**。
#### 缺點
**使用 RepaintBoundary Widget 會創建額外的繪制畫布,這將會增加一定的內存消耗**。
## 2、性能圖層
性能圖層會在當前應用的最上層,以 Flutter 引擎自繪的方式展示 Raster 與 UI 線程的執行圖表,而其中每一張圖表都代表當前線程最近 300 幀的表現,如果 UI 產生了卡頓(跳幀),這些圖表可以幫助你分析并找到原因。
**藍色垂直的線條表示已執行的正常幀,綠色的線條代表的是當前幀,如果其中有一幀處理時間過長,就會導致界面卡頓,圖表中就會展示出一個紅色豎條**。
**如果紅色豎條出現在 GPU 線程圖表,意味著渲染的圖形太復雜,導致無法快速渲染;而如果是出現在了 UI 線程圖表,則表示 Dart 代碼消耗了大量資源,需要優化代碼的執行時間**。如下圖所示:

## 3、Raster 線程問題定位
它定位的是?**渲染引擎底層渲染的異常**。
解決方案是?**把需要靜態緩存的圖像加入到 RepaintBoundary。而 RepaintBoundary 可以確定 Widget 樹的重繪邊界,如果圖像足夠復雜,Flutter 引擎會自動將其緩存,避免重復刷新。當然,因為緩存資源有限,如果引擎認為圖像不夠復雜,也可能會忽略 RepaintBoundary**。
## 4、UI 線程問題定位
### 問題場景
在視圖構建時,在 build 方法中使用了一些復雜的運算,或是在主 Isolate 中進行了同步的 I/O 操作。
### 使用 Performance 進行檢測
點擊 Android Studio 底部工具欄中的 “Open DevTools” 按鈕,然后在打開的 Dart DevTools 網頁中將頂部的 tab 切換到 Performance。
與性能圖層能夠自動記錄應用執行的情況不同,使用 Performance 來分析代碼執行軌跡,你需要手動點擊 “Record” 按鈕去主動觸發,在完成信息的抽樣采集后,點擊 “Stop” 按鈕結束錄制。這時,你就可以得到在這期間應用的執行情況了。
**使用 Performance 記錄應用的執行情況,即 CPU 幀圖,又被稱為火焰圖。火焰圖是基于記錄代碼執行結果所產生的圖片,用來展示 CPU 的調用棧,表示的是 CPU 的繁忙程度**。
其中:
* **y 軸**:表示調用棧,其每一層都是一個函數。**調用棧越深,火焰就越高,底部就是正在執行的函數,上方都是它的父函數**。
* **x 軸**:表示單位時間,一**個函數在 x 軸占據的寬度越寬,就表示它被采樣到的次數越多,即執行時間越長**。
所以,我們要?**檢測 CPU 耗時問題,皆可以查看火焰圖底部的哪個函數占據的寬度最大。只要有 “平頂”,就表示該函數可能存在性能問題**。如下圖所示:

一般的耗時問題,我們通常可以?**使用 Isolate(或 compute)將這些耗時的操作挪到并發主 Isolate 之外去完成**。
> dart 的單線程執行異步任務是怎么實現的?
網絡調用的執行是由操作系統提供的另外的底層線程做的,而在 event queue 里只會放一個網絡調用的最終執行結果(成功或失敗)和響應執行結果的處理回調。
## 5、使用 checkerboardOffscreenLayers 檢查多視圖疊加的視圖渲染
只要在 MaterialApp 的初始化方法中,將 checkerboardOffscreenLayers 開關設置為 true,分析工具就會自動幫你檢測多視圖疊加的情況。
這時,使用了 saveLayer 的 Widget 會自動顯示為棋盤格式,并隨著頁面刷新而閃爍。
而 saveLayer 一般會通過一些功能性 Widget,在涉及需要剪切或半透明蒙層的場景中間接地使用。
## 6、使用 checkerboardRasterCacheImages 檢查緩存的圖像
它也是用來檢測在界面重繪時頻繁閃爍的圖像(即沒有靜態緩存)。解決方案是把需要靜態緩存的圖像加入到 RepaintBoundary。
# 二、關鍵優化指標
## 1、頁面異常率
頁面異常率,即 頁**面渲染過程中出現異常的概率**。
它度量的是頁面維度下功能不可用的情況,其統計公式為:
> 頁面異常率 = 異常發生次數 / 整體頁面 PV 數。
### 統計異常發生次數
利用 Zone 與 FlutterError 這兩個方法,然后在異常攔截的方法中,去累計異常的發生次數。
### 統計整體頁面 PV 數
繼承自 NavigatorObserver 的觀察者,并在其 didPush 方法中,去累加頁面的打開次數。
## 2、頁面幀率
Flutter 在全局 Window 對象上提供了幀回調機制。我們可以在 Window 對象上注冊 onReportTimings 方法,將最近繪制幀耗費的時間(即 ?FrameTiming),以回調的形式告訴我們。
有了每一幀的繪制時間后,我們就可以計算 FPS 了。
為了讓 FPS 的計算更加平滑,我們需要保留最近 25 個 FrameTiming 用于求和計算。
由于幀的渲染是依靠 VSync 信號驅動的,如果幀繪制的時間沒有超過 16.67 ms,我們也需要把它當成 16.67 ms 來算,因為繪制完成的幀必須要等到下一次 VSync 信號來了之后才能渲染。而如果幀繪制時間超過了 16.67 ms,則會占用后續 VSync 的信號周期,從而打亂后續的繪制次序,產生卡頓現象。
那么,頁面幀率的統計公式就是:
> FPS = 60 \* 實際渲染的幀數 / 本來應該在這個時間內渲染完成的幀數。
首先,定義一個容量為 25 的列表,用于存儲最近的幀繪制耗時 FrameTiming。
然后,在 FPS 的計算函數中,你再將列表中每幀繪制時間與 VSync 周期 frameInterval 進行比較,得出本來應該繪制的幀數。
最后,兩者相除就得到了 FPS 指標。
## 3、頁面加載時長
> 頁面加載時長 = 頁面可見的時間 - 頁面創建的時間(包括網絡加載時長)
### 統計頁面可見的時間
**WidgetsBinding 提供了單次 Frame 回調的 addPostFrameCallback 方法,它會在當前 Frame 繪制完成之后進行回調,并且只會回調一次。一旦監聽到 Frame 繪制完成回調后,我們就可以確認頁面已經被渲染出來了**,因此我們可以借助這個方法去獲取頁面的渲染完成時間 endTime。
### 統計頁面創建的時間
獲取頁面創建的時間比較容易,我們只需要在頁面的初始化函數 initState() 里記錄頁面的創建時間 startTime。
最后,再將這兩個時間做減法,你就能得到頁面的加載時長。
需要注意的是,**正常的頁面加載時長一般都不應該超過2秒。如果超過了,則意味著有嚴重的性能問題**。
# 三、布局加載優化
> Flutter 為什么要使用聲明書 UI 的編寫方式?
為了減輕開發人員的負擔,無需編寫如何在不同的 UI 狀態之間進行切換的代碼,Flutter 使用了聲明式的 UI 編寫方式,而不是 Android 和 iOS 中的命令式編寫方式。
這樣的話,**當用戶界面發生變化時,Flutter ?不會修改舊的 Widget 實例,而是會構造新的 Widget 實例**。
Fluuter 框架使用 RenderObjects 管理傳統 UI 對象的職責(比如維護布局的狀態)。RenderObjects 在幀之間保持不變, Flutter 的輕量級 Widget 通知框架在狀態之間修改 RenderObjects, 而 Flutter Framework 則負責處理其余部分。
## 1、常規優化
常規優化即針對 build() 進行優化,build() 方法中的性能問題一般有兩種:**耗時操作和 Widget 堆疊**。
### 1)、在 build() 方法中執行了耗時操作
我們應該盡量避免在 build() 中執行耗時操作,因為 build() 會被頻繁地調用,尤其是當 Widget 重建的時候。
此外,我們不要在代碼中進行阻塞式操作,可以將文件讀取、數據庫操作、網絡請求等通過 Future 來轉換成異步方式來完成。
最后,對于 CPU 計算頻繁的操作,例如圖片壓縮,可以使用 isolate 來充分利用多核心 CPU。
**isolate 作為 Flutter 中的多線程實現方式,之所以被稱之為 isolate(隔離),是因為每一個 isolate 都有一份單獨的內存**。
**Flutter 會運行一個事件循環,它會從事件隊列中取得最舊的事件,處理它,然后再返回下一個事件進行處理,依此類推,直到事件隊列清空為止。每當動作中斷時,線程就會等待下一個事件**。
實質上,不僅僅是 isolate,所有的高級 API 都能夠應用于異步編程,例如 Futures、Streams、async 和 await,它們全部都是構建在這個簡單的事件循環之上。
而,async 和 await 實際上只是使用 futures 和 streams 的替代語法,它將代碼編寫形式從異步變為同步,主要用來幫助你編寫更清晰、簡潔的代碼。
此外,async 和 await 也能使用 try on catch finally 來進行異常處理,這能夠幫助你處理一些數據解析方面的異常。
### 2)、build() 方法中堆砌了大量的 Widget
這將會導致三個問題:
* 1、**代碼可讀性差**:畫界面時需要一個 Widget 嵌套一個 Widget,但如果 Widget 嵌套太深,就會導致代碼的可讀性變差,也不利于后期的維護和擴展。
* 2、**復用難**:由于所有的代碼都在一個 build(),會導致無法將公共的 UI 代碼復用到其它的頁面或模塊。
* 3、**影響性能**:我們在 State 上調用 setState() 時,所有 build() 中的 Widget 都將被重建,因此 build() 中返回的 Widget 樹越大,那么需要重建的 Widget 就越多,也就會對性能越不利。
所以,你需要?**控制 build 方法耗時,將 Widget 拆小,避免直接返回一個巨大的 Widget,這樣 Widget 會享有更細粒度的重建和復用**。
### 3)、使用 Widget 而不是函數
如果一個函數可以做同樣的事情,Flutter 就不會有 StatelessWidget ,使用 StatelessWidget 的最大好處在于:能盡量避免不必要的重建。總的來說,它的優勢有:
* 1)、**允許性能優化:const 構造函數,更細粒度的重建等等**。
* 2)、**確保在兩個不同的布局之間切換時,能夠正確地處理資源(因為函數可能重用某些先前的狀態)**。
* 3)、**確保熱重載正常工作,使用函數可能會破壞熱重載**。
* 4)、**在 flutter 自帶的 Widget 顯示工具中能看到 Widget 的狀態和參數**。
* 5)、**發生錯誤時,有更清晰的提示:此時,Flutter 框架將為你提供當前構建的 Widget 名稱,更容易排查問題**。
* 6)、**可以定義 key 和方便使用 context 的 API**。
### 4)、盡可能地使用 const
如果某一個實例已經用 const 定義好了,那么其它地方再次使用 const 定義時,則會直接從常量池里取,這樣便能夠節省 RAM。
### 5)、盡可能地使用 const 構造器
當構建你自己的 Widget 或者使用 Flutter 的 Widget 時,這將會幫助 Flutter 僅僅去 rebuild 那些應當被更新的 Widget。
因此,你應該盡量多用 const 組件,這樣即使父組件更新了,子組件也不會重新進行 rebuild 操作。特別是針對一些長期不修改的組件,例如通用報錯組件和通用 loading 組件等。
### 6)、使用 nil 去替代 Container() 和 SizedBox()
首先,你需要明白?**nil 僅僅是一個基礎的 Widget 元素 ,它的構建成本幾乎沒有**。
在某些情況下,如果你不想顯示任何內容,且不能返回 null 的時候,你可能會返回類似 const SizedBox/Container 的 Widget,但是 SizedBox 會創建 RenderObject,而渲染樹中的 RenderObject 會帶來多余的生命周期控制和額外的計算消耗,即便你沒有給 SizedBox 指定任何的參數。
下面,是我平時使用 nil 的一套方式:
~~~
//?BEST
text?!=?null???Text(text)?:?nil
or
if?(text?!=?null)?Text(text)
text?!=?null???Text(text)?:?const?Container()/SizedBox()
~~~
### 7)、列表優化
在構建大型網格或列表的時候,我們要盡量避免使用 ListView(children: \[\],) 或 GridView(children: \[\],)。
因為,在這種場景下,不管列表內容是否可見,會導致列表中所有的數據都會被一次性繪制出來,這種用法類似于 Android 的 ScrollView。
如果我們列表數據比較大的時候,建議使用 ListView 和 GridView 的 builder 方法,它們只會繪制可見的列表內容,類似于 Android 的 RecyclerView。
其實,本質上,**就是對列表采用了懶加載而不是直接一次性創建所有的子 Widget,這樣視圖的初始化時間就減少了**。
### 8)、針對于長列表,記得在 ListView 中使用 itemExtent。
有時候當我們有一個很長的列表,想要用滾動條來大跳時,使用 itemExtent 就很重要了,**它會幫助 Flutter 去計算 ListView 的滾動位置而不是計算每一個 Widget 的高度,與此同時,它能夠使滾動動畫有更好的性能**。
### 9)、減少可折疊 ListView 的構建時間
**針對于可折疊的 ListView,未展開狀態時,設置其 itemCount 為 0,這樣 item 只會在展開狀態下才進行構建,以減少頁面第一次的打開構建時間**。
### 10)、盡量不要為 Widget 設置半透明效果
考慮用圖片的形式代替,這樣被遮擋的部分 Widget 區域就不需要繪制了。
除此之外,還有網絡請求預加載優化、抽取文本 Theme 等常規的優化方式就不贅述了。
## 2、深入優化
### 1)、優化光柵線程
所有的 Flutter 應用至少都會運行在兩個并行的線程上:**UI 線程和 Raster 線程**。
\*\*UI 線程是你構建 Widgets 和運行應用邏輯的地方。\*\***Raster 線程是 Flutter 用來柵格化你的應用的。它從 UI 線程獲取指令并將它們轉換為可以發送到圖形卡的內容**。
**在光柵線程中,會獲取圖片的字節,調整圖像的大小,應用透明度、混合模式、模糊等等,直到產生最后的圖形像素。然后,光柵線程會將其發送到圖形卡,繼而發送到屏幕上顯示**。
使用 Flutter DevTools-Performance 進行檢測,步驟如下:
* 1、在 Performance Overlay 中,查看光柵線程和 UI 線程哪個負載過重。
* 2、在 Timeline Events 中,找到那些耗費時間最長的事件,例如常見的 SkCanvas::Flush,它負責解決所有待處理的 GPU 操作。
* 3、找到對應的代碼區域,通過刪除 Widgets 或方法的方式來看對性能的影響。
### 2)、用 key 加速 Flutter 的性能優化光柵線程
一個 element 是由 Widget 內部創建的,它的主要目的是,**知道對應的 Widget 在 Widget 樹中所處的位置。但是元素的創建是非常昂貴的,通過 Keys(ValueKeys 和 GlobalKeys),我們可以去重復使用它們**。
> GlobalKey 與 ValueKey 的區別?
**GlobalKey 是全局使用的 key,在跨小部件的場景時,你就可以使用它去刷新其它小部件。但,它是很昂貴的,如果你不需要訪問 BuildContext、Element 和 State,應該盡量使用 LocalKey**。
而 ValueKey 和 ObjectKey、UniqueKey 一樣都歸屬于局部使用的 LocalKey,無法跨容器使用,ValueKey 比較的是 Widget 的值,而 ObjectKey 比較的是對象的 key,UniqueKey 則每次都會生成一個不同的值。
#### 元素的生命周期
* **Mount**:掛載,當元素第一次被添加到樹上的時候調用。
* **Active**:當需要激活之前失活的元素時被調用。
* **Update**:用新數據去更新 RenderObject。
* **Deactive**:當元素從 Widget 樹中被移除或移動時被調用。如果一個元素在同一幀期間被移動了且它有 GlobalKey,那么它仍然能夠被激活。
* **UnMount**:卸載,如果一個元素在一幀期間沒有被激活,它將會被卸載,并且再也不會被復用。
#### 優化方式
**為了去改善性能,你需要去盡可能讓 Widget 使用 Activie 和 Update 操作,并且盡量避免讓 Widget觸發 UnMount 和 Mount**。而使用 GlobayKeys 和 ValueKey 則能做到這一點:
~~~
///?1、給?MaterialApp?指定?GlobalKeys
MaterialApp(key:?global,?home:?child,);
///?2、通過把?ValueKey?分配到正在被卸載的根?Widget,你就能夠
///?減少 Widget 的平均構建時間。
Widget?build(BuildContext?context)?{
??return?Column(
????children:?[
??????value
????????????const?SizedBox(key:?ValueKey('SizedBox'))
??????????:?const?Placeholder(key:?ValueKey('Placeholder')),
??????GestureDetector(
????????key:?ValueKey('GestureDetector'),
????????onTap:?()?{
??????????setState(()?{
????????????value?=?!value;
??????????});
????????},
????????child:?Container(
??????????width:?100,
??????????height:?100,
??????????color:?Colors.red,
????????),
??????),
??????!value
????????????const?SizedBox(key:?ValueKey('SizedBox'))
??????????:?const?Placeholder(key:?ValueKey('Placeholder')),
????],
??);
}
~~~
> 如何知道哪些 Widget 會被 Update,哪些 Widget會被 UnMount?
只有 build 直接 return 的那個根 Widget 會自動更新,其它都有可能被 UnMount,因此都需要給其分配 ValueKey。
> 為什么沒有給 Container 分配 ValueKey?
因為 Container 是 GestureDetector 的一個子 Widget,所以當給 GestureDetector 使用 ValueKey 去實現復用更新時,Container 也能被自動更新。
#### 優化效果
優化前:

優化后:

可以看到,平均構建時間?**由 5.5ms 減少到 1.6ms**,優化效果還是很明顯的。
#### 優勢
大幅度減少 Widget的平均構建時間。
#### 缺點
* **過多使用 ValueKey 會讓你的代碼變得更冗余**。
* **如果你的根 Widget 是 MaterialApp 時,則需要使用 GlobalKey,但當你去重復使用 GlobalKey 時可能會導致一些錯誤,所以一定要避免濫用 Key**。
注意??:在大部分場景下,Flutter 的性能都是足夠的,不需要這么細致的優化,只有當產生了視覺上的問題,例如卡頓時才需要去分析優化。
# 四、啟動速度優化
## 1、Flutter 引擎預加載
使用它可以達到頁面秒開的一個效果,具體實現為:
在 HIFlutterCacheManager 類中定義一個 preLoad 方法,**使用 Looper.myQueue().addIdleHandler 添加一個 idelHandler,當 CPU 空閑時會回調 queueIdle 方法,在這個方法里,你就可以去初始化 FlutterEngine,并把它緩存到集合中**。
預加載完成之后,你就可以通過 HIFlutterCacheManager 類的 getCachedFlutterEngine 方法從集合中獲取到緩存好的引擎。
## 2、Dart VM 預熱
對于 Native + Flutter 的混合場景,如果不想使用引擎預加載的方式,那么要提升 Flutter 的啟動速度也可以通 過Dart VM 預熱來完成,這種方式會提升一定的 Flutter 引擎加載速度,但整體對啟動速度的提升沒有預加載引擎提升的那么多。

**無論是引擎預加載還是 Dart VM 預熱都是有一定的內存成本的,如果 App 內存壓力不大,并且預判用戶接下來會訪問 Flutter 業務,那么使用這個優化就能帶來很好的價值;反之,則可能造成資源浪費,意義不大**。
# 五、內存優化
## 1、const 實例化
### 優勢
**const 對象只會創建一個編譯時的常量值。在代碼被加載進 Dart Vm 時,在編譯時會存儲在一個特殊的查詢表里,由于 flutter 采用了 AoT 編譯,const + values 的方式會提供一些小的性能優勢**。例如:const Color() 僅僅只分配一次內存給當前實例。
### 應用場景
Color()、GlobayKey() 等等。
## 2、識別出消耗多余內存的圖片
Flutter Inspector:**點擊 “Invert Oversized Images”,它會識別出那些解碼大小超過展示大小的圖片,并且系統會將其倒置,這些你就能更容易在 App 頁面中找到它**。

針對這些圖片,你可以指定 cacheWidth 和 cacheHeight 為展示大小,這樣可以讓 flutter 引擎以指定大小解析圖片,減少內存消耗。
## 3、針對 ListView item 中有 image 的情況來優化內存
ListView 不能夠殺死那些在屏幕可視范圍之外的那些 item,如果 item 使用了高分辨率的圖片,那么它將會消耗非常多的內存。
換言之,ListView 在默認情況下會在整個滑動/不滑動的過程中讓子 Widget 保持活動狀態,這一點是通過 AutomaticKeepAlive 來保證,在默認情況下,每個子 Widget 都會被這個 Widget 包裹,以使被包裹的子 Widget 保持活躍。
其次,如果用戶向后滾動,則不會再次重新繪制子 Widget,這一點是通過 RepaintBoundaries 來保證,在默認情況下,每個子 Widget 都會被這個 Widget 包裹,它會讓被包裹的子 Widget 僅僅繪制一次,以此獲得更高的性能。
但,這樣的問題在于,如果加載大量的圖片,則會消耗大量的內存,最終可能使 App 崩潰。
### 解決方案
**通過將這兩個選項置為 false 來禁用它們,這樣不可見的子元素就會被自動處理和 GC**。
~~~
ListView.builder(
??...
??addAutomaticKeepAlives:?false?(true?by?default)
??addRepaintBoundaries:?false?(true?by?default)
);
~~~
**由于重新繪制子元素和管理狀態等操作會占用更多的 CPU 和 GPU 資源,但是它能夠解決你 App 的內存問題,并且會得到一個高性能的視圖列表**。
# 六、包體積優化
## 1、圖片優化
對圖片壓縮或使用在線的網絡圖片。
## 2、移除冗余的二三庫
隨著業務的增加,項目中會引入越來越多的二三方庫,其中有不少是功能重復的,甚至是已經不再使用的。移除不再使用的和將相同功能的庫進行合并可以進一步減少包體積。
## 3、啟用代碼縮減和資源縮減
打開 minifyEnabled 和 shrinkResources,構建出來的 release 包會減少 10% 左右的大小,甚至更多。
## 4、構建單 ABI 架構的包
目前手機市場上,x86 / x86\_64/armeabi/mips / mips6 的占有量很少,arm64-v8a 作為最新一代架構,是目前的主流,而 armeabi-v7a 只存在少部分的老舊手機中。
所以,**為了進一步優化包大小,你可以構建出單一架構的安裝包,在 Flutter 中可以通過以下方式來構建出單一架構的安裝包**:
~~~
cd?<flutter應用的android目錄>
flutter?build?apk?--split-per-abi
~~~
如果想進一步壓縮包體積可將 so 進行動態下發,將 so 放在遠端進行動態加載,不僅能進一步減少包體積也可以實現代碼的熱修復和動態加載。
# 七、總結
在本篇文章中,我主要從以下 六個方面 講解了 Flutter 性能優化相關的知識:
* 1)、**檢測手段**:Flutter Inspector、性能圖層、Raster 和 UI 線程問題的定位 使用 checkerboardOffscreenLayers 檢查多視圖疊加的視圖渲染 、使用 checkerboardRasterCacheImages 檢查緩存的圖像。
* 2)、**關鍵優化指標**:包括頁面異常率、頁面幀率、頁面加載時長。
* 3)、**布局加載優化**:十大常規優化、優化光柵線程、用 key 加速 Flutter 的性能。
* 4)、**啟動速度優化**:引擎預加載和 Dart VM 預熱。
* 5)、**內存優化**:const 實例化、識別出消耗多余內存的圖片、針對 ListView item 中有 image 的情況來優化內存。
* 6)、**包體積優化**:圖片優化、移除冗余的二三庫、啟用代碼縮減和資源縮減、構建單 ABI 架構的包。
在近一年實踐 Flutter 的過程中,越發發現一個人真正應該具備的核心能力應該是你的思考能力。
思考能力,包括?**結構化思考/系統性思考/遷移思考/層級思考/逆向思考/多元思考**?等,使用這些思考能力分析
# 參考資料
[深入探索 Flutter 性能優化](https://mp.weixin.qq.com/s/pUNlOXGfYo4taFteOg0SxA)
- Android
- 四大組件
- Activity
- Fragment
- Service
- 序列化
- Handler
- Hander介紹
- MessageQueue詳細
- 啟動流程
- 系統啟動流程
- 應用啟動流程
- Activity啟動流程
- View
- view繪制
- view事件傳遞
- choreographer
- LayoutInflater
- UI渲染概念
- Binder
- Binder原理
- Binder最大數據
- Binder小結
- Android組件
- ListView原理
- RecyclerView原理
- SharePreferences
- AsyncTask
- Sqlite
- SQLCipher加密
- 遷移與修復
- Sqlite內核
- Sqlite優化v2
- sqlite索引
- sqlite之wal
- sqlite之鎖機制
- 網絡
- 基礎
- TCP
- HTTP
- HTTP1.1
- HTTP2.0
- HTTPS
- HTTP3.0
- HTTP進化圖
- HTTP小結
- 實踐
- 網絡優化
- Json
- ProtoBuffer
- 斷點續傳
- 性能
- 卡頓
- 卡頓監控
- ANR
- ANR監控
- 內存
- 內存問題與優化
- 圖片內存優化
- 線下內存監控
- 線上內存監控
- 啟動優化
- 死鎖監控
- 崩潰監控
- 包體積優化
- UI渲染優化
- UI常規優化
- I/O監控
- 電量監控
- 第三方框架
- 網絡框架
- Volley
- Okhttp
- 網絡框架n問
- OkHttp原理N問
- 設計模式
- EventBus
- Rxjava
- 圖片
- ImageWoker
- Gilde的優化
- APT
- 依賴注入
- APT
- ARouter
- ButterKnife
- MMKV
- Jetpack
- 協程
- MVI
- Startup
- DataBinder
- 黑科技
- hook
- 運行期Java-hook技術
- 編譯期hook
- ASM
- Transform增量編譯
- 運行期Native-hook技術
- 熱修復
- 插件化
- AAB
- Shadow
- 虛擬機
- 其他
- UI自動化
- JavaParser
- Android Line
- 編譯
- 疑難雜癥
- Android11滑動異常
- 方案
- 工業化
- 模塊化
- 隱私合規
- 動態化
- 項目管理
- 業務啟動優化
- 業務架構設計
- 性能優化case
- 性能優化-排查思路
- 性能優化-現有方案
- 登錄
- 搜索
- C++
- NDK入門
- 跨平臺
- H5
- Flutter
- Flutter 性能優化
- 數據跨平臺