面试官问我State的生命周期,该怎么回答

面试官:小伙,你说说这State的生命周期是咋回事啊?

学习最忌盲目,无计划,零碎的知识点无法串成系统。学到哪,忘到哪,面试想不起来。这里我整理了Flutter面试中最常问以及Flutter framework中最核心的几块知识,大概二十篇左右文章分析,欢迎关注,共同进步。

导语

UI原理部分:

1、为什么不建议大家使用setState()。

2、面试官:小伙,这Widget和State的生命周期是咋回事啊?

3、Flutter的布局约束原理

4、实战Flutter绘制过程

读完本文你将收获:最详细的生命周期分析


引言

一次面试过程中:

面试官:小伙子,不错嘛,看你简历上写你熟悉Flutter framework层啊!

我:是的,是的(心虚)。

面试官:那好,那你和我说说State的生命周期吧。

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

面试官:噢,就这几个么?

我(小心翼翼): .....哦 好像还有didChangeDependencies?

面试官:还有么?

我:还有么???

面试官:那你说说他们什么时候会被回调吧?

我:............. 你就在此处不要动。待我去给你买个橘子(康康源码)。

无论是原生还是Native,组件的生命周期一定是面试中必问的一个一个知识点,根据面试官的水平,程度可深可浅。但对于开发者而言,理解控件的生命周期回调过程,明白每个函数的回调时机,能加深我们对于framework层的理解,掌握到flutter背后的原理。


1、初识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,1、如果一致则说明子Widget没有改变,只是需要根据newWidget(配置清单)更新下当前节点的数据child.update(newWidget);2、如果不一致说明这个位置发生变化,则deactivateChild(child)后返回inflateWidget(newWidget, newSlot)

对应到我们的demo中,对于Container而言,在调用setState()之前,他的child是Text所以满足不为空的条件,而setState将child改为了SecondWidget,但是Text和新生成的SecondWidget并非同一种类型,所以会走到条件D的case2中,执行两个流程1、Text的deactivateChild(child);2、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;
      return true;
    }());
  } finally {
    _debugSetAllowIgnoredCallsToMarkNeedsBuild(false);
  }
  //其次调用didChangeDependencies()
  _state.didChangeDependencies();
  assert(() {
    _state._debugLifecycleState = _StateLifecycle.ready;
    return true;
  }());
  //在super中执行当前state的build
  super._firstBuild();
}
复制代码

这个方法在StateElement中被重写,是生命周期的关键所在。如字面意义,他表示第一次构建的时候调用的方法。在这个过程中我们清晰的看出State对象会经历三个回调:

1、_state.initState()

2、_state.didChangeDependencies()

3、 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 {
    //1、先调用didUpdateWidget(oldWidget)
    final dynamic debugCheckForReturnedFuture = _state.didUpdateWidget(oldWidget) as dynamic;
  } finally {
    _debugSetAllowIgnoredCallsToMarkNeedsBuild(false);
  }
  //2、调用rebuild()
  rebuild();
}
复制代码

这个过程也非常清晰,在第二次调用setState()中,state会经历两个回调:

1、_state.didUpdateWidget(oldWidget)

2、state.build(this)

但不仅如此,如果在SecondWidget中使用dependOnInheritedWidgetOfExactType()方法依赖了来自顶层的InheritedWidget数据之时。如果InheritedWidget发生了update(),会先调用所有依赖这个InheritedWidget对象中的didChangeDependencies()(详情可以学习InheritedWidget的依赖更新机制)。并且由于当前的widget一般作为子节点,所以也会执行上面的update()过程

总结:

  • 如果第二次调用setState(),当前的state对象会走:1、didUpdateWidget();2、build()
  • 如果是依赖的顶层InheritedWidget发生了改变,则会调用1、didChangeDependencies(); 2、didUpdateWidget();3、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的生命周期其实可以用这么一张图理解:

  • 1、第一次展示到屏幕上时会依次调用当前element的构造函数,initState,didChangeDependencies,build
  • 2、如果只是自己发生了更新,则只会回调build。如果当前对象的父节点发生更新,则会调用didUpdateWidget和build。如果依赖的InheritedWidget发生了改变,则还会先回调didChangeDependencies。
  • 3、当widget被移除的的时候,会依次调用deactive和dispose

面试官直呼内行,当即给了我ssp的offer。

结果收到offer邮件的时候,闹钟醒了.....

(to be continued )

最后

Widget层面的内容基本用两篇文章讲完了,下期将针对Flutter的布局原理和大家一起看看,为什么Container默认撑满了整个布局,为什么这个布局和我想的不一样等等奇怪的布局现象背后的原理~。

听说点赞的人面试,HR必发ssp offser哦

作者:Nayuta
链接:https://juejin.cn/post/6908574202253541389

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,558评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,002评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,024评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,144评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,255评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,295评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,068评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,478评论 1 305
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,789评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,965评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,649评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,267评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,982评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,800评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,847评论 2 351

推荐阅读更多精彩内容