面試官問我State的生命周期混坞,該怎么回答

面試官:小伙狐援,你說說這State的生命周期是咋回事啊究孕?

學(xué)習(xí)最忌盲目啥酱,無計劃,零碎的知識點無法串成系統(tǒng)厨诸。學(xué)到哪镶殷,忘到哪,面試想不起來微酬。這里我整理了Flutter面試中最常問以及Flutter framework中最核心的幾塊知識绘趋,大概二十篇左右文章分析,歡迎關(guān)注颗管,共同進步陷遮。

導(dǎo)語

UI原理部分:

1、為什么不建議大家使用setState()忙上。

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的runtimeTypekey梨睁,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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末酌住,一起剝皮案震驚了整個濱河市店归,隨后出現(xiàn)的幾起案子阎抒,更是在濱河造成了極大的恐慌,老刑警劉巖娱节,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件挠蛉,死亡現(xiàn)場離奇詭異祭示,居然都是意外死亡肄满,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進店門质涛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來稠歉,“玉大人,你說我怎么就攤上這事汇陆∨ǎ” “怎么了?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵毡代,是天一觀的道長阅羹。 經(jīng)常有香客問我,道長教寂,這世上最難降的妖魔是什么捏鱼? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮酪耕,結(jié)果婚禮上导梆,老公的妹妹穿的比我還像新娘。我一直安慰自己迂烁,他們只是感情好看尼,可當(dāng)我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著盟步,像睡著了一般藏斩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上却盘,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天狰域,我揣著相機與錄音,去河邊找鬼谷炸。 笑死北专,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的旬陡。 我是一名探鬼主播拓颓,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼描孟!你這毒婦竟也來了驶睦?” 一聲冷哼從身側(cè)響起砰左,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎场航,沒想到半個月后缠导,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡溉痢,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年僻造,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片孩饼。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡髓削,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出镀娶,到底是詐尸還是另有隱情立膛,我是刑警寧澤,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響井佑,放射性物質(zhì)發(fā)生泄漏潘明。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦廓握、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至闹司,卻和暖如春娱仔,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背游桩。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工牲迫, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人借卧。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓盹憎,卻偏偏與公主長得像,于是被迫代替她去往敵國和親铐刘。 傳聞我的和親對象是個殘疾皇子陪每,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,611評論 2 353

推薦閱讀更多精彩內(nèi)容