學習最忌盲目,無計劃,零碎的知識點沒法串成系統。學到哪,忘到哪,面試想不起來。這裏我整理了Flutter面試中最常問以及Flutter framework中最核心的幾塊知識,大概二十篇左右文章分析,歡迎關注,共同進步。
面試
UI原理部分:markdown
二、面試官:小夥,這Widget和State的生命週期是咋回事啊?函數
三、Flutter的佈局約束原理佈局
四、實戰Flutter繪製過程post
讀完本文你將收穫:最詳細的生命週期分析學習
一次面試過程當中:ui
面試官:小夥子,不錯嘛,看你簡歷上寫你熟悉Flutter framework層啊!this
我:是的,是的(心虛)。spa
面試官:那好,那你和我說說State的生命週期吧。
就這?我不假思索的脫口而出:initState,build,deactive,dispose。
面試官:噢,就這幾個麼?
我(當心翼翼): .....哦 好像還有didChangeDependencies?
面試官:還有麼?
我:還有麼???
面試官:那你說說他們何時會被回調吧?
我:............. 你就在此處不要動。待我去給你買個橘子(康康源碼)。
不管是原生仍是Native,組件的生命週期必定是面試中必問的一個一個知識點,根據面試官的水平,程度可深可淺。但對於開發者而言,理解控件的生命週期回調過程,明白每一個函數的回調時機,能加深咱們對於framework層的理解,掌握到flutter背後的原理。
以"Flutter 生命週期"爲關鍵詞,能夠搜到相關不少博客,這張圖就是被反覆引用的一個流程圖。咋眼一看,好像啥都有,但若是深究一下好比:爲何這些生命週期是如何被回調?圖中說的「組件狀態改變」調用didUpdateWidget()
是什麼狀態改變?卻又沒法回答。下面咱們從一個demo和你們從新認識Flutter的生命週期。
如圖,是一個極爲簡單的demo,頁面一開始展現一個Text(我是第一次build的Text)。下方有一個按鈕,點擊以後調用setState()
切換到SecondWidget
。
而第SecondWidget
是一個StatefulWidget,裏面只是簡單的返回了一個Text( 我是被包裹在Stateful中的Text)。咱們以setState()
過程爲例關注SecondWidget
的生命週期,分爲三種狀況。
當咱們第一次點擊按鈕調用setState()的時候,直接來看其實只是至關於將Container下面的child由Text
更改成SecondWidget
。
在原來我一直在錯誤的使用 setState()中分析過,調用setState()
以後其實會對從當前節點開始,他的全部子孫節點調用updateChild(Element child, Widget newWidget, dynamic newSlot)
。
總的來講,這個方法會根據以前掛載在UI樹上的_child
以及再次調用build()出來的newWidget
對象,共有四種狀況
- 若是以前的位置child爲null
- A、若是newWidget爲null的話,說明這個位置始終沒有子節點,直接返回null便可。
- B、若是newWidget不爲null,說明這個位置新增長了子節點調用inflateWidget(newWidget, newSlot)生成一個新的Element返回
- 若是以前的child不爲null
- C、若是newWidget爲null的話,說明這個位置須要移除之前的節點,調用
deactivateChild(child)
移除而且返回null- D、若是newWidget不爲null的話,先調用
Widget.canUpdate(child.widget, newWidget)
對比是否能更新。這個方法會對比兩個Widget的runtimeType
和key
,一、若是一致則說明子Widget沒有改變,只是須要根據newWidget(配置清單)更新下當前節點的數據child.update(newWidget)
;二、若是不一致說明這個位置發生變化,則deactivateChild(child)
後返回inflateWidget(newWidget, newSlot)
對應到咱們的demo中,對於Container而言,在調用setState()以前,他的child是Text因此知足不爲空的條件,而setState將child改成了SecondWidget,可是Text和新生成的SecondWidget並不是同一種類型,因此會走到條件D的case2中,執行兩個流程一、Text的deactivateChild(child)
;二、SecondWidget的inflateWidget(newWidget, newSlot)
,咱們重點關注SecondWidget。
@protected
Element inflateWidget(Widget newWidget, dynamic newSlot) {
//建立一個element對象
final Element newChild = newWidget.createElement();
newChild.mount(this, newSlot);
return newChild;
}
複製代碼
一開始會先根據SecondWidget建立一個Element對象,以後調用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();
}
複製代碼
mount()
這個過程即將widget插入UI樹中,作一些標誌位的賦值,以後調用_firstBuild()
。
@override
void _firstBuild() {
assert(_state._debugLifecycleState == _StateLifecycle.created);
try {
_debugSetAllowIgnoredCallsToMarkNeedsBuild(true);
//首先調用initState()
final dynamic debugCheckForReturnedFuture = _state.initState() as dynamic;
}());
} finally {
_debugSetAllowIgnoredCallsToMarkNeedsBuild(false);
}
//其次調用didChangeDependencies()
_state.didChangeDependencies();
assert(() {
_state._debugLifecycleState = _StateLifecycle.ready;
return true;
}());
//在super中執行當前state的build
super._firstBuild();
}
複製代碼
這個方法在StateElement中被重寫,是生命週期的關鍵所在。如字面意義,他表示第一次構建的時候調用的方法。在這個過程當中咱們清晰的看出State對象會經歷三個回調:
一、_state.initState()
二、_state.didChangeDependencies()
三、 super._firstBuild()最終調用state.build(this)
這個過程結束以後SecondWidget生成的Element對象已經被咱們插入到了UI樹中,以後渲染流程中,將其展現到屏幕上。
總結:當咱們一個StatefulWidget第一次被渲染到屏幕上時,在State中會經歷initState(),didChangeDependencies(),build(BuildContext context)三個方法。
假如如今SecondWidget已經被渲染到屏幕上了以後,若是咱們再次點擊調用setState()。這時會發現,對於Container節點而言,他的child不爲空,且始終都是SecondWidget,而且因爲咱們沒指定key對象,因此Widget.canUpdate(child.widget, newWidget)
是返回true,對應上面條件D的case1執行child.update(newWidget)
@override
void update(StatefulWidget newWidget) {
super.update(newWidget);
final StatefulWidget oldWidget = _state._widget;
_dirty = true;
_state._widget = widget;
try {
//一、先調用didUpdateWidget(oldWidget)
final dynamic debugCheckForReturnedFuture = _state.didUpdateWidget(oldWidget) as dynamic;
} finally {
_debugSetAllowIgnoredCallsToMarkNeedsBuild(false);
}
//二、調用rebuild()
rebuild();
}
複製代碼
這個過程也很是清晰,在第二次調用setState()
中,state會經歷兩個回調:
一、_state.didUpdateWidget(oldWidget)
二、state.build(this)
但不只如此,若是在SecondWidget中使用dependOnInheritedWidgetOfExactType()
方法依賴了來自頂層的InheritedWidget
數據之時。若是InheritedWidget
發生了update()
,會先調用全部依賴這個InheritedWidget
對象中的didChangeDependencies()
(詳情能夠學習InheritedWidget
的依賴更新機制)。而且因爲當前的widget通常做爲子節點,因此也會執行上面的update()
過程
總結:
- 若是第二次調用setState(),當前的state對象會走:一、didUpdateWidget();二、build()
- 若是是依賴的頂層
InheritedWidget
發生了改變,則會調用一、didChangeDependencies(); 二、didUpdateWidget();三、build()
第三種狀況能夠參考狀況一種被移除的Text對象,咱們提到,當一個State被移除的時候會調用其deactivateChild(Element child)
方法
@protected
void deactivateChild(Element child) {
child._parent = null;
child.detachRenderObject();
owner._inactiveElements.add(child); // this eventually calls child.deactivate()
}
複製代碼
這個方法會將當前的對象添加到一個_inactiveElements
集合中,而且最終調用deactivate()
。
以後在下一幀繪製回調到finalizeTree()
的時候,執行unmount()
完全清理。
@override
void unmount() {
super.unmount();
_state.dispose();
_state._element = null;
_state = null;
}
複製代碼
總結:
噹噹前的Widget從屏幕移除的時候回先調用deactivate(),以後在下一幀繪製以前清理UI樹的時候被完全清理,回調dispose()
讀完源碼後,我給面試官這樣說道:
state的生命週期其實能夠用這麼一張圖理解:
面試官直呼內行,立即給了我ssp的offer。
結果收到offer郵件的時候,鬧鐘醒了.....
(to be continued )
Widget層面的內容基本用兩篇文章講完了,下期將針對Flutter的佈局原理和你們一塊兒看看,爲何Container默認撐滿了整個佈局,爲何這個佈局和我想的不同等等奇怪的佈局現象背後的原理~。
據說點讚的人面試,HR必發ssp offser哦