前言
在上篇文章中,了解到通過父 Widget 初始化時傳入的靜態(tài)配置,StatelessWidget 就能完全控制其靜態(tài)展示。而 StatefulWidget 需要借助于 State 對象,在特定的階段來處理用戶的交互或其內(nèi)部數(shù)據(jù)的變化,并體現(xiàn)在 UI 上。在這些特定的階段,就涵蓋了一個組件從加載到卸載的全過程,即生命周期。與 iOS 的 ViewController 和 Android 的 Activity 一樣,F(xiàn)lutter 中的 Widget 也存在生命周期,并且通過 State 來體現(xiàn)。
而 App 則是一個特殊的 Widget。除了需要處理視圖顯示的各個階段(視圖的生命周期),還需要應(yīng)對應(yīng)用從啟動到退 出所經(jīng)歷的各個狀態(tài)(App 的生命周期)。
下面針對 Widget(的 State)和 App 的兩個維度,去了解它們生命周期。
(一)State 生命周期
State 的生命周期,指的是在用戶參與的情況下,其關(guān)聯(lián)的 Widget 所經(jīng)歷的,從創(chuàng)建到顯示再到更新最后到停止,直至銷毀等各個過程階段。這些不同的階段涉及到特定的任務(wù)處理,因此為了寫出一個體驗和性能良好的控件,正確理解 State 的生命周期至關(guān)重要。
State 的生命周期流程,如圖所示:
State 的生命周期可以分為三個階段:創(chuàng)建(插入視圖樹)、更新(在視圖樹中存在)、銷毀(從視圖樹中移除)。
(1)創(chuàng)建
State 初始化的執(zhí)行順序為:構(gòu)造方法 > initState > didChangeDependencies > build,隨后完成頁面渲染。
- 構(gòu)造方法是 State 生命周期的起點(diǎn),F(xiàn)lutter 會通過調(diào)用 StatefulWidget.creatState() 來創(chuàng)建一個 State。通過構(gòu)造方法,來接受父 Widget 傳遞的初始化 UI 配置數(shù)據(jù),決定 Widget 最初的呈現(xiàn)效果。
- initState,會在 State 對象被插入視圖樹的時候調(diào)用。這個函數(shù)在 State 的生命周期中只會被調(diào)用一次,所以我們可以在這里做一些初始化工資,比如為狀態(tài)變量設(shè)定默認(rèn)值。
- didChangeDependencies 用來專門處理 State 對象依賴關(guān)系變化,會在 initState() 調(diào)用結(jié)束后,被 Flutter 調(diào)用。
- build,作用是構(gòu)建視圖,根據(jù)父 Widget 傳遞過來的初始化配置數(shù)據(jù),以及 State 的當(dāng)前狀態(tài),創(chuàng)建一個 Widget 然后返回。
(2)更新
Widget 的狀態(tài)更新,主要由三個方法觸發(fā):setState、didChangeDependencies 與 didUpdateWidget。一旦這三個方法被調(diào)用,F(xiàn)lutter 隨后就會銷毀老 Widget,并調(diào)用 build 方法重建 Widget。
- setState:當(dāng)狀態(tài)數(shù)據(jù)發(fā)生變化時,通過調(diào)用這個方法通知 Flutter 更新重構(gòu) Widget。
- didChangeDependencies:State 對象的依賴關(guān)系發(fā)生變化后,F(xiàn)lutter 會調(diào)用這個方法,隨后觸發(fā)組件構(gòu)建。哪些情況下 State 對象的依賴關(guān)系會發(fā)生變化呢?典型的場景是:系統(tǒng)語言 Locale 或應(yīng)用主題改變時,系統(tǒng)會通知 State 執(zhí)行 didChangeDependencies 回調(diào)方法。
- didUpdateWidget:當(dāng) Widget 的配置發(fā)生變化時,比如,父 Widget 觸發(fā)重建(即父 Widget 的狀態(tài)發(fā)生變化時),熱重載時,系統(tǒng)會調(diào)用這個函數(shù)。
(3)銷毀
當(dāng)組件被移除,或是頁面被銷毀的時候,系統(tǒng)會調(diào)用 deactivate 和 dispose 這兩個方法,來移除或銷毀組件。
- deactivate:當(dāng)組件的可見狀態(tài)發(fā)生變化時,deactivate 函數(shù)會被調(diào)用,這時 State 會被暫時從視圖樹中移除。當(dāng)頁面切換時,由于 State 對象在視圖樹中的位置發(fā)生了變化,需要先暫時移除后再重新添加,重新觸發(fā)組件構(gòu)建,因為這個函數(shù)也會被調(diào)用。
- dispose:當(dāng) State 被永久地從視圖樹中移除時,F(xiàn)lutter 會調(diào)用 dispose 函數(shù)。而一旦到這個階段,組件就要被銷毀了,所以我們可以在這里進(jìn)行最終的資源釋放、移除監(jiān)聽、清理環(huán)境等等。
舉例說明,如圖所示:
第一個圖展示了當(dāng)父 Widget 狀態(tài)發(fā)生變化時,父子雙方共同的生命周期變化;
第二、三圖展示頁面切換時,兩個關(guān)聯(lián)的 Widget 的生命周期函數(shù)是如何響應(yīng)的。
從功能,調(diào)用時機(jī)和調(diào)用次數(shù)的維度總結(jié):
當(dāng)然,也可以在代碼中打印各個函數(shù)的回調(diào),更好的觀察 State 的生命周期:
@override
void initState() {
super.initState();
print('調(diào)用了 + initState');
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
print('調(diào)用了 + didChangeDependencies');
}
@override
void setState(VoidCallback fn) {
super.setState(fn);
print('調(diào)用了 + setState');
}
@override
void didUpdateWidget(MyHomePage oldWidget) {
super.didUpdateWidget(oldWidget);
print('調(diào)用了 + didUpdateWidget');
}
@override
void deactivate() {
super.deactivate();
print('調(diào)用了 + deactivate');
}
@override
void dispose() {
super.dispose();
print('調(diào)用了 + dispose');
}
(二)App 生命周期
App 生命周期定義了 App 從啟動到退出的全過程,其回調(diào)機(jī)制能夠讓我們可以根據(jù) App 狀態(tài)選擇合適的時機(jī)做恰當(dāng)?shù)氖虑椤?/p>
在原生 Android、iOS 開發(fā)中,有時我們需要在對應(yīng)的 App 生命周期事件中做相應(yīng)處理,比如 App 從后臺進(jìn)入前臺、從前臺退到后臺,或是在 UI 繪制完成后做一些處理。
這樣的需求,在原生開發(fā)中,我們可以通過重寫 Activity、ViewController 生命周期回調(diào)方法,或是注冊應(yīng)用程序的相關(guān)通知,來監(jiān)聽 App 的生命周期并做相應(yīng)的處理。而在 Flutter 中,可以利用 WidgetsBindingObserver 類,來實(shí)現(xiàn)同樣的需求。
WidgetsBindingObserver 類中的回調(diào)函數(shù):
abstract class WidgetsBindingObserver {
// 頁面 pop
Future<bool> didPopRoute() => Future<bool>.value(false);
// 頁面 push
Future<bool> didPushRoute(String route) => Future<bool>.value(false);
// 系統(tǒng)窗口相關(guān)改變回調(diào),如旋轉(zhuǎn)
void didChangeMetrics() { }
// 文本縮放系數(shù)變化
void didChangeTextScaleFactor() { }
// 系統(tǒng)亮度變化
void didChangePlatformBrightness() { }
// 本地化語言變化
void didChangeLocales(List<Locale> locale) { }
//App 生命周期變化
void didChangeAppLifecycleState(AppLifecycleState state) { }
// 內(nèi)存警告回調(diào)
void didHaveMemoryPressure() { }
//Accessibility 相關(guān)特性回調(diào)
void didChangeAccessibilityFeatures() {}
}
可以看到,WidgetsBindingObserver 類中的回調(diào)函數(shù)非常豐富,常見的屏幕旋轉(zhuǎn)、屏幕亮度、語言變化、內(nèi)存警告等都可以通過這個實(shí)現(xiàn)進(jìn)行回調(diào)。我們通過給 WidgetsBindingObserver 設(shè)置監(jiān)聽器,就可以監(jiān)聽對應(yīng)的回到方法。
下面主要對 App 生命周期的回調(diào) didChangeAppLifecycleState,和幀繪制回調(diào) addPostFrameCallback 與 addPersistentFrameCallback 進(jìn)行學(xué)習(xí)。
(1)生命周期回調(diào)(didChangeAppLifecycleState)
didChangeAppLifecycleState 回調(diào)函數(shù)中,有一個參數(shù)類型為 AppLifecycleState 的枚舉類,這個枚舉類是 Flutter 對 App 生命周期狀態(tài)的封裝。它的常用狀態(tài)包括 resumed、inactive、paused 這三個。
- resumed:可見的,并能響應(yīng)用戶的輸入。
- inactive:處在不活動狀態(tài),無法處理用戶響應(yīng)。
- paused:不可見并不能響應(yīng)用戶的輸入,但是在后臺繼續(xù)活動中。
在代碼中進(jìn)行打印:
initState 中注冊監(jiān)聽器,在 didChangeAppLifecycleState 回調(diào)方法中打印當(dāng)前 App 狀態(tài),最后在 dispose 中移除監(jiān)聽器。
class _MyHomePageState extends State<MyHomePage> with WidgetsBindingObserver {
···
@override
void initState() {
super.initState();
print('調(diào)用了 + initState');
WidgetsBinding.instance.addObserver(this);
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
print('調(diào)用了 + didChangeDependencies');
}
@override
void setState(VoidCallback fn) {
super.setState(fn);
print('調(diào)用了 + setState');
}
@override
void didUpdateWidget(MyHomePage oldWidget) {
super.didUpdateWidget(oldWidget);
print('調(diào)用了 + didUpdateWidget');
}
@override
void deactivate() {
super.deactivate();
print('調(diào)用了 + deactivate');
}
@override
void dispose() {
super.dispose();
print('調(diào)用了 + dispose');
WidgetsBinding.instance.removeObserver(this);
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
print('當(dāng)前state為:$state');
}
}
當(dāng)切換前、后臺時,控制臺輸入打印 App 狀態(tài)如下:
- 從后臺切入前臺,控制臺打印的 App 生命周期變化如下: AppLifecycleState.paused > AppLifecycleState.inactive > AppLifecycleState.resumed;
-
從前臺退回后臺,控制臺打印的 App 生命周期變化則變成了:AppLifecycleState.resumed > AppLifecycleState.inactive > AppLifecycleState.paused。
App 切換前后臺狀態(tài)變化
(2)幀繪制回調(diào)(addPostFrameCallback 與 addPersistentFrameCallback)
除了需要監(jiān)聽 App 的生命周期回調(diào)做相應(yīng)的處理之外,有時候我們還需要在組件渲染之后做一些與顯示安全相關(guān)的操作。
在 iOS 開發(fā)中,我們可以通過 dispatch_async(dispatch_get_main_queue(),^{…}) 方法,讓操作在下一個 RunLoop 執(zhí)行;而在 Android 開發(fā)中,我們可以通過 View.post() 插入消息隊列,來保證在組件渲染后進(jìn)行相關(guān)操作。
其實(shí),在 Flutter 中實(shí)現(xiàn)同樣的需求會更簡單,依然使用萬能的 WidgetBinding 來實(shí)現(xiàn)。
WidgetBinding 提供了單次 Frame 繪制回調(diào),以及實(shí)時 Frame 繪制回調(diào)兩種機(jī)制,來分別滿足不同的需求:
- 單次 Frame 繪制回調(diào),通過 addPostFrameCallback 實(shí)現(xiàn)。它會在當(dāng)前 Frame 繪制完成后進(jìn)行回調(diào),并且只會回調(diào)一次,如果要再次監(jiān)聽則需要再設(shè)置一次。
WidgetsBinding.instance.addPostFrameCallback((_){
print(" 單次 Frame 繪制回調(diào) ");// 只回調(diào)一次
});
- 實(shí)時 Frame 繪制回調(diào),則通過 addPersistentFrameCallback 實(shí)現(xiàn)。這個函數(shù)會在每次繪制 Frame 結(jié)束后進(jìn)行回調(diào),可以用做 FPS 監(jiān)測。
WidgetsBinding.instance.addPersistentFrameCallback((_){
print(" 實(shí)時 Frame 繪制回調(diào) ");// 每幀都回調(diào)
});
總結(jié)
學(xué)習(xí)了,Widget 生命周期的實(shí)際承載者是 State,State 的生命周期劃分為:創(chuàng)建(插入視圖樹)、更新(在視圖樹中存在)、銷毀(從視圖樹中移除)三個階段。
Flutter 中常用的生命周期狀態(tài)切換機(jī)制 WidgetBindingObserver 與 Flutter 幀繪制回調(diào)機(jī)制,單次 Frame 繪制回調(diào)與實(shí)時 Frame 繪制回調(diào)的異同與使用場景。