Flutter入口方法runApp方法源码分析
入口
我们从一切的起点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<RenderBox>(
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<T> attachToRenderTree(BuildOwner owner, [RenderObjectToWidgetElement<T> 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<int, _FrameCallbackEntry> callbacks = _transientCallbacks;
_transientCallbacks = <int, _FrameCallbackEntry>{};
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<RenderObject> dirtyNodes = _nodesNeedingLayout;
_nodesNeedingLayout = <RenderObject>[];
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<RenderObject> dirtyNodes = _nodesNeedingPaint;
_nodesNeedingPaint = <RenderObject>[];
// 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的深入探索请期待下一篇文章。
网友评论