Flutter Widget生命周期詳解

1 Widget 簡介

在Flutter中丈秩,一切皆是Widget(組件),Widget的功能是“描述一個UI元素的配置數(shù)據(jù)”淳衙,它就是說蘑秽,Widget其實并不是表示最終繪制在設備屏幕上的顯示元素,而它只是描述顯示元素的一個配置數(shù)據(jù)箫攀。

實際上肠牲,F(xiàn)lutter中真正代表屏幕上顯示元素的類是 Element,也就是說Widget 只是描述 Element 的配置數(shù)據(jù)靴跛。并且一個 Widget 可以對應多個 Element缀雳,因為同一個 Widget 對象可以被添加到 UI樹的不同部分,而真正渲染時梢睛,UI樹的每一個 Element 節(jié)點都會對應一個 Widget 對象肥印。

其中組件又包括無狀態(tài)組件和有狀態(tài)組件。

  • 無狀態(tài)組件(StatelessWidget)
    無狀態(tài)組件扬绪,可以理解為將外部傳入的數(shù)據(jù)轉化為界面展示的內容竖独,只會渲染一次。

  • 有狀態(tài)組件(StatefulWidget)
    有狀態(tài)組件挤牛,是定義交互邏輯和業(yè)務數(shù)據(jù)莹痢,可以理解為具有動態(tài)可交互的內容界面,會根據(jù)數(shù)據(jù)的變化進行多次渲染墓赴。

StatelessWidget 和 StatefulWidget 都是直接繼承自 Widget 類竞膳,而這兩個類也正是 Flutter 中非常重要的兩個抽象類,它們引入了兩種 Widget 模型诫硕。

2 兩種Widget模型

2.1 StatelessWidget

@override
StatelessElement createElement() => new StatelessElement(this);

StatelessWidget相對比較簡單坦辟,它繼承自Widget類,重寫了createElement()方法章办。

StatelessElement 間接繼承自Element類锉走,與StatelessWidget相對應(作為其配置數(shù)據(jù))。

StatelessWidget用于不需要維護狀態(tài)的場景藕届,并且只會被渲染一次挪蹭,它通常在build方法中通過嵌套其它Widget來構建UI,在構建過程中會遞歸的構建其嵌套的Widget休偶。

2.2 StatefulWidget

abstract class StatefulWidget extends Widget {
  const StatefulWidget({ Key key }) : super(key: key);

  @override
  StatefulElement createElement() => new StatefulElement(this);

  @protected
  State createState();
}

和StatelessWidget一樣梁厉,StatefulWidget也是繼承自Widget類,并重寫了createElement()方法踏兜,不同的是返回的Element 對象并不相同词顾;另外StatefulWidget類中添加了一個新的接口createState()八秃。

StatefulElement 間接繼承自Element類,與StatefulWidget相對應(作為其配置數(shù)據(jù))肉盹。StatefulElement中可能會多次調用createState()來創(chuàng)建狀態(tài)(State)對象昔驱。

createState() 用于創(chuàng)建和StatefulWidget相關的狀態(tài),它在StatefulWidget的生命周期中可能會被多次調用垮媒。例如舍悯,當一個StatefulWidget同時插入到widget樹的多個位置時航棱,F(xiàn)lutter framework就會調用該方法為每一個位置生成一個獨立的State實例睡雇,其實,本質上就是一個StatefulElement對應一個State實例饮醇。

2.2.1 State

一個StatefulWidget類會對應一個State類它抱,State表示與其對應的StatefulWidget要維護的狀態(tài),State中的保存的狀態(tài)信息可以:

  • 在widget 構建時可以被同步讀取朴艰。
  • 在widget生命周期中可以被改變观蓄,當State被改變時,可以手動調用其setState()方法通知Flutter framework狀態(tài)發(fā)生改變祠墅,F(xiàn)lutter framework在收到消息后侮穿,會重新調用其build方法重新構建widget樹,從而達到更新UI的目的毁嗦。

