[Flutter] 10-Flutter的生命周期

本章介紹 Flutter 的組件承粤,以及組件的生命周期沧奴。

一昭灵、組件 Widget定義

Flutter 中的組件與前端組件的理解和作用基本一致店展,但是沒有一個明確的概念解釋 Flutter 組件苛聘,這里借用前端的組件定義來解釋 Flutter 組件的概念涂炎。

一個 Flutter 組件,包含了組件的模板设哗、樣式和交互等內(nèi)容唱捣,外部只要按照組件設(shè)定的屬性、函數(shù)及事件處理等進行調(diào)用即可网梢,完全不用考慮組件的內(nèi)部實現(xiàn)邏輯震缭。其中組件又包括無狀態(tài)組件和有狀態(tài)組件。

  • 無狀態(tài)組件

無狀態(tài)組件战虏,可以理解為將外部傳入的數(shù)據(jù)轉(zhuǎn)化為界面展示的內(nèi)容拣宰,只會渲染一次

  • 有狀態(tài)組件

有狀態(tài)組件烦感,是定義交互邏輯和業(yè)務(wù)數(shù)據(jù)巡社,可以理解為具有動態(tài)可交互的內(nèi)容界面,會根據(jù)數(shù)據(jù)的變化進行多次渲染啸盏。

二重贺、生命周期

在原生 Android 、原生 iOS 回懦、前端 React 或者 Vue 都存在生命周期的概念气笙,在 Flutter 中一樣存在生命周期的概念,其基本概念和作用相似怯晕。 Flutter 中說的生命周期潜圃,也是指有狀態(tài)組件,對于無狀態(tài)組件生命周期只有 build 這個過程舟茶,也只會渲染一次谭期,而有狀態(tài)組件則比較復(fù)雜,下面我們就來看看有狀態(tài)組件的生命周期過程吧凉。

1)隧出、生命周期的流轉(zhuǎn)

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

  • createState 阀捅,該函數(shù)為 StatefulWidget 中創(chuàng)建 State 的方法胀瞪,當(dāng) StatefulWidget 被調(diào)用時會立即執(zhí)行 createState 。

  • initState ,該函數(shù)為 State 初始化調(diào)用凄诞,因此可以在此期間執(zhí)行 State 各變量的初始賦值圆雁,同時也可以在此期間與服務(wù)端交互,獲取服務(wù)端數(shù)據(jù)后調(diào)用 setState 來設(shè)置 State帆谍。
    注意:這個方法是重寫父類的方法伪朽,必須調(diào)用super,因為父類中會進行一些其他操作汛蝙;
    并且如果你閱讀源碼烈涮,你會發(fā)現(xiàn)這里有一個注解(annotation):@mustCallSuper

@protected
@mustCallSuper
void initState() {
    assert(_debugLifecycleState == _StateLifecycle.created);
}
  • didChangeDependencies ,該函數(shù)是在該組件依賴的 State 發(fā)生變化時窖剑,這里說的 State 為全局 State 跃脊,例如語言或者主題等,類似于前端 Redux 存儲的 State 苛吱。這個方法在兩種情況下會調(diào)用:
    情況一:調(diào)用initState會調(diào)用;
    情況二:從其他對象中依賴一些數(shù)據(jù)發(fā)生改變時器瘪,比如前面我們提到的InheritedWidget翠储;
  • build ,主要是返回需要渲染的 Widget 橡疼,由于 build 會被調(diào)用多次援所,因此在該函數(shù)中只能做返回 Widget 相關(guān)邏輯,避免因為執(zhí)行多次導(dǎo)致狀態(tài)異常欣除。

  • reassemble 住拭,主要是提供開發(fā)階段使用,在 debug 模式下历帚,每次熱重載都會調(diào)用該函數(shù)滔岳,因此在 debug 階段可以在此期間增加一些 debug 代碼,來檢查代碼問題挽牢。

  • didUpdateWidget 谱煤,該函數(shù)主要是在組件重新構(gòu)建,比如說熱重載禽拔,父組件發(fā)生 build 的情況下刘离,子組件該方法才會被調(diào)用,其次該方法調(diào)用之后一定會再調(diào)用本組件中的 build 方法睹栖。

  • deactivate 硫惕,在組件被移除節(jié)點后會被調(diào)用,如果該組件被移除節(jié)點野来,然后未被插入到其他節(jié)點時恼除,則會繼續(xù)調(diào)用 dispose 永久移除。

  • dispose 梁只,永久移除組件缚柳,并釋放組件資源埃脏。

