Flutter - 生命週期

宋魚000發表於2019-09-26

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 繪製回撥"); //  每幀都回撥
});
複製程式碼

相關文章