State中有兩個常用屬性:

  1. widget亲茅,它表示與該State實例關聯(lián)的widget實例,由Flutter framework動態(tài)設置狗准。注意克锣,這種關聯(lián)并非永久的,因為在應用生命周期中腔长,UI樹上的某一個節(jié)點的widget實例在重新構建時可能會變化袭祟,但State實例只會在第一次插入到樹中時被創(chuàng)建,當在重新構建時捞附,如果widget被修改了巾乳,F(xiàn)lutter framework會動態(tài)設置State.widget為新的widget實例。
  2. context鸟召,StatefulWidget對應的BuildContext胆绊,作用同StatelessWidget的BuildContext,表示當前widget在widget樹中的上下文药版,每一個widget都會對應一個context對象(因為每一個widget都是widget樹上的一個節(jié)點)辑舷。

3 生命周期

3.1 組件生命周期

Flutter 中說的生命周期,是獨指有狀態(tài)組件的生命周期槽片,對于無狀態(tài)組件生命周期只有一次 build 這個過程何缓,也只會渲染一次肢础。有狀態(tài)組件的生命周期如下圖:

Flutter-Widget生命周期.png

Flutter 中的生命周期,包含以下幾個階段:

  • createState 碌廓,該函數(shù)為 StatefulWidget 中創(chuàng)建 State 的方法传轰,當 StatefulWidget 被調用時會立即執(zhí)行 createState 。

  • initState 谷婆,該函數(shù)為 State 初始化調用慨蛙,因此可以在此期間執(zhí)行 State 各變量的初始賦值,同時也可以在此期間與服務端交互纪挎,獲取服務端數(shù)據(jù)后調用 setState 來設置 State期贫。

  • didChangeDependencies 遏片,當State對象的依賴發(fā)生變化時會被調用涮俄;例如:在之前build() 中包含了一個InheritedWidget,然后在之后的build() 中InheritedWidget發(fā)生了變化前域,那么此時InheritedWidget的子widget的didChangeDependencies()回調都會被調用烤蜕。典型的場景是當系統(tǒng)語言Locale或應用主題改變時封孙,F(xiàn)lutter framework會通知widget調用此回調。

  • build 讽营,主要是返回需要渲染的 Widget 虎忌,由于 build 會被調用多次,因此在該函數(shù)中只能做返回 Widget 相關邏輯橱鹏,避免因為執(zhí)行多次導致狀態(tài)異常膜蠢。在 build 之后還有個回調 addPostFrameCallback,在當前幀繪制完成后會回調蚀瘸,注冊之后不能被解注冊并且只會回調一次狡蝶;addPostFrameCallback是 SchedulerBinding 的方法;由于 mixin WidgetsBinding on SchedulerBinding贮勃,所以添加這個回調有兩種方式:SchedulerBinding.instance.addPostFrameCallback((_) => {});或者WidgetsBinding.instance.addPostFrameCallback((_) => {});

  • reassemble贪惹, 在 debug 模式下,每次熱重載都會調用該函數(shù)寂嘉,因此在 debug 階段可以在此期間增加一些 debug 代碼奏瞬,來檢查代碼問題。

  • didUpdateWidget 泉孩,在widget重新構建時硼端,F(xiàn)lutter framework會調用Widget.canUpdate來檢測Widget樹中同一位置的新舊節(jié)點,然后決定是否需要更新寓搬,如果Widget.canUpdate返回true則會調用此回調珍昨。正如之前所述,Widget.canUpdate會在新舊widget的key和runtimeType同時相等時會返回true,也就是說在在新舊widget的key和runtimeType同時相等時didUpdateWidget()就會被調用镣典。父組件發(fā)生 build 的情況下兔毙,子組件該方法才會被調用,其次該方法調用之后一定會再調用本組件中的 build 方法兄春。

  • deactivate 澎剥,在組件被移除節(jié)點后會被調用,如果該組件被移除節(jié)點赶舆,然后未被插入到其他節(jié)點時哑姚,則會繼續(xù)調用 dispose 永久移除。

  • dispose 芜茵,永久移除組件叙量,并釋放組件資源。

Flutter 生命周期的整個過程可以分為四個階段

  1. 初始化階段:createState 和 initState
  2. 組件創(chuàng)建階段:didChangeDependencies 和 build
  3. 觸發(fā)組件 build:didChangeDependencies夕晓、setState 或者didUpdateWidget 都會引發(fā)的組件重新 build
  4. 組件銷毀階段:deactivate 和 dispose

3.2 組件首次加載過程

我們通過代碼來看下組件首次加載執(zhí)行的生命周期過程

