入口

我们从一切的起点main.dart说起,这里我们一定会调用runApp方法,这个方法可以说是Flutter程序的入口:

void runApp(Widget app) {
WidgetsFlutterBinding.ensureInitialized()
..attachRootWidget(app)
..scheduleWarmUpFrame();
}

传入的Widget即是我们需要显示的界面Widget。

继续分析源码,其中WidgetsFlutterBinding是一个单例类,WidgetsFlutterBinding继承了BindingBase并且with了大量的mixin,可以说这个类就是将Widget架构和Flutter底层Engine连接的桥梁。

ensureInitialized()负责初始化以及返回实例,该方法会进行大量初始化操作。

Widget到Element到RenderObject的流程

attachRootWidget

初始化完成拿到实例后接下来会调用attachRootWidget方法,该方法完成了Widget到Element到RenderObject的整个关联过程,代码如下:

void attachRootWidget(Widget rootWidget) {
_renderViewElement = new RenderObjectToWidgetAdapter(
container: renderView,
debugShortDescription: '[root]',
child: rootWidget
).attachToRenderTree(buildOwner, renderViewElement);
}

实际上就是将传入的Widget包装到RenderObjectToWidgetAdapter,它继承自RenderObjectWidget,负责将Widget、Element、RenderObject三者关联起来,其中的RenderObject对应前面初始化操作中创建的renderView。

其中renderView和_renderViewElement为WidgetsFlutterBinding的成员,可以看出每个app只存在一个renderViewElement和renderView,并且一一对应。

attachToRenderTree

继续看attachToRenderTree方法的实现:

RenderObjectToWidgetElement attachToRenderTree(BuildOwner owner, [RenderObjectToWidgetElement element]) {
if (element == null) {
owner.lockState(() {
// 创建根Element,RenderObjectToWidgetElement
element = createElement();
assert(element != null);
element.assignOwner(owner);
});
owner.buildScope(element, () {
// 这里会根据WidgetTree构建ElementTree
element.mount(null, null);
});
} else {
element._newWidget = this;
element.markNeedsBuild();
}
return element;
}

该方法负责创建根Element,即RenderObjectToWidgetElement,并且将Element与Widget进行关联,即创建出WidgetTree对应的ElementTree。如果Element已经创建过了,则将根Element中关联的Widget设为新的,由此可以看出Element只会创建一次,后面会进行复用。

mount

如果Element是首次创建,会调用mount,该方法由父类到子类会做下面几件事:

Element

将该Element标记为active的,设置parent为null,slot为null,depth为1,如果对应的widget的key为GlobalKey,在这里进行注册,即将Key与Element进行关联,设置inheritedWidgets,用于由上至下传递数据。

RenderObjectElement

创建对应的RenderObject,并attach到对应的slot位置。

RootRenderObjectElement

没做什么事,只是assert一下parent和slot为null。

RenderObjectToWidgetAdapter

调用_rebuild()方法创建ElementTree。

如果不是首次创建,这种情况一般是多次调用了runApp方法,则更新对应的跟Widget,并调用markNeedsBuild()方法准备重建ElementTree。

rebuild

下面先看下_rebuild()的代码:

void _rebuild() {
try {
// 实际上是调用updateChild更新ElementTree
_child = updateChild(_child, widget.child, _rootChildSlot);
assert(_child != null);
} catch (exception, stack) {
// 这里就是红屏产生的地方
final FlutterErrorDetails details = new FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'widgets library',
context: 'attaching to the render tree'
);
// 这里打印了错误栈
FlutterError.reportError(details);
// 这里就是创建了红屏的Widget,显示在屏幕上
final Widget error = ErrorWidget.builder(details);
_child = updateChild(null, error, _rootChildSlot);
}
}

updateChild

实际上该方法只执行了updateChild(),该方法至关重要,ElementTree的生成主要就在方法中实现,我们来细看一下代码,注意代码中添加的注释:

// child表示要更新的Element,newWidget表示对应Element的Widget,newSlot用来标识Element的所在位置,返回该位置对应的新Element

