面試官:小伙狐援,你說說這State的生命周期是咋回事啊究孕?
學(xué)習(xí)最忌盲目啥酱,無計劃,零碎的知識點無法串成系統(tǒng)厨诸。學(xué)到哪镶殷,忘到哪,面試想不起來微酬。這里我整理了Flutter面試中最常問以及Flutter framework中最核心的幾塊知識绘趋,大概二十篇左右文章分析,歡迎關(guān)注颗管,共同進步陷遮。
導(dǎo)語
UI原理部分:
2拷呆、面試官:小伙闲坎,這Widget和State的生命周期是咋回事耙咧唷?
3腰懂、Flutter的布局約束原理
4梗逮、實戰(zhàn)Flutter繪制過程
讀完本文你將收獲:最詳細(xì)的生命周期分析
引言
一次面試過程中:
面試官:小伙子,不錯嘛绣溜,看你簡歷上寫你熟悉Flutter framework層翱锻!
我:是的,是的(心虛)底哗。
面試官:那好岁诉,那你和我說說State的生命周期吧。
就這跋选?我不假思索的脫口而出:initState涕癣,build,deactive前标,dispose坠韩。
面試官:噢,就這幾個么炼列?
我(小心翼翼): .....哦 好像還有didChangeDependencies?
面試官:還有么只搁?
我:還有么?俭尖?氢惋?
面試官:那你說說他們什么時候會被回調(diào)吧?
我:............. 你就在此處不要動稽犁。待我去給你買個橘子(康康源碼)明肮。
無論是原生還是Native,組件的生命周期一定是面試中必問的一個一個知識點缭付,根據(jù)面試官的水平柿估,程度可深可淺。但對于開發(fā)者而言陷猫,理解控件的生命周期回調(diào)過程秫舌,明白每個函數(shù)的回調(diào)時機,能加深我們對于framework層的理解绣檬,掌握到flutter背后的原理足陨。
1、初識State的生命周期
以"Flutter 生命周期"為關(guān)鍵詞娇未,可以搜到相關(guān)很多博客墨缘,這張圖就是被反復(fù)引用的一個流程圖。咋眼一看零抬,好像啥都有镊讼,但如果深究一下比如:為什么這些生命周期是如何被回調(diào)?圖中說的“組件狀態(tài)改變”調(diào)用didUpdateWidget()
是什么狀態(tài)改變平夜?卻又無法回答蝶棋。下面我們從一個demo和大家重新認(rèn)識Flutter的生命周期。
如圖忽妒,是一個極為簡單的demo玩裙,頁面一開始展示一個Text(我是第一次build的Text)兼贸。下方有一個按鈕,點擊之后調(diào)用setState()
切換到SecondWidget
吃溅。
而第SecondWidget
是一個StatefulWidget溶诞,里面只是簡單的返回了一個Text( 我是被包裹在Stateful中的Text)。我們以setState()
過程為例關(guān)注SecondWidget
的生命周期决侈,分為三種情況很澄。
情況1:第一次setState() -> State第一次展示
當(dāng)我們第一次點擊按鈕調(diào)用setState()的時候,直接來看其實只是相當(dāng)于將Container下面的child由Text
更改為SecondWidget
颜及。
在原來我一直在錯誤的使用 setState()中分析過甩苛,調(diào)用setState()
之后其實會對從當(dāng)前節(jié)點開始,他的所有子孫節(jié)點調(diào)用updateChild(Element child, Widget newWidget, dynamic newSlot)
俏站。
總的來說讯蒲,這個方法會根據(jù)之前掛載在UI樹上的_child
以及再次調(diào)用build()出來的newWidget
對象,共有四種情況
- 如果之前的位置child為null
- A肄扎、如果newWidget為null的話墨林,說明這個位置始終沒有子節(jié)點,直接返回null即可犯祠。
- B旭等、如果newWidget不為null,說明這個位置新增加了子節(jié)點調(diào)用inflateWidget(newWidget, newSlot)生成一個新的Element返回
- 如果之前的child不為null
- C衡载、如果newWidget為null的話搔耕,說明這個位置需要移除以前的節(jié)點,調(diào)用
deactivateChild(child)
移除并且返回null- D痰娱、如果newWidget不為null的話弃榨,先調(diào)用
Widget.canUpdate(child.widget, newWidget)
對比是否能更新。這個方法會對比兩個Widget的runtimeType
和key
梨睁,1鲸睛、如果一致則說明子Widget沒有改變,只是需要根據(jù)newWidget(配置清單)更新下當(dāng)前節(jié)點的數(shù)據(jù)child.update(newWidget)
坡贺;2官辈、如果不一致說明這個位置發(fā)生變化,則deactivateChild(child)
后返回inflateWidget(newWidget, newSlot)
對應(yīng)到我們的demo中遍坟,對于Container而言拳亿,在調(diào)用setState()之前,他的child是Text所以滿足不為空的條件政鼠,而setState將child改為了SecondWidget风瘦,但是Text和新生成的SecondWidget并非同一種類型,所以會走到條件D的case2中公般,執(zhí)行兩個流程1万搔、Text的deactivateChild(child)
;2官帘、SecondWidget的inflateWidget(newWidget, newSlot)
,我們重點關(guān)注SecondWidget瞬雹。
@protected
Element inflateWidget(Widget newWidget, dynamic newSlot) {
//創(chuàng)建一個element對象
final Element newChild = newWidget.createElement();
newChild.mount(this, newSlot);
return newChild;
}
復(fù)制代碼
一開始會先根據(jù)SecondWidget創(chuàng)建一個Element對象,之后調(diào)用newChild.mount(this, newSlot)
刽虹。
@override
void mount(Element parent, dynamic newSlot) {
_parent = parent;
_slot = newSlot;
_depth = _parent != null ? _parent.depth + 1 : 1;
_active = true;
_firstBuild();
}
復(fù)制代碼
mount()
這個過程即將widget插入UI樹中酗捌,做一些標(biāo)志位的賦值,之后調(diào)用_firstBuild()
涌哲。
@override
void _firstBuild() {
assert(_state._debugLifecycleState == _StateLifecycle.created);
try {
_debugSetAllowIgnoredCallsToMarkNeedsBuild(true);
//首先調(diào)用initState()
final dynamic debugCheckForReturnedFuture = _state.initState() as dynamic;
return true;
}());
} finally {
_debugSetAllowIgnoredCallsToMarkNeedsBuild(false);
}
//其次調(diào)用didChangeDependencies()
_state.didChangeDependencies();
assert(() {
_state._debugLifecycleState = _StateLifecycle.ready;
return true;
}());
//在super中執(zhí)行當(dāng)前state的build
super._firstBuild();
}
復(fù)制代碼
這個方法在StateElement中被重寫胖缤,是生命周期的關(guān)鍵所在。如字面意義阀圾,他表示第一次構(gòu)建的時候調(diào)用的方法哪廓。在這個過程中我們清晰的看出State對象會經(jīng)歷三個回調(diào):
1、_state.initState()
2初烘、_state.didChangeDependencies()
3涡真、 super._firstBuild()最終調(diào)用state.build(this)
這個過程結(jié)束之后SecondWidget生成的Element對象已經(jīng)被我們插入到了UI樹中,之后渲染流程中肾筐,將其展示到屏幕上哆料。
總結(jié):當(dāng)我們一個StatefulWidget第一次被渲染到屏幕上時,在State中會經(jīng)歷initState(),didChangeDependencies()吗铐,build(BuildContext context)三個方法东亦。
情況二:第二次setState() -> State被刷新
假如現(xiàn)在SecondWidget已經(jīng)被渲染到屏幕上了之后,如果我們再次點擊調(diào)用setState()唬渗。這時會發(fā)現(xiàn)讥此,對于Container節(jié)點而言,他的child不為空谣妻,且始終都是SecondWidget萄喳,并且由于我們沒指定key對象,所以Widget.canUpdate(child.widget, newWidget)
是返回true蹋半,對應(yīng)上面條件D的case1執(zhí)行child.update(newWidget)
@override
void update(StatefulWidget newWidget) {
super.update(newWidget);
final StatefulWidget oldWidget = _state._widget;
_dirty = true;
_state._widget = widget;
try {
//1他巨、先調(diào)用didUpdateWidget(oldWidget)
final dynamic debugCheckForReturnedFuture = _state.didUpdateWidget(oldWidget) as dynamic;
} finally {
_debugSetAllowIgnoredCallsToMarkNeedsBuild(false);
}
//2、調(diào)用rebuild()
rebuild();
}
復(fù)制代碼
這個過程也非常清晰减江,在第二次調(diào)用setState()
中染突,state會經(jīng)歷兩個回調(diào):
1、_state.didUpdateWidget(oldWidget)
2辈灼、state.build(this)
但不僅如此份企,如果在SecondWidget中使用dependOnInheritedWidgetOfExactType()
方法依賴了來自頂層的InheritedWidget
數(shù)據(jù)之時。如果InheritedWidget
發(fā)生了update()
巡莹,會先調(diào)用所有依賴這個InheritedWidget
對象中的didChangeDependencies()
(詳情可以學(xué)習(xí)InheritedWidget
的依賴更新機制)司志。并且由于當(dāng)前的widget一般作為子節(jié)點甜紫,所以也會執(zhí)行上面的update()
過程
總結(jié):
- 如果第二次調(diào)用setState(),當(dāng)前的state對象會走:1骂远、didUpdateWidget()囚霸;2、build()
- 如果是依賴的頂層
InheritedWidget
發(fā)生了改變激才,則會調(diào)用1拓型、didChangeDependencies(); 2瘸恼、didUpdateWidget()劣挫;3、build()
情況三:SecondWidget被移除 -> State被移除
第三種情況可以參考情況一種被移除的Text對象东帅,我們提到压固,當(dāng)一個State被移除的時候會調(diào)用其deactivateChild(Element child)
方法
@protected
void deactivateChild(Element child) {
child._parent = null;
child.detachRenderObject();
owner._inactiveElements.add(child); // this eventually calls child.deactivate()
}
復(fù)制代碼
這個方法會將當(dāng)前的對象添加到一個_inactiveElements
集合中,并且最終調(diào)用deactivate()
冰啃。
之后在下一幀繪制回調(diào)到finalizeTree()
的時候邓夕,執(zhí)行unmount()
徹底清理。
@override
void unmount() {
super.unmount();
_state.dispose();
_state._element = null;
_state = null;
}
復(fù)制代碼
總結(jié):
當(dāng)當(dāng)前的Widget從屏幕移除的時候回先調(diào)用deactivate()阎毅,之后在下一幀繪制之前清理UI樹的時候被徹底清理焚刚,回調(diào)dispose()
總結(jié)
讀完源碼后,我給面試官這樣說道:
state的生命周期其實可以用這么一張圖理解:
- 1扇调、第一次展示到屏幕上時會依次調(diào)用當(dāng)前element的構(gòu)造函數(shù),initState,didChangeDependencies,build
- 2矿咕、如果只是自己發(fā)生了更新,則只會回調(diào)build狼钮。如果當(dāng)前對象的父節(jié)點發(fā)生更新碳柱,則會調(diào)用didUpdateWidget和build。如果依賴的InheritedWidget發(fā)生了改變熬芜,則還會先回調(diào)didChangeDependencies莲镣。
- 3、當(dāng)widget被移除的的時候涎拉,會依次調(diào)用deactive和dispose
面試官直呼內(nèi)行瑞侮,當(dāng)即給了我ssp的offer。
結(jié)果收到offer郵件的時候鼓拧,鬧鐘醒了.....
(to be continued )
最后
Widget層面的內(nèi)容基本用兩篇文章講完了半火,下期將針對Flutter的布局原理和大家一起看看,為什么Container默認(rèn)撐滿了整個布局季俩,為什么這個布局和我想的不一樣等等奇怪的布局現(xiàn)象背后的原理~钮糖。
聽說點贊的人面試,HR必發(fā)ssp offser哦
作者:Nayuta
鏈接:https://juejin.cn/post/6908574202253541389