在 lib 中創(chuàng)建 test_stateful_widget.dart

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

class TestStatefulWidget extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    print('create state');
    return TestState();
  }
}

class TestState extends State<TestStatefulWidget> {
  /// 定義 state [count] 計算器
  int count = 1;

  /// 定義 state [name] 為當前描述字符串
  String name = 'test';

  @override
  void initState() {
    print('init state');
    super.initState();
  }

  @override
  void didChangeDependencies() {
    print('did change dependencies');
    super.didChangeDependencies();
  }

  @override
  void didUpdateWidget(TestStatefulWidget oldWidget) {
    count++;
    print('did update widget');
    super.didUpdateWidget(oldWidget);
  }

  @override
  void deactivate() {
    print('deactivate');
    super.deactivate();
  }

  @override
  void dispose() {
    print('dispose');
    super.dispose();
  }

  @override
  void reassemble() {
    print('reassemble');
    super.reassemble();
  }

  void changeName() {
    setState(() {
      print('set state');
      this.name = 'Flutter';
    });
  }

  @override
  Widget build(BuildContext context) {
    print('build');
    return Column(
      children: <Widget>[
        FlatButton(
            child: Text('$name $count'), onPressed: () => this.changeName())
      ],
    );
  }
}

在 main.dart 中加載該組件

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Test',
      theme: ThemeData(
          primaryColor: Colors.amberAccent
      ),
      home: Scaffold(
        appBar: AppBar(
          title: Text('生命周期測試'),
        ),
        body: Center(
          child: TestStatefulWidget(),
        ),
      ),
    );
  }
}

我們打開手機模擬器宛乃,然后運行該 App ,在輸出控制臺可以看到下面的運行打印日志信息:

I/flutter ( 7729): create state
I/flutter ( 7729): init state
I/flutter ( 7729): did change dependencies
I/flutter ( 7729): build

當我們點擊 Android Studio 中的 Hot Reload 按鈕(黃色小閃電 ??)時控制臺輸出信息如下:


I/flutter ( 7729): reassemble
I/flutter ( 7729): did update widget
I/flutter ( 7729): build

3.3 觸發(fā)組件再次 build

觸發(fā)組件再次 build 有三種方式

  1. setState
  2. didChangeDependencies
  3. didUpdateWidget

setState的場景開發(fā)中你那個經常遇到蒸辆,在數(shù)據(jù)狀態(tài)變化時觸發(fā)組件 build,在上面的代碼運行后的界面中點擊 test 文本按鈕析既,然后觀察控制臺輸出如下:

I/flutter ( 7729): set state
I/flutter ( 7729): build

didChangeDependencies場景:一般情況下我們會將一些比較基礎的數(shù)據(jù)放到全局變量中躬贡,例如主題顏色、地區(qū)語言或者其他通用變量等眼坏。如果這些全局 state 發(fā)生狀態(tài)變化則會觸發(fā)該函數(shù)拂玻,而該函數(shù)之后就會觸發(fā) build 操作。

接下來看下didUpdateWidget 觸發(fā) build 的場景:
創(chuàng)建一個組件 SubStatefulWidget 繼承 TestStatefulWidget

class SubStatefulWidget extends TestStatefulWidget {
  @override
  State<StatefulWidget> createState() {
    print('sub create state');
    return SubState();
  }
}

class SubState extends State<SubStatefulWidget> {

  @override
  void initState() {
    print('sub init state');
    super.initState();
  }

  @override
  void didChangeDependencies() {
    print('sub did change dependencies');
    super.didChangeDependencies();
  }

  @override
  void didUpdateWidget(TestStatefulWidget oldWidget) {
    print('sub did update widget');
    super.didUpdateWidget(oldWidget);
  }

  @override
  void deactivate() {
    print('sub deactivate');
    super.deactivate();
  }

  @override
  void dispose() {
    print('sub dispose');
    super.dispose();
  }

  @override
  void reassemble() {
    print('sub reassemble');
    super.reassemble();
  }

  void changeName() {
    setState(() {
      print('sub set state');
    });
  }

  @override
  Widget build(BuildContext context) {
    print('sub build');
    return Text('SubStatefulWidget');
  }
}