圖1 生命周期流程圖

整個過程分為四個階段:

  • 1.初始化階段,包括兩個生命周期函數(shù) createState 和 initState秋忙;

  • 2.組件創(chuàng)建階段彩掐,也可以稱組件出生階段,包括 didChangeDependencies 和 build灰追;

  • 3.觸發(fā)組件多次 build 堵幽,這個階段有可能是因為 didChangeDependencies、setState 或者 didUpdateWidget 而引發(fā)的組件重新 build 弹澎,在組件運行過程中會多次被觸發(fā)朴下,這也是優(yōu)化過程中需要著重需要注意的點;

  • 4.最后是組件銷毀階段苦蒿,deactivate 和 dispose殴胧。

2)、組件首次加載執(zhí)行過程

我們先實現(xiàn)一段代碼佩迟,來看下組件在首次創(chuàng)建的執(zhí)行過程是否是按照圖 1 的流程团滥。

  • 1、 在 lib 中 pages 下創(chuàng)建 test_stateful_widget.dart 报强;
  • 2灸姊、 在 test_stateful_widget.dart 添加如下代碼:
import 'package:flutter/material.dart';
/// 創(chuàng)建有狀態(tài)測試組件
class TestStatefulWidget extends StatefulWidget {
  @override
  createState() {
    print('create state');
    return TestState();
  }
}
/// 創(chuàng)建狀態(tài)管理類,繼承狀態(tài)測試組件
class TestState extends State<TestStatefulWidget> {
  /// 定義 state [count] 計算器
  int count = 1;
  /// 定義 state [name] 為當(dāng)前描述字符串
  String name = 'test';
  @override
  initState() {
    print('init state');
    super.initState();
  }
  @override
  didChangeDependencies() {
    print('did change dependencies');
    super.didChangeDependencies();
  }
  @override
  didUpdateWidget(TestStatefulWidget oldWidget) {
    count++;
    print('did update widget');
    super.didUpdateWidget(oldWidget);
  }
  @override
  deactivate() {
    print('deactivate');
    super.deactivate();
  }
  @override
  dispose() {
    print('dispose');
    super.dispose();
  }
  @override
  reassemble(){
    print('reassemble');
    super.reassemble();
  }
  /// 修改 state name
  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'), // 使用 Text 組件顯示描述字符和當(dāng)前計算
          onPressed:()=> this.changeName(), // 點擊觸發(fā)修改描述字符 state name
        )
      ],
    );
  }
}

上述代碼把有狀態(tài)組件的一些生命周期函數(shù)都進行了重寫秉溉,并且在執(zhí)行中都打印了一些字符串標(biāo)識力惯,目的是可以看到該函數(shù)被執(zhí)行。

  • 3召嘶、 然后在 main.dart 中加載該組件父晶,代碼如下:
import 'package:flutter/material.dart';
import 'package:flutter_liftcycle_demo/pages/test_stateful_widget.dart';

/// APP 核心入口文件
void main() => runApp(MyApp());
/// MyApp 核心入口界面
class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'Flutter Lifecycle', // APP 名字
        theme: ThemeData(
          primarySwatch: Colors.blue, // APP 主題
        ),
        home: Scaffold(
            appBar: AppBar(
              title: Text('首頁'), // 頁面名字
            ),
            body: Center(
             child:
              TestStatefulWidget(),
            )
        ));
  }
}

代碼修改后,我們打開手機模擬器弄跌,然后運行該 App 诱建,在輸出控制臺可以看到下面的運行打印日志信息。

flutter: create state
flutter: init state
flutter: did change dependencies
flutter: build
flutter: reassemble
flutter: did update widget
flutter: build

運行結(jié)果中碟绑,打印過程可以看到是按照我們上面圖 1 的執(zhí)行流程在運行的俺猿,但其中最值得關(guān)注的是 build 運行了兩次。這是在開發(fā)模式下才會執(zhí)行的過程格仲,在正式環(huán)境是不會出現(xiàn)的押袍,因為重新渲染成本非常大,這個問題可以使用打印 build 的調(diào)用堆棧即可發(fā)現(xiàn)凯肋。如果你要關(guān)閉兩次 build 也可以實現(xiàn)谊惭,在 Flutter 框架中搜索 constants.dart 文件,并找到下面這行代碼,將 defaultValue 從 false 修改為 true圈盔。

