绘制相关FlutterDart

原文传送门:

调用图

我们用下面的例子来分析Flutter启动所做的事情

void main() {
  runApp(MyApp());
}

<!-- more -->
class MyApp extends StatelessWidget {
  const MyApp({Key key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return Container();
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

初始化Binding

runApp

void runApp(Widget app) {
  WidgetsFlutterBinding.ensureInitialized()
    ..scheduleAttachRootWidget(app)
    ..scheduleWarmUpFrame();
}
1
2
3
4
5

runApp中,我们会调用WidgetsFlutterBinding.ensureInitialized()来初始化Binding

Binding

Binding是一系列单例,在启动的时候初始化,其中包括了WidgetsFlutterBindingBindingBaseGestureBindingSchedulerBindingServicesBinding等,下面来分析它们的作用

  • BindingBase: 是所有类的基类,负责初始化其它类、初始化一些Native相关的信息(如平台是Android还是iOS)、注册基本的Native事件(如热更新、退出)
  • GestureBinding: 提供window.onPointerDataPacket回调,接受Native事件,负责事件转换及分发
  • SchedulerBinding: 使用了window.scheduleFrame来通知Native及使用window.onBeginFramewindow.onDrawFrame回调来接收消息,主要是负责通知Native在下一侦的事件下发与事件注册,当我们调用setState后,就会触发此类的方法,等待事件下发后进行渲染
  • ServicesBinding: 使用window.onPlatformMessage回调,负责通道相关的初始化及通信相关的处理
  • PaintingBinding: 与绘制相关的函数绑定,还处理一些图片渲染相关的缓存
  • SemanticsBinding: 注册平台相关的辅助函数
  • RendererBinding: 初始化PipelineOwnerrenderViewonMetricsChangedonTextScaleFactorChangedonPlatformBrightnessChangedonSemanticsEnabledChanged onSemanticsAction等,用于监听并处理平台渲染相关如字体、状态栏改变时的事件,是渲染树与Flutter engine沟通的桥梁
  • WidgetsBinding: 初始化BuildOwner,注册window.onLocaleChangedonBuildScheduled等回调。它是Flutter widget层与engine的桥梁。

渲染树的构建

attachRootWidget

[-> packages/flutter/lib/src/widgets/binding.dart]

void attachRootWidget(Widget rootWidget) {
  _readyToProduceFrames = true;
  _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
    container: renderView,
    debugShortDescription: '[root]',
    child: rootWidget,
  ).attachToRenderTree(buildOwner!, renderViewElement as RenderObjectToWidgetElement<RenderBox>?);
}
1
2
3
4
5
6
7
8

上面会先初始化一个RenderObjectToWidgetAdapter,它是一个特殊的Widget,主要用Flutter树根节点的初始化,其中传了一个renderView,上面提到了是在RendererBinding初始化时就已经初始化了,它是根RenderObject节点,rootWidget就是我们runApp时传入的Widget

后面调用了attachToRenderTree会开始一系列的树构建

attachToRenderTree

[-> packages/flutter/lib/src/widgets/binding.dart]

RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T>? element ]) {
  // 如果element==null执行,第一次渲染element一定为null
  if (element == null) {
    owner.lockState(() {
      // 调用createElement创建Element
      element = createElement();
      assert(element != null);
      // 将owner传给element,让element持有owner
      element!.assignOwner(owner);
    });
    owner.buildScope(element!, () {
      element!.mount(null, null);
    });
    SchedulerBinding.instance!.ensureVisualUpdate();
  } else {
    element._newWidget = this;
    element.markNeedsBuild();
  }
  return element!;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

上面会先根据createElement创建一个Element对象,然后传入ownerBuildOwner是Flutter处理更新的类,主要是处理脏节点收集与重绘,需要注意的是,根据Flutter的设计,全局只有一个BuildOwner对象,在此处传入后,它会在子节点传递。

然后会调用owner.buildScapebuildScape主要提供两个作用,一是为Widget Tree更新建立一个作用域并执行回调(像上面这种情况,不过回调也可以不传),二是当作用域中存在脏节点,它会调用Element.rebuild方法进行重新构建(就在此时调用的Widget的build重新渲染Element)。

接着会调用element.mount,这是一个Element非常重要的方法,这里就开始构建三棵树了.mount最简单的作用就是为当前对象绑定父对象及为当前绑定_inheritedWidgets(你认为的没错,就是Flutter中用于数据共享的InheritedWidgetElement)。一般我们的子Element会重写mount方法,用于孩子节点的初始化。

mount

[-> packages/flutter/lib/src/widgets/framework.dart]


void mount(Element? parent, dynamic newSlot) {
  assert(parent == null);
  super.mount(parent, newSlot);
  _rebuild();
}
void _rebuild() {
  //...
    _child = updateChild(_child, widget.child, _rootChildSlot);
  //...
}

1
2
3
4
5
6
7
8
9
10
11
12

所以在RenderObjectToWidgetElement中会调用_rebuild然后调用updateChild来初始化孩子节点。

updateChild

[-> packages/flutter/lib/src/widgets/framework.dart]


Element? updateChild(Element? child, Widget? newWidget, dynamic newSlot) {
  // ...
  final Element newChild;
  if (child != null) {
    bool hasSameSuperclass = true;
    if (hasSameSuperclass && child.widget == newWidget) {
      if (child.slot != newSlot)
        updateSlotForChild(child, newSlot);
      newChild = child;
    } else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
      //...
      child.update(newWidget);
      //...
      newChild = child;
    } else {
      deactivateChild(child);
      newChild = inflateWidget(newWidget, newSlot);
    }
  } else {
    newChild = inflateWidget(newWidget, newSlot);
  }
  return newChild;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

updateChild方法用于初始化或者更新孩子节点,当child != null时,主要是调用child.update来更新child,否则调用inflateWidget,这里第一次渲染当然是会调用inflateWidget

inflateWidget

[-> packages/flutter/lib/src/widgets/framework.dart]


Element inflateWidget(Widget newWidget, dynamic newSlot) {
  // ...
  final Element newChild = newWidget.createElement();
  // ...
  newChild.mount(this, newSlot);
  return newChild;
}
1
2
3
4
5
6
7
8

inflateWidget方法的作用就是根据Widget生成Element同时调用child.mount。这样,一次树的循环调用就接上了。

Widget树构建

有人问StatelessWidgetStatefulWidget的build方法怎么都没调用。StatelessWidgetStatefulWidget对应的ElementStatelessElementStatefulElement,它们都是继承至ComponentElement。根据上面的分析,mount方法是构建起点,我们可以看下ComponentElement中的mount方法。

_firstBuild

[-> packages/flutter/lib/src/widgets/framework.dart]


void mount(Element? parent, dynamic newSlot) {
  super.mount(parent, newSlot);
  assert(_child == null);
  assert(_lifecycleState == _ElementLifecycle.active);
  _firstBuild();
  assert(_child != null);
}
1
2
3
4
5
6
7
8

调用了_firstBuild方法,然后再调用了Elementrebuild方法,接着调用到ComponentElementperformRebuild方法

performRebuild

[-> packages/flutter/lib/src/widgets/framework.dart]

void rebuild() {
  performRebuild();
}
//...

void performRebuild() {
  Widget? built;
  try {
    // ...
    built = build();
  } catch (e, stack) {
    // ...
  } finally {
    _dirty = false;
  }
  try {
    _child = updateChild(_child, built, slot);
  } catch (e, stack) {
    // ...
    _child = updateChild(null, built, slot);
  }

  if (!kReleaseMode && debugProfileBuildsEnabled)
    Timeline.finishSync();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

performRebuildComponentElement实现的父类,其中调用了build方法返回一个Widget,然后就看到了熟悉的updateChild方法,这就接上了上面的分析。

RenderObject树的构建

RenderObjectWidget

RenderObject是Flutter中用于实际绘制的类,对于StatefulWidgetStatelessWidget实际上都是没有对应的RenderObject节点的,实际上它们只是提供一个容器,用来组合各种绘制。

只有继承了RenderObjectWidgetWidget才有对应的RenderObject节点。Flutter中为了方便,又提供了三种继承至RenderObjectWidget的类,每个类都有不同作用

  • LeafRenderObjectWidget 用于只有渲染功能,无子节点 (如Switch、Radio等)
  • SingleChildRenderObjectWidget 只含有一个子节点 (如SizedBox、Padding等)
  • MultiChildRenderObjectWidget 含有多个子节点(如Column、Stack等)

上面这些对象只是提供了一个容器给开发者使用,不过不是我们这次分析重点,先看看RenderObjectWidget

[-> packages/flutter/lib/src/widgets/framework.dart]

abstract class RenderObjectWidget extends Widget {
  const RenderObjectWidget({ Key? key }) : super(key: key);
  
  @factory
  RenderObjectElement createElement();
  
  @factory
  RenderObject createRenderObject(BuildContext context);
  
  void updateRenderObject(BuildContext context, covariant RenderObject renderObject) { }
  
  void didUnmountRenderObject(covariant RenderObject renderObject) { }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

它提供了createRenderObject方法来构建RenderObject,看下其对应的Element->RenderObjectElementmount方法

createRenderObject

[-> packages/flutter/lib/src/widgets/framework.dart]


void mount(Element? parent, dynamic newSlot) {
  super.mount(parent, newSlot);
  _renderObject = widget.createRenderObject(this);
  attachRenderObject(newSlot);
  _dirty = false;
}

void attachRenderObject(dynamic newSlot) {
  _slot = newSlot;
  _ancestorRenderObjectElement = _findAncestorRenderObjectElement();
  // 将上一步生成的renderObject插入到父renderObject中
  _ancestorRenderObjectElement?.insertRenderObjectChild(renderObject, newSlot);
  final ParentDataElement<ParentData>? parentDataElement = _findAncestorParentDataElement();
  if (parentDataElement != null)
    _updateParentData(parentDataElement.widget);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

其会先调用createRenderObject来构建RenderObject,然后调用attachRenderObject将上一步生成的renderObject插入到父renderObject中。这里的通过_findAncestorParentDataElement来查找的父类,因为上面说过,不是每个Element都有对应的RenderObject节点。

_findAncestorRenderObjectElement

[-> packages/flutter/lib/src/widgets/framework.dart]

RenderObjectElement? _findAncestorRenderObjectElement() {
  Element? ancestor = _parent;
  while (ancestor != null && ancestor is! RenderObjectElement)
    ancestor = ancestor._parent;
  return ancestor as RenderObjectElement?;
}
1
2
3
4
5
6

所以这里通过父ElementElement树中找上一个为RenderObjectElement的类,而insertRenderObjectChild是由上面说的SingleChildRenderObjectWidgetMultiChildRenderObjectWidget来具体实现的,主要是将生成的RenderObject插入到刚刚查找的父RenderObject中。因为根Element节点RenderObjectToWidgetElement是继承至RenderObjectElement,所以,只要通过RenderObjectToWidgetElement.renderObject即可拿到整棵树RenderObject树。

总结

上面我们分析了Flutter第一次启动时的大致过程,WidgetsFlutterBinding是总工头,几乎App所有的事情都是通过它来管理,BuildOwner是构建树的负责人,它负责树的构建和更新,RenderObjectToWidgetAdapter是一个特殊的Widget,它是渲染树的根节点,也是构建树的起点,Widget只是负责与开发人员对接,它就是一个工具人,开发人员把数据放到这里,后面都是通过Element进行处理并生成RenderObject(有些Widget不一定会生成RenderObject)。

现在我们只是生成了一个渲染树,可是界面到底是如何渲染的呢,回到上面的attachToRenderTree方法,其中构件树生成后会调用SchedulerBinding.instance!.ensureVisualUpdate方法,这个方法最后会调用window.scheduleFrame,它会发送通知给Native:我这边树都生成好了,你在下一侦给我发个通知,我要开始渲染页面啦。

想看渲染过程,请听下回分解。

Last Updated: 8/15/2021, 7:00:32 PM