@protected
Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
assert(() {
// Debug下保证一个GlobalKey只对应一个Widget
if (newWidget != null && newWidget.key is GlobalKey) {
final GlobalKey key = newWidget.key;
key._debugReserveFor(this);
}
return true;
}());
if (newWidget == null) {
// 如果newWidget为空,child非空表示需要移除旧Element
if (child != null)
deactivateChild(child);
// 将此Element的位置设为null
return null;
}
if (child != null) {
// 都非空且是相同Widget,更新位置标识即可
if (child.widget == newWidget) {
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
// 更新后返回原Element
return child;
}
// 若不是相同Widget则判断是否有相同的类型和相同的Key,是的话则更新Widget信息到Element
if (Widget.canUpdate(child.widget, newWidget)) {
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
child.update(newWidget);
assert(child.widget == newWidget);
assert(() {
child.owner._debugElementWasRebuilt(child);
return true;
}());
// 更新后返回原Element
return child;
}
// 若不符合更新的要求,则抛弃掉原Element,抛弃掉的Element会被回收到`_inactiveElements`列表中,不会立即被销毁
deactivateChild(child);
assert(child._parent == null);
}
// 其他情况下需要创建新的Element
return inflateWidget(newWidget, newSlot);
}
inflateWidget
继续看inflateWidget方法:
@protected
Element inflateWidget(Widget newWidget, dynamic newSlot) {
assert(newWidget != null);
final Key key = newWidget.key;
if (key is GlobalKey) {
// 先使用key去被回收的列表中看看是否有可以复用的Element
final Element newChild = _retakeInactiveElement(key, newWidget);
if (newChild != null) {
assert(newChild._parent == null);
assert(() { _debugCheckForCycles(newChild); return true; }());
newChild._activateWithParent(this, newSlot);
// 找到后就复用被回收的Element,并且更新它的Child
final Element updatedChild = updateChild(newChild, newWidget, newSlot);
assert(newChild == updatedChild);
return updatedChild;
}
}
// 没有可以复用的Element了,只能创建新的
final Element newChild = newWidget.createElement();
assert(() { _debugCheckForCycles(newChild); return true; }());
// mount新的Element
newChild.mount(this, newSlot);
assert(newChild._debugLifecycleState == _ElementLifecycle.active);
return newChild;
}

这里新创建的Element继续调用mount,于是又会触发新一轮的updateChild,最终对应WidgetTree的整个ElementTree就构建完成了。

开始渲染

回到最初的入口代码调用,最后一行会调用WidgetsFlutterBinding实例的scheduleWarmUpFrame进行第一次绘制,该方法的实现在SchedulerBinding类中。这次draw完成之前都不会接收各种event(触摸事件等等),其他scheduledFrame会被延迟到该次draw完成之后。

该方法主要调用了handleBeginFrame()和handleDrawFrame()两个方法,在看这两个方法之前首先了解一下Frame和FrameCallbacks的概念:

Frame

Frame即每一帧的绘制过程,engine通过VSync信号不断地触发Frame的绘制,实际上就是调用SchedulerBinding类中的_handleBeginFrame()和_handleDrawFrame()这两个方法,这个过程中会完成动画、布局、绘制等工作。

FrameCallbacks

Frame绘制期间,有三个callbacks列表会被调用,这三个列表是SchedulerBinding类中的成员,它们的调用顺序如下:

transientCallbacks,由Ticker触发和停止,一般用于动画的回调。

persistentCallbacks,永久callback,一经添加无法移除,由WidgetsBinding.instance.addPersitentFrameCallback()注册,这个回调处理了布局与绘制工作。

postFrameCallbacks,只会调用一次,调用后会被系统移除,可由WidgetsBinding.instance.addPostFrameCallback()注册,该回调一般用于State的更新。

handleBeginFrame

接下来看下handleBeginFrame方法的代码,这里去掉了assert以及profile相关的代码:

try {
// TRANSIENT FRAME CALLBACKS
Timeline.startSync('Animate', arguments: timelineWhitelistArguments);
_schedulerPhase = SchedulerPhase.transientCallbacks;
final Map callbacks = _transientCallbacks;
_transientCallbacks = {};
callbacks.forEach((int id, _FrameCallbackEntry callbackEntry) {
if (!_removedIds.contains(id))
_invokeFrameCallback(callbackEntry.callback, _currentFrameTimeStamp, callbackEntry.debugStack);
});
_removedIds.clear();
} finally {
_schedulerPhase = SchedulerPhase.midFrameMicrotasks;
}

实际上这里执行的操作不多,就是执行了transientCallbacks。

handleDrawFrame

这里进行persistentCallbacks和postFrameCallbacks的回调,主要的操作都发生在这里,详情见下一节。