const bool kReleaseMode = bool.fromEnvironment('dart.vm.product', defaultValue: true);

其實這里會觸發(fā) didUpdateWidget 函數(shù)豹芯,是因為 TestStatefulWidget 組件是 MyApp 組件中的子組件,從而導(dǎo)致 MyApp 函數(shù)中的 build 觸發(fā)子組件 didUpdateWidget 函數(shù)的執(zhí)行驱敲,具體會在下面觸發(fā)組件再次 build 中詳細說明铁蹈。

3)、觸發(fā)組件再次 build

觸發(fā)組件再次 build 有三種方式: 一個是 setState 众眨,另一個是 didChangeDependencies 握牧,再一個是 didUpdateWidget 。

  • setState 比較容易理解娩梨,在數(shù)據(jù)狀態(tài)進行變化時沿腰,觸發(fā)組件 build ,在上面的代碼運行后的界面中狈定,點擊中間的text颂龙,如圖 2 位置,就可以看到在調(diào)用 setState 后纽什,會調(diào)用 build 一個方法厘托。
圖 2 測試組件運行界面
//點擊中間的text
flutter: set state
flutter: build
  • didChangeDependencies ,你可以理解為本組件依賴的全局 state 的值發(fā)生了變化稿湿,例如前端的 redux 中的數(shù)據(jù)發(fā)生了變化,也會進行 build 操作押赊。一般情況下我們會將一些比較基礎(chǔ)的數(shù)據(jù)放到全局變量中饺藤,例如主題顏色、地區(qū)語言或者其他通用變量等流礁。如果這些全局 state 發(fā)生狀態(tài)變化則會觸發(fā)該函數(shù)涕俗,而該函數(shù)之后就會觸發(fā) build 操作。

  • didUpdateWidget 觸發(fā) build 我們需要從代碼層面來講解下神帅,現(xiàn)在我們需要設(shè)計兩個組件再姑,一個是我們剛實現(xiàn)的 TestStatefulWidget ,另外一個則是該組件的子組件,我們命名為SubStatefulWidget 。接下來我們在 TestStatefulWidget 加載該組件既棺,在頭部 import 該組件滞伟,然后將 build 中的代碼修改為下面:

@override
Widget build(BuildContext context) {
  print('build');
  return Column(
    children: <Widget>[
      FlatButton(
        child: Text('$name $count'), // 使用 Text 組件顯示描述字符和當(dāng)前計算
        onPressed:()=> this.changeName(), // 點擊觸發(fā)修改描述字符 state name
      ),
      SubStatefulWidget() // 加載子組件
    ],
  );
}

接下來我們實現(xiàn) SubStatefulWidget 子組件的代碼,和父組件基本相似翘狱,只是在打印處都加了 sub ,其次 build 實現(xiàn)邏輯也修改了,具體代碼如下:

import 'package:flutter/material.dart';
/// 創(chuàng)建子組件類
class SubStatefulWidget extends StatefulWidget {
  @override
  createState() {
    print('sub create state');
    return SubState();
  }
}
/// 創(chuàng)建子組件狀態(tài)管理類
class SubState extends State<SubStatefulWidget> {
  String name = 'sub test';
  @override
  initState() {
    print('sub init state');
    super.initState();
  }
  @override
  didChangeDependencies() {
    print('sub did change dependencies');
    super.didChangeDependencies();
  }
  @override
  didUpdateWidget(SubStatefulWidget oldWidget) {
    print('sub did update widget');
    super.didUpdateWidget(oldWidget);
  }
  @override
  deactivate() {
    print('sub deactivate');
    super.deactivate();
  }
  @override
  dispose() {
    print('sub dispose');
    super.dispose();
  }
  @override
  reassemble(){
    print('sub reassemble');
    super.reassemble();
  }
  @override
  Widget build(BuildContext context) {
    print('sub build');
    return Text('subname $name'); // 使用Text組件顯示當(dāng)前name state
  }
}

代碼實現(xiàn)完成后遇革,我們再重新加載 App ,可以看到如下運行日志信息。