接著在 TestStatefulWidget 加載該組件

  @override
  Widget build(BuildContext context) {
    print('build');
    return Column(
      children: <Widget>[
        FlatButton(
            child: Text('$name $count'), onPressed: () => this.changeName()),
        SubStatefulWidget()
      ],
    );
  }

重新加載 APP宰译,觀看控制臺輸出如下:

I/flutter ( 8464): create state
I/flutter ( 8464): init state
I/flutter ( 8464): did change dependencies
I/flutter ( 8464): build
I/flutter ( 8464): sub create state
I/flutter ( 8464): sub init state
I/flutter ( 8464): sub did change dependencies
I/flutter ( 8464): sub build

上面日志先后打印了 TestStatefulWidget 與 SubStatefulWidget 四個狀態(tài)函數(shù) createState檐蚜、initState、didChangeDependencies 和 build沿侈。當我們再次點擊 test 文本按鈕闯第,觀察控制臺輸出如下:

I/flutter ( 9425): set state
I/flutter ( 9425): build
I/flutter ( 9425): sub did update widget
I/flutter ( 9425): sub build

通過上面打印的日志我們可知,父組件調用setState不僅會觸發(fā)自己 build缀拭,還會引發(fā)子組件重新 build 咳短,雖然子組件沒有任何改動。

3.4 觸發(fā)組件銷毀

在3.3的代碼的基礎上蛛淋,我們直接在 TestStatefulWidget 組件中注釋子組件 SubStatefulWidget 的調用

  @override
  Widget build(BuildContext context) {
    print('build');
    return Column(
      children: <Widget>[
        FlatButton(
            child: Text('$name $count'), onPressed: () => this.changeName()),
//        SubStatefulWidget()
      ],
    );
  }

然后點擊 Android Studio 上的 Hot Reload 按鈕觀察控制臺輸出如下信息:

I/flutter ( 9425): reassemble
I/flutter ( 9425): sub reassemble
I/flutter ( 9425): did update widget
I/flutter ( 9425): build
I/flutter ( 9425): sub deactivate
I/flutter ( 9425): sub dispose

可以看到 SubStatefulWidget 被銷毀

4 App 生命周期監(jiān)聽

在 Flutter 中咙好,可以利用 WidgetsBindingObserver 類來監(jiān)聽App 生命周期

abstract class WidgetsBindingObserver {
  // 頁面 pop
  Future<bool> didPopRoute() => Future<bool>.value(false);
  // 頁面 push
  Future<bool> didPushRoute(String route) => Future<bool>.value(false);
  // 系統(tǒng)窗口相關改變回調,如旋轉
  void didChangeMetrics() { }
  // 文本縮放系數(shù)變化
  void didChangeTextScaleFactor() { }
  // 系統(tǒng)亮度變化
  void didChangePlatformBrightness() { }
  // 本地化語言變化
  void didChangeLocales(List<Locale> locale) { }
  //App 生命周期變化
  void didChangeAppLifecycleState(AppLifecycleState state) { }
  // 內存警告回調
  void didHaveMemoryPressure() { }
  //Accessibility 相關特性回調
  void didChangeAccessibilityFeatures() {}
}

其中didChangeAppLifecycleState(AppLifecycleState state)就是用來監(jiān)聽App生命周期褐荷。在 didChangeAppLifecycleState 回調函數(shù)中勾效,有一個參數(shù)類型為 AppLifecycleState 的枚舉類,這個枚舉類是 Flutter 對 App 生命周期狀態(tài)的封裝。它的常用狀態(tài)包括 resumed层宫、inactive绘迁、paused 這三個。

  • resumed:可見的卒密,并能響應用戶的輸入缀台。
  • inactive:處在不活動狀態(tài),無法處理用戶響應哮奇。
  • paused:不可見并不能響應用戶的輸入膛腐,但是在后臺繼續(xù)活動中。

當切換前鼎俘、后臺時哲身,App 狀態(tài)如下:

  • 從后臺切入前臺: AppLifecycleState.paused -> AppLifecycleState.inactive -> AppLifecycleState.resumed
  • 從前臺退回后臺:AppLifecycleState.resumed -> AppLifecycleState.inactive -> AppLifecycleState.paused

5 幀繪制回調

5.1 addPostFrameCallback

