老孟導讀:關于生命周期的文章共有2篇,第一篇是介紹 Flutter 中Stateful 組件的生命周期。
博客地址:http://laomengit.com/blog/20201227/Stateful%E7%BB%84%E4%BB%B6%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F.html第二篇是 Flutter 中與平臺相關的生命周期,
博客中還有更多精彩文章,也歡迎加入 Flutter 交流群。
此篇文章介紹 StatefulWidget 組件的生命周期, StatefulWidget 組件的生命周期時非常重要的知識點,就像 Android 中 Activity 的生命周期一樣,不僅在以后的工作中經常用到,面試也會經常被問到。
在 Flutter 中一切皆 組件,而組件又分為 StatefulWidget(有狀態) 和 StatelessWidget(無狀態)組件 ,他們之間的區別是 StatelessWidget 組件發生變化時必須重新創建新的實例,而 StatefulWidget 組件則可以直接改變當前組件的狀態而無需重新創建新的實例。
注意:使用的 Flutter 版本 和 Dart 版本如下:
Flutter 1.22.4 ? channel stable ? https://github.com/flutter/flutter.git
Framework ? revision 1aafb3a8b9 (6 weeks ago) ? 2020-11-13 09:59:28 -0800
Engine ? revision 2c956a31c0
Tools ? Dart 2.10.4不同的版本 StatefulWidget 組件的生命周期會有差異。
下面的 StatefulWidget 和 State 結構圖是StatefulWidget 組件生命周期的概覽,不同版本的差異也可以對比此結構圖。
生命周期流程圖:
下面詳細介紹 StatefulWidget 組件的生命周期。
生命周期一:createState
下面是一個非常簡單的 StatefulWidget 組件:
class StatefulWidgetDemo extends StatefulWidget {
@override
_StatefulWidgetDemoState createState() => _StatefulWidgetDemoState();
}
class _StatefulWidgetDemoState extends State<StatefulWidgetDemo> {
@override
Widget build(BuildContext context) {
return Container();
}
}
當我們構建一個 StatefulWidget 組件時,首先執行其構造函數(上面的代碼沒有顯示的構造函數,但有默認的無參構造函數),然后執行 createState 函數。但構造函數并不是生命周期的一部分。
當 StatefulWidget 組件插入到組件樹中時 createState 函數由 Framework 調用,此函數在樹中給定的位置為此組件創建 State,如果在組件樹的不同位置都插入了此組件,即創建了多個此組件,如下:
Row(children: [
MyStatefulWidget(),
MyStatefulWidget(),
MyStatefulWidget(),
],)
那么系統會為每一個組件創建一個單獨的 State,當組件從組件樹中移除,然后重新插入到組件樹中時, createState 函數將會被調用創建一個新的 State。
createState 函數執行完畢后表示當前組件已經在組件樹中,此時有一個非常重要的屬性 mounted 被 Framework 設置為 true。
生命周期二:initState
initState 函數在組件被插入樹中時被 Framework 調用(在 createState 之后),此函數只會被調用一次,子類通常會重寫此方法,在其中進行初始化操作,比如加載網絡數據,重寫此方法時一定要調用 super.initState(),如下:
@override
void initState() {
super.initState();
//初始化...
}
如果此組件需要訂閱通知,比如 ChangeNotifier 或者 Stream,則需要在不同的生命周期內正確處理訂閱和取消訂閱通知。
- 在 initState 中訂閱通知。
- 在 didUpdateWidget 中,如果需要替換舊組件,則在舊對象中取消訂閱,并在新對象中訂閱通知。
- 并在 dispose 中取消訂閱。
另外在此函數中不能調用 BuildContext.dependOnInheritedWidgetOfExactType,典型的錯誤寫法如下:
@override
void initState() {
super.initState();
IconTheme iconTheme = context.dependOnInheritedWidgetOfExactType<IconTheme>();
}
異常信息如下:
解決方案:
@override
void didChangeDependencies() {
super.didChangeDependencies();
context.dependOnInheritedWidgetOfExactType<IconTheme>();
}
上面的用法作為初學者使用的比較少,但下面的錯誤代碼大部分應該都寫過:
@override
void initState() {
super.initState();
showDialog(context: context,builder: (context){
return AlertDialog();
});
}
異常信息如下:
解決方案:
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
showDialog(context: context,builder: (context){
return AlertDialog(title: Text('AlertDialog'),);
});
});
}
注意:彈出 AlertDialog 在 didChangeDependencies 中調用也會出現異常,但和上面的異常不是同一個。
生命周期三:didChangeDependencies
didChangeDependencies 方法在 initState 之后由 Framework 立即調用。另外,當此 State 對象的依賴項更改時被調用,比如其所依賴的 InheritedWidget 發生變化時, Framework 會調用此方法通知組件發生變化。
此方法是生命周期中第一個可以使用 BuildContext.dependOnInheritedWidgetOfExactType 的方法,此方法很少會被重寫,因為 Framework 會在依賴發生變化時調用 build,需要重寫此方法的場景是:依賴發生變化時需要做一些耗時任務,比如網絡請求數據。
didChangeDependencies 方法調用后,組件的狀態變為 dirty,立即調用 build 方法。
生命周期四:build
此方法是我們最熟悉的,在方法中創建各種組件,繪制到屏幕上。 Framework會在多種情況下調用此方法:
- 調用 initState 方法后。
- 調用 didUpdateWidget 方法后。
- 收到對 setState 的調用后。
- 此 State 對象的依存關系發生更改后(例如,依賴的 InheritedWidget 發生了更改)。
- 調用 deactivate 之后,然后將 State 對象重新插入樹的另一個位置。
此方法可以在每一幀中調用,此方法中應該只包含構建組件的代碼,不應該包含其他額外的功能,尤其是耗時任務。
生命周期五:didUpdateWidget
當組件的 configuration 發生變化時調用此函數,當父組件使用相同的 runtimeType 和 Widget.key 重新構建一個新的組件時,Framework 將更新此 State 對象的組件屬性以引用新的組件,然后使用先前的組件作為參數調用此方法。
@override
void didUpdateWidget(covariant StatefulLifecycle oldWidget) {
super.didUpdateWidget(oldWidget);
print('didUpdateWidget');
}
此方法中通常會用當前組件與前組件進行對比。Framework 調用完此方法后,會將組件設置為 dirty 狀態,然后調用 build 方法,因此無需在此方法中調用 setState 方法。
生命周期六:deactivate
當框架從樹中移除此 State 對象時將會調用此方法,在某些情況下,框架將重新插入 State 對象到樹的其他位置(例如,如果包含該樹的子樹 State 對象從樹中的一個位置移植到另一位置),框架將會調用 build 方法來提供 State 對象適應其在樹中的新位置。
生命周期七:dispose
當框架從樹中永久移除此 State 對象時將會調用此方法,與 deactivate 的區別是,deactivate 還可以重新插入到樹中,而 dispose 表示此 State 對象永遠不會在 build。調用完 dispose后,mounted 屬性被設置為 false,也代表組件生命周期的結束,此時再調用 setState 方法將會拋出異常。
子類重寫此方法,釋放相關資源,比如動畫等。
非常重要的幾個概念
下面介紹幾個非常重要的概念和方法,這些并不是生命周期的一部分,但是生命周期過程中的產物,與生命周期關系非常緊密。
mounted
mounted 是 State 對象中的一個屬性,此屬性表示當前組件是否在樹中,在創建 State 之后,調用 initState 之前,Framework 會將 State 和 BuildContext 進行關聯,當 Framework 調用 dispose 時,mounted 被設置為 false,表示當前組件已經不在樹中。
createState 函數執行完畢后表示當前組件已經在組件樹中,屬性 mounted 被 Framework 設置為 true,平時寫代碼時或者看其他開源代碼時經常看到如下代碼:
if(mounted){
setState(() {
...
});
}
強烈建議:在調用 setState 時加上 mounted 判斷。
為什么要加上如此判斷?因為如果當前組件未插入到樹中或者已經從樹中移除時,調用 setState 會拋出異常,加上 mounted 判斷,則表示當前組件在樹中。
dirty 和 clean
dirty 表示組件當前的狀態為 臟狀態,下一幀時將會執行 build 函數,調用 setState 方法或者 執行 didUpdateWidget 方法后,組件的狀態為 dirty。
clean 與 dirty 相對應,clean 表示組件當前的狀態為 干凈狀態,clean 狀態下組件不會執行 build 函數。
setState
setState 方法是開發者經常調用的方法,此方法調用后,組件的狀態變為 dirty,當有數據要更新時,調用此方法。
reassemble
reassemble 用于開發,比如 hot reload ,在 release 版本中不會回調此方法。
交流
老孟Flutter博客(330個控件用法+實戰入門系列文章):http://laomengit.com