哈罗大家好,这个是我们Flutter的原理篇第二篇内容,第一篇的内容大家感兴趣的话可以点击这个《Flutter原理篇:聊一聊future,await,事件队列,微任务》 连接去查看,我们今天来说一下《Flutter运行原理篇之Build构建的过程》
Build构建是Widget最开始执行的步骤过程,我们要弄懂原理就要从Widget一开始怎么运行去洞察他,首先我们知道Widget的更新运行要经过5个步骤:
void drawFrame() {
buildOwner!.buildScope(renderViewElement!); // 1.重新构建widget
super.drawFrame();
//下面几个是在super.drawFrame()执行的
pipelineOwner.flushLayout(); // 2.更新布局
pipelineOwner.flushCompositingBits(); //3.更新“层合成”信息
pipelineOwner.flushPaint(); // 4.重绘
if (sendFramesToEngine) {
renderView.compositeFrame(); // 5. 上屏,将绘制出的bit数据发送给GPU
}
}
所以我们的Build只是其中的第一个重要的步骤而已,好的让我们从头开始吧!
在介绍这个原理之前先给大家普及或者回忆下一些基本的内容,我怕有些初学的朋友连概率可能都不清楚一看源码有可能会迷糊,其中有些我也是吸收了网路上面好的文章再加上我自己总结的经验在里面了
首先介绍的就是三棵树的概念:这个概念想必是Flutter的初学者也会不陌生,上图:
这里需要注意:
- 三棵树中,经常我们听过这三棵树是一一对应的,其实这种说法只是宏观上的,微观上是不对的,确切的说应该是Widget 和 Element 是一一对应的,但并不和 RenderObject 一一对应
例如:Text的Widget 在他的build方法里面返回的确是一个叫做RichText的Widget,而RichText 又继承于 MultiChildRenderObjectWidget 在RichText里面才有createRenderObject方法去生产一个RenderObject
Text的Build方法片段如下:
@override
Widget build(BuildContext context) {
// 省略部分片段代码
Widget result = RichText(
textAlign: textAlign ?? defaultTextStyle.textAlign ?? TextAlign.start,
textDirection: textDirection, // RichText uses Directionality.of to obtain a default if this is null.
locale: locale, // RichText uses Localizations.localeOf to obtain a default if this is null
softWrap: softWrap ?? defaultTextStyle.softWrap,
overflow: overflow ?? defaultTextStyle.overflow,
textScaleFactor: textScaleFactor ?? MediaQuery.textScaleFactorOf(context),
maxLines: maxLines ?? defaultTextStyle.maxLines,
strutStyle: strutStyle,
textWidthBasis: textWidthBasis ?? defaultTextStyle.textWidthBasis,
textHeightBehavior: textHeightBehavior ?? defaultTextStyle.textHeightBehavior ?? DefaultTextHeightBehavior.of(context),
text: TextSpan(
style: effectiveTextStyle,
text: data,
children: textSpan != null ? [textSpan!] : null,
),
);
// 省略部分片段代码
}
class RichText extends MultiChildRenderObjectWidget {
// 省略部分片段代码
@override
RenderParagraph createRenderObject(BuildContext context) {
assert(textDirection != null || debugCheckHasDirectionality(context));
return RenderParagraph(text,
textAlign: textAlign,
textDirection: textDirection ?? Directionality.of(context),
softWrap: softWrap,
overflow: overflow,
textScaleFactor: textScaleFactor,
maxLines: maxLines,
strutStyle: strutStyle,
textWidthBasis: textWidthBasis,
textHeightBehavior: textHeightBehavior,
locale: locale ?? Localizations.maybeLocaleOf(context),
);
}
// 省略部分片段代码
}
这足以说明在微观上并不是一一对应的,我们再来简单介绍下这三棵树的对象有什么作用:
1. Widget就是一个描述文件,这些描述文件在我们进行状态改变时会不断的调用build方法;
2. Element是一个元素的起点,从他开始生成Widget,再从widget生成RenderObject,他包括了这两个对象属性,你也可以理解为他们互相都包容
3. RenderObject才是最终可以被layout可以被paint绘制的对象
只不过为了开发简单便捷,一般情况下我们只会操作Widget而且,把Element与RenderObject隔离开了,真的隔离开了吗?其实并没有,例如我们再widget的build方法里面的BuildContext对象其实就是他对应的Element对象,这个我们后面会说到
好了让我开始说到Flutter的Widget构建过程分两种调用时机:
BuildOwner.buildScope() 会有两种调用时机:
- 树构建(应用启动时):runApp() 方法调用的 scheduleAttachRootWidget() 方法,它会构建Widgets Tree,Element Tree与RenderObject Tree三棵树。
- 树更新(更新时):这里不会重新构建三棵树,而是只会更新dirty区域的Element,从而开始Build构建Widget
我们先来看看第一次构建Build是怎么运行的:
void runApp(Widget app) {
WidgetsFlutterBinding.ensureInitialized()
..scheduleAttachRootWidget(app)
..scheduleWarmUpFrame();
}
WidgetsBinding.attachRootWidget
void attachRootWidget(Widget rootWidget) {
_readyToProduceFrames = true;
_renderViewElement = RenderObjectToWidgetAdapter(
container: renderView,
debugShortDescription: '[root]',
child: rootWidget,
).attachToRenderTree(buildOwner, renderViewElement as RenderObjectToWidgetElement);
}
RenderObjectToWidgetAdapter继承自RenderObjectWidget,把他看成是一个普通的RenderObjectWidget即可,RenderObjectWidget也就是从RenderObject到Element树的桥梁,注意此时的renderViewElement 肯定是null,接着往下看
RenderObjectToWidgetAdapter.attachToRenderTree
RenderObjectToWidgetElement attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement element ]) {
if (element == null) {
owner.lockState(() {
element = createElement();
assert(element != null);
element.assignOwner(owner);
});
owner.buildScope(element, () {
element.mount(null, null);
});
// This is most likely the first time the framework is ready to produce
// a frame. Ensure that we are asked for one.
SchedulerBinding.instance.ensureVisualUpdate();
} else {
element._newWidget = this;
element.markNeedsBuild();
}
return element;
}
createElement创建的element是RenderObjectToWidgetElement,他是一个RootRenderObjectElement,也就是根element。
element.mount(null, null);会向下遍历并构建整个widget树,这里我们看到了熟悉的两个方法createElement与mount方法,只不过这里的createElement并不是创建我们自己写的Widget对应的Element,而是一个根Element,这个mount确是整个非根Element创建的开端,让我往下看:
RenderObjectToWidgetElement.mount
@override
void mount(Element parent, dynamic newSlot) {
assert(parent == null);
super.mount(parent, newSlot);
_rebuild();
}
RenderObjectToWidgetElement._rebuild
void _rebuild() {
try {
_child = updateChild(_child, widget.child, _rootChildSlot);
} catch (exception, stack) {
}
}
此时的widget就是RenderObjectToWidgetAdapter,它的widget.child就是runApp传递进去的widget,也就是运行的第一个应用Widget
Element.updateChild
Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
。。。
Element newChild;
if (child != null) {
//省略部分代码
} else {
newChild = inflateWidget(newWidget, newSlot);
}
return newChild;
}
由于是第一次,Element child是null,执行else里的逻辑,inflateWidget使用子widget来创建一个子Element,此时的newWidget是runApp传递进去的widget。
Element.inflateWidget
Element inflateWidget(Widget newWidget, dynamic newSlot) {
assert(newWidget != null);
final Key key = newWidget.key;
if (key is GlobalKey) {
final Element newChild = _retakeInactiveElement(key, newWidget);
if (newChild != null) {
assert(newChild._parent == null);
newChild._activateWithParent(this, newSlot);
final Element updatedChild = updateChild(newChild, newWidget, newSlot);
assert(newChild == updatedChild);
return updatedChild;
}
}
final Element newChild = newWidget.createElement();
newChild.mount(this, newSlot);
return newChild;
}
运行有两步:
- 如果是GlobalKey的话,会先从GlobalKey中获取引用的Element,如果有 有效的element的话就复用
- 如果不是GlobalKey 或 没有从GlobalKey中获取到element 的话,就用widget调用其createElement()来创建了一个element,接着就把新建的子element挂载到了element树上。
一般我们走的是第二步,下面一个是重点,这里调用了newWidget.createElement 去创建这个Widget对应的Element,也就是我们第一个运行的Widget对应的Element
接下来到element.mount,他主要有两个运行分支流程,
- 一个是ComponentElement的,
- 一个是RenderObjectElement的,
ComponentElement.mount
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
assert(_child == null);
assert(_active);
_firstBuild();
assert(_child != null);
}
super.mount会把parent记录在此element中。
void mount(Element? parent, Object? newSlot) {
assert(_lifecycleState == _ElementLifecycle.initial);
assert(widget != null);
assert(_parent == null);
assert(parent == null || parent._lifecycleState == _ElementLifecycle.active);
assert(slot == null);
_parent = parent;
_slot = newSlot;
_lifecycleState = _ElementLifecycle.active;
_depth = _parent != null ? _parent!.depth + 1 : 1;
if (parent != null) {
// Only assign ownership if the parent is non-null. If parent is null
// (the root node), the owner should have already been assigned.
// See RootRenderObjectElement.assignOwner().
_owner = parent.owner;
}
assert(owner != null);
final Key? key = widget.key;
if (key is GlobalKey) {
owner!._registerGlobalKey(key, this);
}
_updateInheritance();
}
firstBuild会调用到performRebuild方法:
void performRebuild() {
Widget built;
try {
built = build();
} catch (e, stack) {
} finally {
}
try {
_child = updateChild(_child, built, slot);
assert(_child != null);
} catch (e, stack) {
}
}
此方法会首先调用build方法,通过这一步来创建其子widget,也就是build方法里面返回的Widget,ComponentElement主要有两个子类stateful和stateless,会有不同的实现。
class StatelessElement extends ComponentElement {
/// Creates an element that uses the given widget as its configuration.
StatelessElement(StatelessWidget widget) : super(widget);
@override
StatelessWidget get widget => super.widget as StatelessWidget;
@override
Widget build() => widget.build(this);
@override
void update(StatelessWidget newWidget) {
super.update(newWidget);
assert(widget == newWidget);
_dirty = true;
rebuild();
}
}
/// An [Element] that uses a [StatefulWidget] as its configuration.
class StatefulElement extends ComponentElement {
/// Creates an element that uses the given widget as its configuration.
//省略部分代码
@override
Widget build() => state.build(this);
//省略部分代码
}
看到这里大家就应该很明白了,这个两个Element的Build方法其实就是调用了Widget的Build方法,也是我们最熟悉的build方法了他就是在这里被调用的
然后就又调用到了updateChild方法,这就回到了上边流程一直往下遍历创建widget树,不知道大家看明白了没有,其实就是首先从我们传入的第一个Widget开始创建他对应的Element,然后这个Element调用了build方法然后间接调用了这个Widget的build方法,这个方法里面是我们的实现,一般都会返回一个Widget(也就是他的子Widget),然后再调用这个updateChild方法再调用inflateWidget方法去创建这个子Widget对应的Element,然后再调用他的mount方法去调用子Widget的build方法去返回他的子子Widget,一直周而复始的这样循环一直到这个Element是RenderObjectElement
上面说的是ComponentElement的mount方法运行流程,还有一种是RenderObjectElement的mount方法,这种就是可渲染对象的mount方法与上面的流程会不一样
RenderObjectElement.mount
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
_renderObject = widget.createRenderObject(this);
attachRenderObject(newSlot);
_dirty = false;
}
此方法会创建一个RenderObject,之后就会把此RenderObject添加到RenderObject树上。
void attachRenderObject(dynamic newSlot) {
assert(_ancestorRenderObjectElement == null);
_slot = newSlot;
_ancestorRenderObjectElement = _findAncestorRenderObjectElement();
_ancestorRenderObjectElement?.insertRenderObjectChild(renderObject, newSlot);
final ParentDataElement parentDataElement = _findAncestorParentDataElement();
if (parentDataElement != null)
_updateParentData(parentDataElement.widget);
}
_findAncestorRenderObjectElement往上找离它最近的父RenderObjectElement,然后把当前的renderObject给插入到父RenderObjectElement中。
SingleChildRenderObjectElement.insertRenderObjectChild
@override
void insertRenderObjectChild(RenderObject child, Object? slot) {
final RenderObjectWithChildMixin renderObject = this.renderObject as RenderObjectWithChildMixin;
assert(slot == null);
assert(renderObject.debugValidateChild(child));
renderObject.child = child;
assert(renderObject == this.renderObject);
}
我们来看看一个可渲染对象对应的Element,SingleChildRenderObjectElement的insertRenderObjectChild具体实现是什么样的呢,很简单他这里把上面找到的父类的可渲染对象renderObject的child赋值了当前对象,这个无形中通过child字段就形成了一个renderObject的链条,这里是值得关注的
接着_findAncestorParentDataElement(),它的作用是找到离它最近的父ParentDataElement,并用ParentDataWidget上的数据更新此renderObject上的ParentData数据
好了,上面是我们一开始运行程序的时候的构建流程,看起来其实也没有那么复杂,接下来我们讲解一下如果遇到更新的时候Widget的构建流程是怎么样的
状态更新时,会把标记变为element dirty状态(这个下面我们会具体分析),由于一般状态更新是StatefulWidget才可以的,所以我们这里只分析StatefulWidget即可,大家应该记得StatefulWidget是通过调用State.setState来标识当前需要更新子widget的,如下:
void setState(VoidCallback fn) {
final dynamic result = fn() as dynamic;
_element.markNeedsBuild();
}
我们可以看到会先执行传递进来的函数类型参数(这个也就是我们在setState里面自定义更新的具体内容),然后调用markNeedsBuild来标记此element需要更新其子widget
void markNeedsBuild() {
if (!_active)
return;
if (dirty)
return;
_dirty = true;
owner.scheduleBuildFor(this);
}
此方法首先会判断是否已经调用过了,_dirty会在之后的rebuild后重新设置为true,
owner是BuildOwner,用来管理widget框架。此类用于追踪需要rebuilding的widget,并作为一个整体处理应用于widget树的其他任务,比如管理树的 inactive element列表,以及在调试时hot reload期间在必要时触发“reassemble”命令,一般BuildOwner是由WidgetsBinding持有,并且与 build/layout/paint管道的其余部分一起被操作系统驱动
BuildOwner.scheduleBuildFor
void scheduleBuildFor(Element element) {
if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
_scheduledFlushDirtyElements = true;
onBuildScheduled();
}
_dirtyElements.add(element);
element._inDirtyList = true;
}
首先调用onBuildScheduled()方法,此方法是一个回调,他是在WidgetsBinding中赋值的,然后把此element加入到了_dirtyElements列表中了。
WidgetsBinding.initInstances
void initInstances() {
super.initInstances();
_instance = this;
_buildOwner = BuildOwner();
buildOwner.onBuildScheduled = _handleBuildScheduled;
}
_handleBuildScheduled的具体实现如下:
void _handleBuildScheduled() {
ensureVisualUpdate();
}
void ensureVisualUpdate() {
switch (schedulerPhase) {
case SchedulerPhase.idle:
case SchedulerPhase.postFrameCallbacks:
scheduleFrame();
return;
case SchedulerPhase.transientCallbacks:
case SchedulerPhase.midFrameMicrotasks:
case SchedulerPhase.persistentCallbacks:
return;
}
}
void scheduleFrame() {
if (_hasScheduledFrame || !framesEnabled)
return;
ensureFrameCallbacksRegistered();
window.scheduleFrame();
_hasScheduledFrame = true;
}
先调用ensureFrameCallbacksRegistered(这个里面会注册下一帧到来的回调方法),最后会去调用底层window.scheduleFrame()来注册一个下一帧时回调,就类似于Android中的ViewRootImpl.scheduleTraversals(),下一帧来到后,会调用到的方法是在上边的ensureFrameCallbacksRegistered()中注册的回调,
SchedulerBinding.ensureFrameCallbacksRegistered
void ensureFrameCallbacksRegistered() {
window.onBeginFrame ??= _handleBeginFrame;
window.onDrawFrame ??= _handleDrawFrame;
}
onBeginFrame :主要是用来执行动画,onDrawFrame :这个主要处理上边case里面的persistentCallbacks
下面我们简单介绍下frame 处理流程以及对应的几个状态:
当有新的 frame 到来时,开始调用 SchedulerBinding.handleDrawFrame 来处理 frame,具体处理过程就是依次执行四个任务队列:transientCallbacks、midFrameMicrotasks、persistentCallbacks、postFrameCallbacks,当四个任务队列执行完毕后当前 frame 结束。
综上,Flutter 将整个生命周期分为五种状态,通过 SchedulerPhase 枚举类来表示它们:
enum SchedulerPhase {
/// 空闲状态,并没有 frame 在处理。这种状态代表页面未发生变化,并不需要重新渲染。
/// 如果页面发生变化,需要调用`scheduleFrame()`来请求 frame。
/// 注意,空闲状态只是指没有 frame 在处理,通常微任务、定时器回调或者用户事件回调都
/// 可能被执行,比如监听了tap事件,用户点击后我们 onTap 回调就是在idle阶段被执行的。
idle,
/// 执行”临时“回调任务,”临时“回调任务只能被执行一次,执行后会被移出”临时“任务队列。
/// 典型的代表就是动画回调会在该阶段执行。
transientCallbacks,
/// 在执行临时任务时可能会产生一些新的微任务,比如在执行第一个临时任务时创建了一个
/// Future,且这个 Future 在所有临时任务执行完毕前就已经 resolve 了,这中情况
/// Future 的回调将在[midFrameMicrotasks]阶段执行
midFrameMicrotasks,
/// 执行一些持久的任务(每一个frame都要执行的任务),比如渲染管线(构建、布局、绘制)
/// 就是在该任务队列中执行的.
persistentCallbacks,
/// 在当前 frame 在结束之前将会执行 postFrameCallbacks,通常进行一些清理工作和
/// 请求新的 frame。
postFrameCallbacks,
}
好了我们继续讲解上面提到的回调handleDrawFrame
void handleDrawFrame() {
try {
// PERSISTENT FRAME CALLBACKS
_schedulerPhase = SchedulerPhase.persistentCallbacks;
for (final FrameCallback callback in _persistentCallbacks)
_invokeFrameCallback(callback, _currentFrameTimeStamp!);
// POST-FRAME CALLBACKS
_schedulerPhase = SchedulerPhase.postFrameCallbacks;
final List localPostFrameCallbacks =
List.from(_postFrameCallbacks);
_postFrameCallbacks.clear();
for (final FrameCallback callback in localPostFrameCallbacks)
_invokeFrameCallback(callback, _currentFrameTimeStamp!);
} finally {
}
}
看下persistentCallbacks列表在哪添加的callback,最终找到是在RendererBinding.initInstances中添加的callback
void initInstances() {
super.initInstances();
_instance = this;
_pipelineOwner = PipelineOwner(
onNeedVisualUpdate: ensureVisualUpdate,
onSemanticsOwnerCreated: _handleSemanticsOwnerCreated,
onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,
);
initRenderView();
addPersistentFrameCallback(_handlePersistentFrameCallback);
initMouseTracker();
}
RendererBinding._handlePersistentFrameCallback
void _handlePersistentFrameCallback(Duration timeStamp) {
drawFrame();
_scheduleMouseTrackerUpdate();
}
WidgetsFlutterBinding中的WidgetsBinding继承了RendererBinding重载了drawFrame方法,把build流程加入进来了。
WidgetsBinding.drawFrame
@override
void drawFrame() {
try {
if (renderViewElement != null)
buildOwner.buildScope(renderViewElement); //这里就是我们提到的刷新以后的build构建
super.drawFrame();
buildOwner.finalizeTree();
} finally {
}
}
由于继承关系的顺序问题WidgetsBinding里面的super.drawFrame();又会调用到RendererBinding的drawFrame方法
class WidgetsFlutterBinding extends BindingBase with GestureBinding, SchedulerBinding, ServicesBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {
static WidgetsBinding ensureInitialized() {
if (WidgetsBinding.instance == null)
WidgetsFlutterBinding();
return WidgetsBinding.instance!;
}
}
我们再贴一下RendererBinding.drawFrame方法,加上父类的drawFrame方法是不是就得出了本文一开始我们提到的整个渲染的几个步骤呢
@protected
void drawFrame() {
assert(renderView != null);
pipelineOwner.flushLayout();
pipelineOwner.flushCompositingBits();
pipelineOwner.flushPaint();
if (sendFramesToEngine) {
renderView.compositeFrame(); // this sends the bits to the GPU
pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
_firstFrameSent = true;
}
}
好了,本文还是重点关注build构建阶段的内容,
void buildScope(Element context, [ VoidCallback callback ]) {
if (callback == null && _dirtyElements.isEmpty)
return;
try {
_scheduledFlushDirtyElements = true;
if (callback != null) {
assert(_debugStateLocked);
Element debugPreviousBuildTarget;
_dirtyElementsNeedsResorting = false;
try {
// 可以添加一个回调在build之前执行。
callback();
} finally {
}
}
_dirtyElements.sort(Element._sort);
_dirtyElementsNeedsResorting = false;
int dirtyCount = _dirtyElements.length;
int index = 0;
while (index < dirtyCount) {
try {
_dirtyElements[index].rebuild();
} catch (e, stack) {
}
index += 1;
if (dirtyCount < _dirtyElements.length || _dirtyElementsNeedsResorting) {
_dirtyElements.sort(Element._sort);
_dirtyElementsNeedsResorting = false;
dirtyCount = _dirtyElements.length;
while (index > 0 && _dirtyElements[index - 1].dirty) {
index -= 1;
}
}
}
} finally {
for (final Element element in _dirtyElements) {
element._inDirtyList = false;
}
_dirtyElements.clear();
_scheduledFlushDirtyElements = false;
_dirtyElementsNeedsResorting = null;
}
}
可以看到会遍历dirtyElements列表中的element.rebuild(),而element.rebuild()最终会调用到performRebuild()。
Element.rebuild
@pragma('vm:prefer-inline')
void rebuild() {
assert(_lifecycleState != _ElementLifecycle.initial);
if (_lifecycleState != _ElementLifecycle.active || !_dirty)
return;
assert(() {
debugOnRebuildDirtyWidget?.call(this, _debugBuiltOnce);
if (debugPrintRebuildDirtyWidgets) {
if (!_debugBuiltOnce) {
debugPrint('Building $this');
_debugBuiltOnce = true;
} else {
debugPrint('Rebuilding $this');
}
}
return true;
}());
assert(_lifecycleState == _ElementLifecycle.active);
assert(owner!._debugStateLocked);
Element? debugPreviousBuildTarget;
assert(() {
debugPreviousBuildTarget = owner!._debugCurrentBuildTarget;
owner!._debugCurrentBuildTarget = this;
return true;
}());
performRebuild();
assert(() {
assert(owner!._debugCurrentBuildTarget == this);
owner!._debugCurrentBuildTarget = debugPreviousBuildTarget;
return true;
}());
assert(!_dirty);
}
Element.performRebuild方法会根据不同类型的子类element去重建子widget或重建子element,分为以下两种:
ComponentElement.performRebuild
void performRebuild() {
Widget built;
try {
built = build();
} catch (e, stack) {
} finally {
// We delay marking the element as clean until after calling build() so
// that attempts to markNeedsBuild() during build() will be ignored.
_dirty = false;
}
try {
_child = updateChild(_child, built, slot);
} catch (e, stack) {
}
}
会先调用build方法创建一个子widget(这个就是build方法也就是我们自己在stateful控件里面实现的方法),然后调用Element.updateChild来更新
RenderObjectElement.performRebuild
void performRebuild() {
widget.updateRenderObject(this, renderObject);
_dirty = false;
}
因为RenderObjectElement是可渲染对象RenderObjec的对应的Element,其不具备包裹性(这里说的包裹性指的是他没有包裹widget)所以他没有对应的build方法可以调用,只是更新widget的配置,这个更新我们晚点再说,我们先看看Element.updateChild里面的内容
Element.updateChild
Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
if (newWidget == null) {
if (child != null)
deactivateChild(child);
return null;
}
Element newChild;
if (child != null) {
bool hasSameSuperclass = true;
assert(() {
final int oldElementClass = Element._debugConcreteSubtype(child);
final int newWidgetClass = Widget._debugConcreteSubtype(newWidget);
hasSameSuperclass = oldElementClass == newWidgetClass;
return true;
}());
if (hasSameSuperclass && child.widget == newWidget) {
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
newChild = child;
} else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
child.update(newWidget);
newChild = child;
} else {
deactivateChild(child);
newChild = inflateWidget(newWidget, newSlot);
}
} else {
newChild = inflateWidget(newWidget, newSlot);
}
return newChild;
}
由于不是第一次调用,所以Element child不是null,进入if逻辑,会进行三个判断:
- 如果子widget不变,子element和子widget匹配(就是是否都是stateless,或都是stateful),那么更新slot
- 如果子element和子widget匹配,但子widget发生了变化,就调用子element.update(newWidget)来更新widget配置,这种是我们最常见的情况
- 最后一个判断是子element和子widget不匹配,那么就把老的child element加入到一个_inactiveElements列表中(变成未激活状态),然后进行重建element
element.update方法会把newWidget记录下来,以便于下次更新作为更新判断使用,并且这里也是Element里面用了新build的newWidget替换掉了老的widget,这里是需要注意的
@mustCallSuper
void update(covariant Widget newWidget) {
_widget = newWidget;
}
我们再看看Element另一个子类StatelessElement的update方法
StatelessElement.update
@override
void update(StatelessWidget newWidget) {
super.update(newWidget);
assert(widget == newWidget);
_dirty = true;
rebuild();
}
最后又调用rebuild,上面已经提到了rebuild中会调用performRebuild()去重建其子widget,类似一个递归的流程
再来看看Element另一个子类StatefulElement的update方法,也就是我们的例子会调用到这的方法
StatefulElement.update
@override
void update(StatefulWidget newWidget) {
super.update(newWidget);
assert(widget == newWidget);
final StatefulWidget oldWidget = _state._widget;
// Notice that we mark ourselves as dirty before calling didUpdateWidget to
// let authors call setState from within didUpdateWidget without triggering
// asserts.
_dirty = true;
_state._widget = widget as StatefulWidget;
try {
final dynamic debugCheckForReturnedFuture = _state.didUpdateWidget(oldWidget) as dynamic;
} finally {
}
rebuild();
}
回调state.didUpdateWidget(这里就是我们在自定义Widget写的生命周期回调函数就是在这里触发的),最后又调用rebuild,rebuild中会调用performRebuild()去重建其子widget,类似一个递归的流程
最后来看看可渲染对象对应的Element-RenderObjectElement的update方法是怎么样的:
RenderObjectElement.update
@override
void update(covariant RenderObjectWidget newWidget) {
super.update(newWidget);
assert(widget == newWidget);
widget.updateRenderObject(this, renderObject);
_dirty = false;
}
上面我们说过了RenderObjectElement是可渲染对象RenderObjec的对应的Element,其不具备包裹性(这里说的包裹性指的是他没有包裹widget)所以他没有再构建子Widget的build方法的行为,update方法里面只是更新widget的配置,我们下面会提到这个更新的步骤
好了写到这里我们已经很清楚了build构建步骤触发的时间与流程,另外我们也额外的了解到了一些生命周期函数的触发时机在哪里,顺便在给大家一个小的彩蛋:我们的Element链条是在哪里形成的呢,大家可以观察下Element.mount函数,里面通过_parent字段标记了一个Element的父亲,再通过updateChild方法里面(代码就不贴了往上看)的child字段来确定他的子类,这样的话一个Element的链条就形成了(这里有些博客忽略的细节,希望能给到你帮助)
Element.mount
void mount(Element? parent, Object? newSlot) {
assert(_lifecycleState == _ElementLifecycle.initial);
assert(widget != null);
assert(_parent == null);
assert(parent == null || parent._lifecycleState == _ElementLifecycle.active);
assert(slot == null);
_parent = parent; //重点看这里
_slot = newSlot;
_lifecycleState = _ElementLifecycle.active;
_depth = _parent != null ? _parent!.depth + 1 : 1;
if (parent != null) {
// Only assign ownership if the parent is non-null. If parent is null
// (the root node), the owner should have already been assigned.
// See RootRenderObjectElement.assignOwner().
_owner = parent.owner;
}
assert(owner != null);
final Key? key = widget.key;
if (key is GlobalKey) {
owner!._registerGlobalKey(key, this);
}
_updateInheritance();
}
PS:我们的RenderObject的链条上面也已经说过了哦,忘记的可以回看一下
最后我们再来提一下另一个菜单也就是我们上面留下的一个问题RenderObjectElement.performRebuild里面的updateRenderObject方法与上面提到的RenderObjectElement.update里面的updateRenderObject方法好像还没有结论
void performRebuild() {
widget.updateRenderObject(this, renderObject);
_dirty = false;
}
其实这里会触发我们整个渲染流程的第二步,也就是layout的步骤就是在这里触发的,我们下一篇原理篇会重点讲解下这个步骤,小伙伴们期待一下吧,好了本文就写到这里了如果有喜欢的小伙伴欢迎点赞,有疑问的可以给我留言,我都会回复大家的,我们下期再见···