flutter: create state
flutter: init state
flutter: did change dependencies
flutter: build
flutter: sub create state
flutter: sub init state
flutter: sub did change dependencies
flutter: sub build
flutter: reassemble
flutter: sub reassemble
flutter: did update widget
flutter: build
flutter: sub did update widget
flutter: sub build
  • 加載 TestStatefulWidget 組件萝快,四個狀態(tài)函數(shù) createState锻霎、initState、didChangeDependencies 和 build揪漩;

  • 加載 SubStatefulWidget 組件旋恼,四個狀態(tài)函數(shù) createState、initState氢拥、didChangeDependencies 和 build蚌铜;

  • TestStatefulWidget 進行二次 build ,因為父組件需要重新 build 觸發(fā)子組件的 didUpdateWidget 嫩海,didUpdateWidget 則觸發(fā) build冬殃。

為了驗證上面邏輯,我們現(xiàn)在再次點擊圖 3 中的紅色部分叁怪,來觸發(fā) TestStatefulWidget 組件的 build 审葬,看下是否會觸發(fā)子組件的 didUpdateWidget 和 build。

圖 3 增加子組件界面點擊指示圖

在運行日志窗口可以看到增加了下面的日志信息奕谭。

flutter: set state
flutter: build
flutter: sub did update widget
flutter: sub build

這就說明了父組件的變化會引發(fā)子組件的 build 涣觉,雖然子組件沒有任何的改動。這點如果是在前端的話血柳,是需要使用 shouldUpdateComponent 官册,來介紹重新構(gòu)建,不過在 Flutter 中是沒有該功能來減少重新 build 的难捌。

4)膝宁、組件銷毀觸發(fā)

在上面的代碼基礎(chǔ)上,我們直接在 TestStatefulWidget 組件中注釋子組件 SubStatefulWidget 的調(diào)用根吁,然后熱重載即可看到下面的日志信息(請注意一定是需要熱重載才會有效果员淫,主要目的是一開始加載了該組件,后面再去掉該組件觸發(fā))击敌。

flutter: reassemble
flutter: sub reassemble
flutter: did update widget
flutter: build
flutter: sub deactivate
flutter: sub dispose

5) 補充: 生命周期屬性

其實生命周期中initState方法前后還有兩個屬性調(diào)用介返,如下圖:

1、mounted是State內(nèi)部設(shè)置的一個屬性沃斤,事實上我們不了解它也可以圣蝎,但是如果你想深入了解它,會對State的機制理解更加清晰衡瓶;

  • 很多資料沒有提到這個屬性捅彻,但是我這里把它列出來,是內(nèi)部設(shè)置的鞍陨,不需要我們手動進行修改步淹;
mounted屬性源碼

2从隆、dirty state的含義是臟的State

  • 它實際是通過一個Element的東西的屬性來標(biāo)記的;
  • 將它標(biāo)記為dirty會等待下一次的重繪檢查缭裆,強制調(diào)用build方法來構(gòu)建我們的Widget键闺;

3、clean state的含義是干凈的State

  • 它表示當(dāng)前build出來的Widget澈驼,下一次重繪檢查時不需要重新build辛燥;
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市缝其,隨后出現(xiàn)的幾起案子挎塌,更是在濱河造成了極大的恐慌,老刑警劉巖内边,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件榴都,死亡現(xiàn)場離奇詭異,居然都是意外死亡漠其,警方通過查閱死者的電腦和手機嘴高,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來和屎,“玉大人拴驮,你說我怎么就攤上這事〔裥牛” “怎么了套啤?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長随常。 經(jīng)常有香客問我潜沦,道長,這世上最難降的妖魔是什么线罕? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮窃判,結(jié)果婚禮上钞楼,老公的妹妹穿的比我還像新娘。我一直安慰自己袄琳,他們只是感情好询件,可當(dāng)我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著唆樊,像睡著了一般宛琅。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上逗旁,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天嘿辟,我揣著相機與錄音舆瘪,去河邊找鬼。 笑死红伦,一個胖子當(dāng)著我的面吹牛英古,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播昙读,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼召调,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蛮浑?” 一聲冷哼從身側(cè)響起唠叛,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎沮稚,沒想到半個月后艺沼,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡壮虫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年澳厢,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片囚似。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡剩拢,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出饶唤,到底是詐尸還是另有隱情徐伐,我是刑警寧澤,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布募狂,位于F島的核電站办素,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏祸穷。R本人自食惡果不足惜性穿,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望雷滚。 院中可真熱鬧需曾,春花似錦、人聲如沸祈远。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽车份。三九已至谋减,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間扫沼,已是汗流浹背出爹。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工庄吼, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人以政。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓霸褒,卻偏偏與公主長得像,于是被迫代替她去往敵國和親盈蛮。 傳聞我的和親對象是個殘疾皇子废菱,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,092評論 2 355