【老孟Flutter】Stateful 元件的生命週期
老孟導讀:關於生命週期的文章共有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
歡迎加入Flutter交流群(微信:laomengit)、關注公眾號【老孟Flutter】: