学习最忌盲目,无计划,零碎的知识点没法串成系统。学到哪,忘到哪,面试想不起来。这里我整理了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哦