單次 Frame 繪制回調,會在當前 Frame 繪制完成后進行回調贸伐,并且只會回調一次勘天,如果要再次監(jiān)聽則需要再設置一次。

WidgetsBinding.instance.addPostFrameCallback((_){
  print(" 單次 Frame 繪制回調 ");
});

5.2 addPersistentFrameCallback

實時 Frame 繪制回調捉邢,會在每次繪制 Frame 結束后進行回調脯丝,可以用做 FPS 監(jiān)測。

WidgetsBinding.instance.addPersistentFrameCallback((_){
  print(" 實時 Frame 繪制回調");
});

6 總結

  1. Flutter 中 Widget 分為兩種:StatelessWidget 和 StatefulWidget
  2. Flutter 生命周期的整個過程可以分為四個階段:初始化伏伐、組件創(chuàng)建宠进、觸發(fā)組件 build、組件銷毀
  3. StatelessWidget 用于不需要維護狀態(tài)的場景藐翎,只會 build 一次
  4. StatefulWidget 會被多次觸發(fā) build 函數(shù)材蹬,觸發(fā)函數(shù)是setState、didChangeDependencies吝镣、didUpdateWidget
  5. 父組件調用setState不僅會觸發(fā)自己 build堤器,還會引發(fā)子組件重新 build ,雖然子組件沒有任何改動
  6. 可以利用 WidgetsBindingObserver 類來監(jiān)聽App 生命周期
  7. 幀繪制回調有單次 Frame 繪制回調(addPostFrameCallback)和實時 Frame 繪制回調(addPersistentFrameCallback)
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末末贾,一起剝皮案震驚了整個濱河市闸溃,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌未舟,老刑警劉巖圈暗,帶你破解...
    沈念sama閱讀 216,843評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異裕膀,居然都是意外死亡员串,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,538評論 3 392
  • 文/潘曉璐 我一進店門昼扛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來寸齐,“玉大人欲诺,你說我怎么就攤上這事∶祓校” “怎么了扰法?”我有些...
    開封第一講書人閱讀 163,187評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長毅厚。 經常有香客問我塞颁,道長,這世上最難降的妖魔是什么吸耿? 我笑而不...
    開封第一講書人閱讀 58,264評論 1 292
  • 正文 為了忘掉前任祠锣,我火速辦了婚禮,結果婚禮上咽安,老公的妹妹穿的比我還像新娘伴网。我一直安慰自己,他們只是感情好妆棒,可當我...
    茶點故事閱讀 67,289評論 6 390
  • 文/花漫 我一把揭開白布澡腾。 她就那樣靜靜地躺著,像睡著了一般糕珊。 火紅的嫁衣襯著肌膚如雪动分。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,231評論 1 299
  • 那天放接,我揣著相機與錄音刺啦,去河邊找鬼。 笑死纠脾,一個胖子當著我的面吹牛,可吹牛的內容都是我干的蜕青。 我是一名探鬼主播苟蹈,決...
    沈念sama閱讀 40,116評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼右核!你這毒婦竟也來了慧脱?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,945評論 0 275
  • 序言:老撾萬榮一對情侶失蹤贺喝,失蹤者是張志新(化名)和其女友劉穎菱鸥,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體躏鱼,經...
    沈念sama閱讀 45,367評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡氮采,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,581評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了染苛。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鹊漠。...
    茶點故事閱讀 39,754評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出躯概,到底是詐尸還是另有隱情登钥,我是刑警寧澤,帶...
    沈念sama閱讀 35,458評論 5 344
  • 正文 年R本政府宣布娶靡,位于F島的核電站牧牢,受9級特大地震影響,放射性物質發(fā)生泄漏姿锭。R本人自食惡果不足惜塔鳍,卻給世界環(huán)境...
    茶點故事閱讀 41,068評論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望艾凯。 院中可真熱鬧献幔,春花似錦、人聲如沸趾诗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,692評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽恃泪。三九已至郑兴,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間贝乎,已是汗流浹背情连。 一陣腳步聲響...
    開封第一講書人閱讀 32,842評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留览效,地道東北人却舀。 一個月前我還...
    沈念sama閱讀 47,797評論 2 369
  • 正文 我出身青樓,卻偏偏與公主長得像锤灿,于是被迫代替她去往敵國和親挽拔。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,654評論 2 354