面試官問我State的生命週期,該怎麼回答

面試官:小夥,你說說這State的生命週期是咋回事啊?

學習最忌盲目,無計劃,零碎的知識點沒法串成系統。學到哪,忘到哪,面試想不起來。這裏我整理了Flutter面試中最常問以及Flutter framework中最核心的幾塊知識,大概二十篇左右文章分析,歡迎關注,共同進步。flutter framework面試

導語

UI原理部分:markdown

一、爲何不建議你們使用setState()。ide

二、面試官:小夥,這Widget和State的生命週期是咋回事啊?函數

三、Flutter的佈局約束原理佈局

四、實戰Flutter繪製過程post

讀完本文你將收穫:最詳細的生命週期分析學習


引言

一次面試過程當中:ui

面試官:小夥子,不錯嘛,看你簡歷上寫你熟悉Flutter framework層啊!this

我:是的,是的(心虛)。spa

面試官:那好,那你和我說說State的生命週期吧。

就這?我不假思索的脫口而出:initState,build,deactive,dispose。

面試官:噢,就這幾個麼?

我(當心翼翼): .....哦 好像還有didChangeDependencies?

面試官:還有麼?

我:還有麼???

面試官:那你說說他們何時會被回調吧?

我:............. 你就在此處不要動。待我去給你買個橘子(康康源碼)。

不管是原生仍是Native,組件的生命週期必定是面試中必問的一個一個知識點,根據面試官的水平,程度可深可淺。但對於開發者而言,理解控件的生命週期回調過程,明白每一個函數的回調時機,能加深咱們對於framework層的理解,掌握到flutter背後的原理。


一、初識State的生命週期

以"Flutter 生命週期"爲關鍵詞,能夠搜到相關不少博客,這張圖就是被反覆引用的一個流程圖。咋眼一看,好像啥都有,但若是深究一下好比:爲何這些生命週期是如何被回調?圖中說的「組件狀態改變」調用didUpdateWidget()是什麼狀態改變?卻又沒法回答。下面咱們從一個demo和你們從新認識Flutter的生命週期。


如圖,是一個極爲簡單的demo,頁面一開始展現一個Text(我是第一次build的Text)。下方有一個按鈕,點擊以後調用setState()切換到SecondWidget

而第SecondWidget是一個StatefulWidget,裏面只是簡單的返回了一個Text( 我是被包裹在Stateful中的Text)。咱們以setState()過程爲例關注SecondWidget的生命週期,分爲三種狀況。


狀況1:第一次setState() -> State第一次展現

當咱們第一次點擊按鈕調用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的runtimeTypekey,一、若是一致則說明子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)三個方法。


狀況二:第二次setState() -> State被刷新

假如如今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()

狀況三:SecondWidget被移除 -> State被移除

第三種狀況能夠參考狀況一種被移除的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的生命週期其實能夠用這麼一張圖理解:

  • 一、第一次展現到屏幕上時會依次調用當前element的構造函數,initState,didChangeDependencies,build
  • 二、若是隻是本身發生了更新,則只會回調build。若是當前對象的父節點發生更新,則會調用didUpdateWidget和build。若是依賴的InheritedWidget發生了改變,則還會先回調didChangeDependencies。
  • 三、當widget被移除的的時候,會依次調用deactive和dispose

面試官直呼內行,立即給了我ssp的offer。

結果收到offer郵件的時候,鬧鐘醒了.....

(to be continued )

最後

Widget層面的內容基本用兩篇文章講完了,下期將針對Flutter的佈局原理和你們一塊兒看看,爲何Container默認撐滿了整個佈局,爲何這個佈局和我想的不同等等奇怪的佈局現象背後的原理~。

據說點讚的人面試,HR必發ssp offser哦

相關文章
相關標籤/搜索