StatefulWidget 需要藉助於 State 對象,在特定的階段來處理用戶的交互或其內部數據的變化,並體現在 UI 上。這個特定的階段,就涵蓋來一個組件從載入到卸載的全過程,即生命周期。Flutter 中的 Widget 也存在生命周期,並且通過 State 來體現。 而 App 則是一個特 ...
StatefulWidget 需要藉助於 State 對象,在特定的階段來處理用戶的交互或其內部數據的變化,並體現在 UI 上。這個特定的階段,就涵蓋來一個組件從載入到卸載的全過程,即生命周期。Flutter 中的 Widget 也存在生命周期,並且通過 State 來體現。
而 App 則是一個特殊的 Widget。除了需要處理視圖顯示的各個階段(即視圖的生命周期)之外,還需要應對應用從啟動到退出所經歷的各個狀態(App 的生命周期)。
State 生命周期
State 的生命周期,指的是在用戶參與的情況下,其關聯的 Widget 所經歷的,從創建到顯示再到更新最後到停止,直至銷毀等各個過程階段。State 的生命周期可以分為 3 個階段:創建(插入視圖樹)、更新(在視圖樹中存在)、銷毀(從視圖樹中移除)。
創建
State 初始化時會依次執行:構造方法 -> initState -> disChangeDependencies -> build,隨後完成頁面渲染。
初始化過程中每個方法的意義如下:
- 構造方法是 State 生命周期的起點,Flutter 會通過調用 StatefulWidget.createState() 來創建一個 State。可以通過構造方法,來接收父 Widget 傳遞的初始化 UI 配置數據。這些配置數據,決定了 Widget 最初的呈現效果。
- initState,會在 State 對象被插入視圖樹的時候調用。這個函數在 State 的生命周期中只會被調用一次,所以可以在這裡做一些初始化工作,比如為狀態變數設定預設值。
- didChangeDependencies 則來專門處理 State 對象依賴關係變化,會在 initState() 調用結束後,被 Flutter 調用。
- build,作用是構建視圖。經過以上步驟,Framework 認為 State 已經準備好了,於是調用 build。可以在這個函數中,根據父 Widget 傳遞過來的初始化配置數據,以及 State 的當前狀態,創建一個 Widget 然後返回。
更新
Widget 的狀態更新,主要由 3 個方法觸發:setState、didchangeDependencies 與 didUpdateWidget.
如上三個方法對應的調用場景:
- setState:當狀態數據發生變化時,通過調用這個方法告訴 Flutter:“這兒的數據變了,請使用更新後的數據重建 UI!”。
- didChangeDependencies:State 對象的依賴關係發生變化後,Flutter 會回調這個方法,隨後觸發組建構建。典型的場景是,系統語言 Locale 或應用主題改變時,系統會通知 State 執行 didChangeDependencies 回調方法。
- didUpdateWidget:當 Widget 的配置發生變化時,比如,父 Widget 觸發重建(即父 Widget 的狀態發生變化時),熱重載時,系統會調用這個函數
一旦這三個方法被調用,Flutter 隨後就會銷毀老 Widget,並調用 build 方法重建 widget。
銷毀
組件銷毀比較簡單。比如組件被移除,或是頁面銷毀的時候,系統會調用 deactivate 和 dispose 這兩個方法,來移除或銷毀組件。
具體調用機制如下:
- 當組件的可見狀態發生變化時,deactivate 函數會被調用,這時 State 會被暫時從視圖樹中移除。值得註意的是,頁面切換時,由於 State 對象在視圖樹中的位置發生了變化,需要先暫時移除後再重新添加,重新觸發組件構建,因此這個函數也會被調用。
- 當 Sate 被永久地從視圖樹中移除時,Flutter 會調用 dispose 函數。而一旦到這個階段,組件就要被銷毀了,所以我們可以在這裡進行最終的資源釋放、移除監聽、清理環境,等等。
從功能、調用時機和調用次數的緯度總結這些方法如下;
方法名 | 功能 | 調用時機 | 調用次數 |
---|---|---|---|
構造方法 | 接收父Widget傳遞的初始化UI配置數據 | 創建State時 | 1 |
initState | 與渲染相關的初始化工作 | 在State被插入視圖樹時 | 1 |
didChangeDependencies | 處理State隊形依賴關係變化 | initState後及State對象依賴關係變化時 | >= 1 |
build | 構建視圖 | State準備好數據需要渲染時 | >= 1 |
setState | 觸發視圖重建 | 需要刷新UI時 | >= 1 |
didUpdateWidget | 處理Widget的配置變化 | 父Widget setState觸發子Widget重建時 | >= 1 |
deactivate | 組件被移除 | 組件不可視 | >= 1 |
dispose | 組件被銷毀 | 組件被永久移除 | 1 |
App 生命周期
視圖的生命周期,定義了視圖的載入到構建的全過程,其回調機制能夠確保我們可以根據視圖的狀態選擇合適的時機做恰當的事情。App 的生命周期,則定義了 App 從啟動到退出的全過程。
在原生 Android、iOS 開發中,有時需要在對應的 App 生命周期事件中做相應處理,比如 App 從後臺進入前臺、從前臺退到後臺,或是在 UI 繪製完成後做一些處理。
在原生開發中,可以通過重寫 Activity、ViewController 生命周期回調方法,或是註冊應用程式的相關通知,來監聽 App 的生命周期並做相應的處理。而在 Flutter 中,我們可以利用 WidgetsBindingObserver 類,來實現同樣的需求。
WidgetsBindingObserver 中具體回調函數有如下一些方法:
abstract class WidgetsBindingObserver {
// 頁面 pop
Future<bool> didPopRoute() => Future<bool>.value(false);
// 頁面 push
Future<bool> didPushRoute(String route) => Future<bool>.value(false);
// 系統視窗相關改變回調,如旋轉
void didChangeMetrics() { }
// 文本縮放繫數變化
void didChangeTextScaleFactor() { }
// 系統亮度變化
void didChangePlatformBrightness() { }
// 本地化語言變化
void didChangeLocales(List<Locale> locale) { }
//App 生命周期變化
void didChangeAppLifecycleState(AppLifecycleState state) { }
// 記憶體警告回調
void didHaveMemoryPressure() { }
//Accessibility 相關特性回調
void didChangeAccessibilityFeatures() {}
}
其他回調相對簡單,可以查看官方文檔
生命周期回調
didChangeAppLifecycleState 回調函數中,有一個參數類型為 AppLifecycleState 的枚舉類,這個枚舉類是 Flutter 對 App 生命周期狀態的封裝。它的常用狀態包括 resumed、inactive、paused 這三個。
- resumed:可見的,並能響應用戶的輸入;
- inactive:處在不活動狀態,無法處理用戶響應;
- paused:不可見並不能響應用戶的輸入,但是在後臺繼續活動中。
案例分享:在 initState 時註冊來監聽器,在 didChangeAppLifecycleState 回調方法中列印來當前的 App 狀態,最後在 dispose 時把監聽器移除:
class _MyHomePageState extends State<MyHomePage> with WidgetsBindingObserver {
...
@override
@mustCallSuper
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this); // 註冊監聽器
}
@override
@mustCallSuper
void dispose() {
super.dispose();
WidgetsBinding.instance.removeObserver(this); // 移除監聽器
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) async {
print("$state");
if (state == AppLifecycleState.resumed) {
// do sth
}
}
}
嘗試這切換一下前、後臺,觀察控制台輸出的 App 狀態,可以發現:
- 從後臺切入前臺,控制台列印的 App 生命周期變化如下:AppLifecycleState.paused -> AppLifecycleState.inactive -> AppLifecycleState.resumed;
- 從前臺退回後臺,控制台列印的 App 生命周期變化則變成了:AppLifecycleState.resumed -> AppLifecycleState.inactive -> AppLifecycleState.paused
幀繪製回調
除了需要監聽 App 的生命周期回調做相應的處理之外,有時候還需要在組件渲染之後做一些與顯示安全相關的操作。
在 iOS 開發中,可以通過 dispatch_async(dispatch_get_main_queue(),^{...}) 方法,讓操作在下一個 RunLoop 執行;在 Android 開發中,可以通過 View.post() 插入消息隊列,來保證在組件渲染後進行相關操作。
在 Flutter 中實現同樣的需求會更簡單: 依然使用萬能的 WidgetsBinding 來實現。
WidgetsBinding 提供了單次 Frame 繪製回調,以及實時 Frame 繪製回調兩種機制,來分別滿足不同的需求:
- 單次 Frame 繪製回調,通過 addPostFrameCallback 實現。它會在當前 Frame 繪製完成後進行回調,並只會回調一次,如果要再次監聽則需要再設置一次。
WidgetsBinding.instance.addPostFrameCallback((_){
print(" 單次 Frame 繪製回調"); // 只回調一次
});
- 實時 Frame 繪製回調,則通過 addPersistentFrameCallback 實現。這個函數會在每次繪製 Frame 結束後進行回調,可以用作 FPS 檢測。
WidgetsBinding.instance.addPersistentFrameCallback((_){
print(" 實時 Frame 繪製回調"); // 每幀都回調
});