Flutter中State的生命周期
Flutter中State類(狀態(tài))一般與StatefulWidget配合使用间护,來實(shí)現(xiàn)Widget的更新。State負(fù)責(zé)維護(hù)StatefulWidget的狀態(tài)又兵,并保存其狀態(tài)信息。當(dāng)Widget的狀態(tài)發(fā)生變化時卒废,用戶只需要調(diào)用setState()方法沛厨,F(xiàn)ultter引擎便會重新構(gòu)建Widget樹來更新UI。其中setState()方法更類似于一個消息機(jī)制摔认,用于通知Flutter引擎Widget狀態(tài)發(fā)生了變化逆皮,F(xiàn)lutter引擎收到通知后便會更新Widget樹。
由于State管理的StatefulWidget的狀態(tài)信息级野,并負(fù)責(zé)了StateWidget控件的更新页屠,因此了解其的生命周期對于Flutter開發(fā)具有重要意義。在某些應(yīng)用場景下我們需要知道State和Widget何時被構(gòu)建蓖柔,何時被銷毀以及何時被更新等辰企。
1. State簡介
State類的接口如以下代碼所示,其中我們開發(fā)中最常用的為build()和setState()方法,分別用于構(gòu)建Widget和通知Flutter引擎更新Widget樹况鸣。State類持有了一個Widget對象牢贸,通過該對象來重構(gòu)Widget樹,同時持有一個Widget的一個上下文镐捧,來獲取該Widget在Widget樹中的位置潜索。
//State為一個模板虛類,必須繼承才能夠使用
@optionalTypeArgs
abstract class State<T extends StatefulWidget> with Diagnosticable {
T? _widget; //State的實(shí)例中會持有一個Widget對象
StatefulElement? _element; //持有StatefulElement對象懂酱,構(gòu)建Element樹
BuildContext get context { //持有context對象竹习,實(shí)際上為StatefulElement
return _element!;
}
Widget build(BuildContext context); //構(gòu)建Widget樹
void initState(){} //初始化State
void didUpdateWidget(covariant T oldWidget) { } //更新Widget
void reassemble() {} //重新裝配Widget,一般在熱重載時調(diào)用
void setState(VoidCallback fn) {} //更新State列牺,后面詳細(xì)解釋
void deactivate() {} //注銷Widget整陌,將Widget從樹中移除,不釋放資源瞎领。
void dispose() {} //銷毀Widget泌辫,將Widget從樹中真正移除,并釋放資源
void didChangeDependencies() {} //更改依賴九默,State依賴發(fā)生變化
}
在開發(fā)中震放,我們一般只需要重載build()方法來定義子Widget,同時使用setState()方法來通知Flutter引擎更新UI驼修。其中build()方法完全由用戶定義的殿遂,這里我們給出setState()的主要代碼來探究setState()方法的運(yùn)行機(jī)制诈铛,代碼如下:
//setState()方法的主要代碼,略去了調(diào)試信息
@protected
void setState(VoidCallback fn) { //傳入一個回調(diào)函數(shù)fn
final dynamic result = fn() as dynamic;
_element!.markNeedsBuild(); //將Element標(biāo)記為需要重新build()
}
//owner為BuildOwner類型墨礁,同樣略去調(diào)試信息癌瘾。
void markNeedsBuild() {
owner!.scheduleBuildFor(this); //傳入該Element,并進(jìn)入Flutter引擎的調(diào)度序列饵溅,等待重構(gòu)
}
從setState()中的源碼可以看出,傳入的回調(diào)函數(shù)會被運(yùn)行妇萄,但是不影響Flutter引擎的build()過程蜕企,換句話說,即使回調(diào)函數(shù)fn()為空冠句,F(xiàn)lutter引擎也會去根據(jù)現(xiàn)有的值去更新UI轻掩。在代碼層面以下兩種書寫方式是等價的。
//書寫方式1:先更新UI值懦底,再調(diào)用setState()方法
TextButton(
child: Text('$_counter'),
onPressed:(){
++_counter; //先更新需要變動的UI值
setState(() {}); //再調(diào)用setState()方法
},
);
//書寫方式2:在setState()方法體中更新UI值
TextButton(
child: Text('$_counter'),
onPressed:(){
setState(() {
++_counter; //在setState()方法中更新UI值
});
},
);
但是為了代碼的可讀性唇牧,即讓程序員知道我們在什么地方刷新了UI,還是會將需要更新UI的值放在setState()的回調(diào)函數(shù)體中聚唐。
2.State()的生命周期
清楚地知道State()的生命周期對使用Flutter開發(fā)是很有意義的丐重。舉例來說,如果在某些情況下我們需要用到Widget中的參數(shù)來完成對State<Widget>的初始化操作杆查,我們就可以在initState()方法中通過value=widget.initValue的方式來進(jìn)行初始化扮惦。如果采用State<Widget>的構(gòu)造函數(shù)來進(jìn)行初始化的話,就會造成代碼的冗余和可讀性變差的問題亲桦。另外崖蜜,了解State何時被構(gòu)建何時被銷毀,對程序開發(fā)也具有重要的意義客峭。
在上一節(jié)豫领,我們以及較為詳細(xì)的介紹了State類中的成員,這里給出一個表格來詳細(xì)介紹State中方法的作用舔琅。
方法 | 作用 |
---|---|
initState() | 初始化State時調(diào)用等恐,將State插入渲染樹,只會調(diào)用一次 |
didChangeDependencies() | State依賴對象變化時調(diào)用搏明,如改變語言和主題時會調(diào)用該方法鼠锈。 |
didUpdateWidget() | 組件狀態(tài)改變時調(diào)用,可能調(diào)用多次 |
build() | 構(gòu)建Widget |
deactivate() | 注銷星著,將State移除渲染樹购笆,但是暫時不銷毀 |
dispose() | 銷毀,將State從內(nèi)存中移除 |
reassemble() | 重組虚循,當(dāng)熱面熱加載時會被調(diào)用 |
下面我們將以一個簡單的頁面跳轉(zhuǎn)的代碼來探究Flutter的生命周期同欠。代碼由兩個頁面構(gòu)成样傍,分別為StateDemoPage1(頁面1)和StateDemoPage2(頁面2),他們都只有一個TextButton控件铺遂,StateDemoPage1實(shí)現(xiàn)了到StateDemoPage2的一個簡單跳轉(zhuǎn)衫哥,StateDemoPage2實(shí)現(xiàn)了一個點(diǎn)擊更換Button顏色的邏輯,通過這個簡單跳轉(zhuǎn)我們來觀察State()的生命周期襟锐,代碼如下:
//頁面1代碼
class StateDemoPage1 extends StatefulWidget{
const StateDemoPage1({
Key key
});
@override
State<StatefulWidget> createState() {
return new _StateDemoPage1();
}
}
class _StateDemoPage1 extends State<StateDemoPage1>{
@override
Widget build(BuildContext context) {
print("_StateDemoPage1: build(創(chuàng)建Widget,構(gòu)建Widget樹)");
return Scaffold(
body: Center(
child: TextButton(
child: Text('跳轉(zhuǎn)到下個頁面'),
//點(diǎn)擊后計數(shù)器自增
onPressed:(){
Navigator.push(context, MaterialPageRoute(builder: (context){
StateDemoPage2();
}));
},
style: ButtonStyle(
backgroundColor: MaterialStateProperty.resolveWith((states) => Colors.blue)
)
),
),
);
}
@override
void initState() {
super.initState();
print("_StateDemoPage1: initState(初始化State)");
}
@override
void didUpdateWidget(covariant StateDemoPage1 oldWidget) {
super.didUpdateWidget(oldWidget);
print("_StateDemoPage1: didUpdateWidget(更新Widget)");
}
@override
void deactivate() {
super.deactivate();
print("_StateDemoPage1: deactive(注銷Widget撤逢,將Widget從樹中移除,不釋放資源粮坞。)");
}
@override
void dispose() {
super.dispose();
print("_StateDemoPage1: dispose(銷毀Widget蚊荣,將Widget從樹中真正移除,并釋放資源)");
}
@override
void reassemble() {
super.reassemble();
print("_StateDemoPage1: reassemble(重組Widget)");
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
print("_StateDemoPage1: didChangeDependencies(更改依賴莫杈,State依賴發(fā)生變化)");
}
_StateDemoPage1(){
print("_StateDemoPage1: constructor(更改依賴互例,State依賴發(fā)生變化)");
}
}
//頁面2代碼
class StateDemoPage2 extends StatefulWidget{
@override
State<StatefulWidget> createState() {
return new _StateDemoPage2();
}
}
class _StateDemoPage2 extends State<StateDemoPage2>{
Color mBackGroundColor = Colors.redAccent;
@override
Widget build(BuildContext context) {
print("_StateDemoPage2: build(創(chuàng)建Widget,構(gòu)建Widget樹)");
return Scaffold(
body: Center(
child: TextButton(
child: Text('這里是第二個頁面',
style: TextStyle(
color: Colors.white
),),
onPressed: (){
setState(() {
mBackGroundColor=Colors.green;
});
},
style: ButtonStyle(
backgroundColor: MaterialStateProperty.resolveWith((states) => mBackGroundColor)
)
),
),
);
}
@override
void initState() {
super.initState();
print("_StateDemoPage2: initState(初始化State)");
}
@override
void didUpdateWidget(covariant StateDemoPage2 oldWidget) {
super.didUpdateWidget(oldWidget);
print("_StateDemoPage2: didUpdateWidget(更新Widget)");
}
@override
void deactivate() {
super.deactivate();
print("_StateDemoPage2: deactive(注銷Widget,將Widget從樹中移除筝闹,不釋放資源媳叨。)");
}
@override
void dispose() {
super.dispose();
print("_StateDemoPage2: dispose(銷毀Widget,將Widget從樹中真正移除关顷,并釋放資源)");
}
@override
void reassemble() {
super.reassemble();
print("_StateDemoPage2: reassemble(重組Widget)");
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
print("_StateDemoPage2: didChangeDependencies(更改依賴糊秆,State依賴發(fā)生變化)");
}
_StateDemoPage2(){
print("_StateDemoPage2: constructor(構(gòu)造函數(shù))");
}
}
step1: 點(diǎn)擊開始運(yùn)行
頁面1中State的初始化過程: 當(dāng)運(yùn)行頁面1時,依次運(yùn)行了_StateDemoPage1的:構(gòu)造函數(shù)->initState()->didChangeDependencies()->build()议双。
_StateDemoPage1: constructor(構(gòu)造函數(shù))
_StateDemoPage1: initState(初始化State)
_StateDemoPage1: didChangeDependencies(更改依賴扩然,State依賴發(fā)生變化)
_StateDemoPage1: build(創(chuàng)建Widget,構(gòu)建Widget樹)
頁面1中State的熱重載過程: 當(dāng)我們對頁面1進(jìn)行熱重載的時候,運(yùn)行了_StateDemoPage1的:reassemble()->didUpdateWidget()->build()聋伦。
_StateDemoPage1: constructor(構(gòu)造函數(shù))
_StateDemoPage1: initState(初始化State)
_StateDemoPage1: didChangeDependencies(更改依賴夫偶,State依賴發(fā)生變化)
_StateDemoPage1: build(創(chuàng)建Widget,構(gòu)建Widget樹)
_StateDemoPage1: reassemble(重組Widget)
_StateDemoPage1: didUpdateWidget(更新Widget)
_StateDemoPage1: build(創(chuàng)建Widget,構(gòu)建Widget樹)
step2:在點(diǎn)擊頁面1中的跳轉(zhuǎn)按鈕后,跳轉(zhuǎn)到頁面2
頁面2初始化過程:此時觉增,頁面2進(jìn)入了初始化過程兵拢,其構(gòu)建過程與頁面1相同。而頁面1為不可見狀態(tài)逾礁,但是被沒有進(jìn)入銷毀過程说铃。終端輸出如下:
_StateDemoPage2: constructor(構(gòu)造函數(shù))
_StateDemoPage2: initState(初始化State)
_StateDemoPage2: didChangeDependencies(更改依賴,State依賴發(fā)生變化)
_StateDemoPage2: build(創(chuàng)建Widget,構(gòu)建Widget樹)
step3:此時點(diǎn)擊頁面2嘹履,更換到綠色按鈕
頁面2的更新過程: 此時腻扇,調(diào)用了build()方法來更新了UI。
_StateDemoPage2: build(創(chuàng)建Widget,構(gòu)建Widget樹)
step4: TopBar上的點(diǎn)擊返回
頁面2的銷毀過程: 此時,我們進(jìn)入了銷毀過程,依次調(diào)用了_StateDemoPage2的deactive()->dispose()方法來完成頁面2的銷毀岂贩。
_StateDemoPage2: deactive(注銷Widget郁惜,將Widget從樹中移除,不釋放資源席噩。)
_StateDemoPage2: dispose(銷毀Widget蛾坯,將Widget從樹中真正移除丑搔,并釋放資源)
StatefulWidget的生命周期如下圖所示括荡,我們可以大致分為3個階段:
- 初始化過程: constructor->initState()->didChangeDependencies()->build()
- 更新過程: reassemble()->didUpdateWidget()
- 銷毀過程: deactive()->dispose()
歡迎關(guān)注
參考文獻(xiàn)
[1] https://book.flutterchina.club/chapter3/
[2] https://blog.csdn.net/u011272795/article/details/82695920