真正的渲染操作

系统只在persistentCallbacks注册了一个回调,实际上为RenderBinding类中的drawFrame()方法以及其子类WidgetsBinding类中的drawFrame()方法:

RendererBinding
@protected
void drawFrame() {
assert(renderView != null);
pipelineOwner.flushLayout();
pipelineOwner.flushCompositingBits();
pipelineOwner.flushPaint();
renderView.compositeFrame(); // this sends the bits to the GPU
pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
}
** WidgetsBinding **
@override
void drawFrame() {
try {
if (renderViewElement != null)
buildOwner.buildScope(renderViewElement);
super.drawFrame();
buildOwner.finalizeTree();
} finally {
...
}
}

先看子类的实现,首先调用的是buildOwner.buildScope(),该方法会将被标记为dirty的Element进行rebuild()(调用过setState()或正在进行动画的Widget此时会是dirty的)。然后调用父类的super.drawFrame(),最后调用buildOwner.finalizeTree()还记得之前回收被抛弃的Element的列表_inactiveElements吗?列表中的Element们在这里会被彻底清除掉,接下来进入父类的drawFrame()方法继续分析。

pipelineOwner.flushLayout()

这个方法看名字就能猜到,这就是进行布局的地方了:

void flushLayout() {
...
while (_nodesNeedingLayout.isNotEmpty) {
final List dirtyNodes = _nodesNeedingLayout;
_nodesNeedingLayout = [];
for (RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => a.depth - b.depth)) {
if (node._needsLayout && node.owner == this)
node._layoutWithoutResize();
}
}
...
}

代码删去了assert和profile相关的内容,当RenderObject的宽高等布局相关的属性被set时(通过更改Widget的属性),它会被添加到_nodesNeedingLayout列表中,以标记为需要重新进行layout。这里遍历了该列表,并调用_layoutWithoutResize()进行布局。

flushCompositingBits()
void flushCompositingBits() {
...
_nodesNeedingCompositingBitsUpdate.sort((RenderObject a, RenderObject b) => a.depth - b.depth);
for (RenderObject node in _nodesNeedingCompositingBitsUpdate) {
if (node._needsCompositingBitsUpdate && node.owner == this)
node._updateCompositingBits();
}
_nodesNeedingCompositingBitsUpdate.clear();
...
}

该方法用于判断RenderObject是否拥有自己的layer,如果该状态变化了,就会将该RenderObject标记为需要进行重绘的,然后在下面flushPaint()方法中进行重绘。

flushPaint()
void flushPaint() {
...
final List dirtyNodes = _nodesNeedingPaint;
_nodesNeedingPaint = [];
// Sort the dirty nodes in reverse order (deepest first).
for (RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => b.depth - a.depth)) {
if (node._needsPaint && node.owner == this) {
if (node._layer.attached) {
PaintingContext.repaintCompositedChild(node);
} else {
node._skippedPaintingOnLayer();
}
}
}
...
}

该方法就是进行绘制的地方,可以看出它不是重绘了所有RenderObject,而是只重绘了被标记为dirty的RenderObject,这些RenderObject会调用engine下的skia库进行绘制。

compositeFrame()
void compositeFrame() {
...
final ui.SceneBuilder builder = new ui.SceneBuilder();
layer.addToScene(builder, Offset.zero);
final ui.Scene scene = builder.build();
if (automaticSystemUiAdjustment)
_updateSystemChrome();
ui.window.render(scene);
scene.dispose();
...
}

这个方法将画好的layer传给engine,该方法调用结束之后,手机屏幕就会显示出内容了。

flushSemantics()

Semantics用于将一些Widget的信息传给系统用于搜索、App内容分析等场景,这与Flutter绘制流程关系不大,这里略过。

End

到此整个runApp方法就分析完了,回顾一下整个过程,总结来说就是根据传入的Widget生成对应的ElementTree和RenderTree,之后开始进行首帧的布局和绘制。其中Widget用来描述页面的属性,这个对象是十分轻量级的且是不可变的,同一个Widget可以描述多个Element的配置,Element同时持有了Widget和RenderObject,它汇总了所有的属性信息,重绘时只将需要修改的部分通知到RenderObject。对于普通开发者,只需要关注最上层的Widget就可以了,十分简单高效。

关于Element和RenderObject的深入探索请期待下一篇文章。