入口函数,其主要作用是注入给定的小控件并将其附加到屏幕上。
void runApp(Widget app) {
WidgetsFlutterBinding.ensureInitialized()
..scheduleAttachRootWidget(app)
..scheduleWarmUpFrame();
}
1.初始化一个widgetsBinding
的全局单例
2.创建跟widget并添加到renderView上,在这个过程中完成Element
树和RenderObject
树的生成
3.执行渲染
WidgetsBinding 初始化
static WidgetsBinding ensureInitialized() {
if (WidgetsBinding.instance == null)
WidgetsFlutterBinding();
return WidgetsBinding.instance!;
}
这里通过子类widgetsFlutterBinding
实例化了一个widgetsBinding
对象。但是这里widgetsFlutterBinding
没有显示声明构造方法,因此我们查看它的父类构造方法实现
abstract class BindingBase {
BindingBase() {
developer.Timeline.startSync('Framework initialization');
initInstances();
initServiceExtensions();
developer.postEvent('Flutter.FrameworkInitialization', <String, String>{});
developer.Timeline.finishSync();
}
可以看到,这里主要调用了initInstances()
做了一些初始化操作,但是基类BindingBase
自己的initInstances()
是一个空的实现,因此要查看其他父类中的实现。这里我们需要注意到widgetsFlutterBinding
类的继承情况
class WidgetsFlutterBinding extends BindingBase with GestureBinding, SchedulerBinding, ServicesBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {
}
因此,这里initInstances()
方法会调用最外侧的widgetsBinding
中的具体实现。检查代码可知,混入的每一个类中都实现了initInstances
方法,并且还调用了super.initInstances()
,这样一来,就会从最外侧的widgetsBinding
开始,依次链式调用每一个混入类中的initInstances()
方法,完成各个Binding的初始化。
接下来我们先进入widgetsBinding
中的initInstances
实现,逻辑不多,主要是创建了BuildOwner
对象,并给window设置了一些回调函数
mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
@override
void initInstances() {
super.initInstances();
_instance = this;
assert(() {
_debugAddStackFilters();
return true;
}());
_buildOwner = BuildOwner();
buildOwner!.onBuildScheduled = _handleBuildScheduled;
window.onLocaleChanged = handleLocaleChanged;
window.onAccessibilityFeaturesChanged = handleAccessibilityFeaturesChanged;
SystemChannels.navigation.setMethodCallHandler(_handleNavigationInvocation);
FlutterErrorDetails.propertiesTransformers.add(transformDebugCreator);
}
再看看RendererBinding
中的initInstances
实现,这里创建了PipelineOwner
对象,也给window设置了另一些回调函数
mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
@override
void initInstances() {
super.initInstances();
_instance = this;
_pipelineOwner = PipelineOwner(
onNeedVisualUpdate: ensureVisualUpdate,
onSemanticsOwnerCreated: _handleSemanticsOwnerCreated,
onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,
);
window
..onMetricsChanged = handleMetricsChanged
..onTextScaleFactorChanged = handleTextScaleFactorChanged
..onPlatformBrightnessChanged = handlePlatformBrightnessChanged
..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged
..onSemanticsAction = _handleSemanticsAction;
// 创建一个根RenderObject
initRenderView();
_handleSemanticsEnabledChanged();
assert(renderView != null);
// 注册presistentCallbacks回调
addPersistentFrameCallback(_handlePersistentFrameCallback);
initMouseTracker();
if (kIsWeb) {
addPostFrameCallback(_handleWebFirstFrame);
}
}
这里我们可以查看一些跟RenderObject
的创建
void initRenderView() {
assert(!_debugIsRenderViewInitialized);
assert(() {
_debugIsRenderViewInitialized = true;
return true;
}());
renderView = RenderView(configuration: createViewConfiguration(), window: window);
renderView.prepareInitialFrame();
}
这里其他Binding的初始化先略过,我们查看一下出现多次的window是什么东西。根据官方的解释,window是Flutter框架链接宿主操作系统的接口
我们可以发下,混入的那些Binding基本上都是监听并处理window对象的一些事件,然后将这些事件根据Flutter框架层的模型进行包装、抽象最后分发。
查看官方文档可知,widgetsFlutterBinding
是将Framework与Flutter引擎榜单的胶水。BindingBase
相当于所以Binding的基类,定义了一些公共的行为。
*GestureBinding
:榜单Framework手势子系统,是Framework事件模型与底层事件的绑定入口
*ServiceBinding
:用于绑定平台消息通道(message channel),主要处理原生和Flutter通信
*SchedulerBinding
:监听刷新事件,绑定Framework绘制调度子系统
*PaintingBinding
:绑定绘制库,主要用于处理图片缓存
*SemanticsBinding
:语义化层与Flutter引擎的桥梁,主要是辅助功能的底层这次
*RendererBinding
:是渲染树与Flutter引擎的桥梁
*WidgetsBinding
:它是Flutter Widget层与引擎的桥梁
构建Element和RenderObject树
再看runApp下方法中scheduleAttachRootWidget
方法,它实际上调用了如下方法,它完成了Widget、RenderObject
和Element
三者的关联。具体代码在attachToRenderTree
方法中
void attachRootWidget(Widget rootWidget) {
_readyToProduceFrames = true;
_renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
container: renderView,
debugShortDescription: '[root]',
child: rootWidget,
).attachToRenderTree(buildOwner!, renderViewElement as RenderObjectToWidgetElement<RenderBox>?);
}
BuildOwner
是widget框架的管理器类,该类跟着哪些widgets需要重建,并处理其他使用于widgets树的任务,如管理树的非活动元素列表,并在调试时的热重载期间在必要时触发“reassemble”命令。
RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T>? 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!;
}
首先执行 element 为null, 所以执行creteElement
方法创建Element,而真正执行构建树的操作是owner.buildScope
方法。这个方法首先执行传入的回调,即执行element.mount(null,null)
方法
@override
void mount(Element? parent, dynamic newSlot) {
assert(parent == null);
super.mount(parent, newSlot);
_rebuild();
}
mount 方法会首先调用父类的mount方法,即调用到RenderObjectElement
类的 mount 方法如下,在此处构建RenderObject
对象,同时调用attachRenderObject
方法生成RenderObject
树
@override
void mount(Element? parent, dynamic newSlot) {
super.mount(parent, newSlot);
// ...
_renderObject = widget.createRenderObject(this);
attachRenderObject(newSlot);
_dirty = false;
}
再看上面的_rebuild
方法,其中主要调用了updateChild
方法,updateChild方法的主要作用是用给定的新配置(widge)更新给定的子元素。这里可以关注catch
中的代码,正是此处社鞥从了Flutter中常见的红屏报错页面的ErrorWidget
。
void _rebuild() {
try {
_child = updateChild(_child, widget.child, _rootChildSlot);
assert(_child != null);
} catch (exception, stack) {
final FlutterErrorDetails details = FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'widgets library',
context: ErrorDescription('attaching to the render tree'),
);
FlutterError.reportError(details);
final Widget error = ErrorWidget.builder(details);
_child = updateChild(null, error, _rootChildSlot);
}
}
updateChilid
方法移除注释与断言后如下,newWidget
和child
的值存在几种不同的组合情况, 当首次进入时,会执行inflateWidget
,为根Widget创建一个新的element。
@protected
Element? updateChild(Element? child, Widget? newWidget, dynamic newSlot) {
if (newWidget == null) {
if (child != null)
deactivateChild(child);
return null;
}
final 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);
assert(child.widget == newWidget);
assert(() {
child.owner!._debugElementWasRebuilt(child);
return true;
}());
newChild = child;
} else {
deactivateChild(child);
assert(child._parent == null);
newChild = inflateWidget(newWidget, newSlot);
}
} else {
newChild = inflateWidget(newWidget, newSlot);
}
return newChild;
}
inflateWidget
方法删除断言如下,可以看到,这里创建了newChild
后又调用了mount方法,依次递归变了Widget树的子节点,不断为Widget创建对应的Element、RenderObject, 以完成“三棵树”的构建与关联。
@protected
Element inflateWidget(Widget newWidget, dynamic newSlot) {
final Key? key = newWidget.key;
if (key is GlobalKey) {
final Element? newChild = _retakeInactiveElement(key, newWidget);
if (newChild != null) {
newChild._activateWithParent(this, newSlot);
final Element? updatedChild = updateChild(newChild, newWidget, newSlot);
return updatedChild!;
}
}
final Element newChild = newWidget.createElement();
newChild.mount(this, newSlot);
return newChild;
}
执行渲染
接下runApp 调用scheduleWarmUpFrame
进行了第一次绘制,具体实现在SchedulerBinding
中。 该方法会锁定事件调度直到完成为止,即在这次绘制完成之前都不会接收event(触摸事件等)。
该方法中主要调用了handleBeginFrame()
和handleDrawFrame()
void scheduleWarmUpFrame() {
if (_warmUpFrame || schedulerPhase != SchedulerPhase.idle)
return;
_warmUpFrame = true;
Timeline.startSync('Warm-up frame');
final bool hadScheduledFrame = _hasScheduledFrame;
// 在这里使用计时器来确保microtasks在两者之间刷新
Timer.run(() {
handleBeginFrame(null);
});
Timer.run(() {
handleDrawFrame();
resetEpoch();
_warmUpFrame = false;
if (hadScheduledFrame)
scheduleFrame();
});
// 锁定事件,使触摸事件在等到预定帧结束前不会自行插入
lockEvents(() async {
await endOfFrame;
Timeline.finishSync();
});
}
其中handleBeginFrame
方法用于渲染前的一些准备,主要是处理transientCallbacks
回调,也就是触发动画相关的Ticker回调。 而真正处理渲染的是handleDrawFrame
方法,它由引擎调用以生成新帧。
void handleDrawFrame() {
Timeline.finishSync(); // end the "Animate" phase
try {
// 处理persistentCallbacks回调
_schedulerPhase = SchedulerPhase.persistentCallbacks;
for (final FrameCallback callback in _persistentCallbacks)
_invokeFrameCallback(callback, _currentFrameTimeStamp!);
// 处理postFrameCallbacks回调
_schedulerPhase = SchedulerPhase.postFrameCallbacks;
final List<FrameCallback> localPostFrameCallbacks =
List<FrameCallback>.from(_postFrameCallbacks);
_postFrameCallbacks.clear();
for (final FrameCallback callback in localPostFrameCallbacks)
_invokeFrameCallback(callback, _currentFrameTimeStamp!);
} finally {
_schedulerPhase = SchedulerPhase.idle;
Timeline.finishSync(); // end the Frame
_currentFrameTimeStamp = null;
}
}
根据官方文档的解释,有一下三个回调队列
-
transientCallbacks
:由系统的Window.onBenginFrame
回调触发,一般用于处理动画的回调。 -
persistentCallbacks
:由系统的Window.onDrawFrame
回调触发。用久callback,一经注册无法移除,由widgetsBinding.instance.addPersitentFrameCallback()
注册,主要产后护理布局与绘制工作。 -
postFrameCallbacks
:在persistentCallbacks回调之后,Window.onDrawFrame
返回之前执行。它只会调用一次,调用后就会被系统移除。可由widgetsBinding.Instance.addPostFrameCallback()
注册,通常用于State的更新。
可由看的,这里的代码逻辑主要就是处理persistentCallbacks
和postFrameCallbacks
的回调,而框架正好又在 RenderBinding 初始化时注册了一个persistentCallbacks回调
mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureBinding, SemanticsBinding, HitTestable {
@override
void initInstances() {
super.initInstances();
_instance = this;
/// ...
addPersistentFrameCallback(_handlePersistentFrameCallback);
/// ....
}
void _handlePersistentFrameCallback(Duration timeStamp) {
drawFrame();
_scheduleMouseTrackerUpdate();
}
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;
}
}
以上代码中需要特别注意,回调中并不是直接调用RenderBinding
中的drawFrame()
方法,而是依据widgetsFlutterBinding
混入的顺序调用。
根据Dart mixin 语法,混入多个类后,调用同名方法时,是从最外层开始调用。可以看到,这里是调用的widgetsBinding.drawFrame()
/// [WidgetsBinding]
/// 抽取构建和渲染管道以生成一帧。
/// 这个方法被handleDrawFrame调用,当需要布局和绘制一帧时,引擎会自动调用这个方法
void drawFrame() {
TimingsCallback? firstFrameCallback;
if (_needToReportFirstFrame) {
firstFrameCallback = (List<FrameTiming> timings) {
if (!kReleaseMode) {
developer.Timeline.instantSync('Rasterized first useful frame');
developer.postEvent('Flutter.FirstFrame', <String, dynamic>{});
}
SchedulerBinding.instance!.removeTimingsCallback(firstFrameCallback!);
firstFrameCallback = null;
_firstFrameCompleter.complete();
};
// 只有再调用 [window.render] 时才会被调用
// 当 [sendFramesToEngine] 在帧中被设置为false时,它将不会被调用,我们需要删除回调
SchedulerBinding.instance!.addTimingsCallback(firstFrameCallback!);
}
try {
if (renderViewElement != null)
buildOwner!.buildScope(renderViewElement!);
super.drawFrame();
buildOwner!.finalizeTree();
} finally {
// assert
}
if (!kReleaseMode) {
if (_needToReportFirstFrame && sendFramesToEngine) {
developer.Timeline.instantSync('Widgets built first useful frame');
}
}
_needToReportFirstFrame = false;
if (firstFrameCallback != null && !sendFramesToEngine) {
// 这个帧是延时的,并不是第一个发送给引擎的应该报告的帧
_needToReportFirstFrame = true;
SchedulerBinding.instance!.removeTimingsCallback(firstFrameCallback!);
}
}
实际上,这里的主要逻辑是try
中的代码
try {
if (renderViewElement != null)
// 将被标记为dirty的Element进行rebuild()
buildOwner!.buildScope(renderViewElement!);
// 调用父类的drawFrame,这里实际上调用的是 RenderBinding中的drawFrame()方法
super.drawFrame();
// 通过卸载任何不再活动的元素来完成元素构建过程
buildOwner!.finalizeTree();
}
其中通过调用super.drawFrame()
又再次回到RenderBinding
中的drawFrame()
方法
//更新需要计算布局的谊染对象。在这个阶段,计算每个渲染对象的大小和位置。
pipelineOwner.flushLayout();
//更新具有dirty compositing bits的所有渲染对象。在此阶段, 每个渲染对象将了解其子对象是否需要合成。
//
在选择如何实现视觉效果(例如剪裁)时,在绘画阶段将使用此信息。
pipeline0wner.flushCompositingBits();
//访问需要绘制的渲染对象进行绘制
pipeline0wner.flushPaint();
//该方法将画好的layer传给引擎, 该方法调用结束后,屏幕就会显示内容
renderView.compositeFrame();
//如果启用了语义,则该方法将编译渲染对象的语义,
传给系统用于辅助功能,如搜索等。
pipelineOwner.flushSemantics();
小结
根据官方drawFrame 文档的描述,绘制过程经历一下十个阶段:
1、动画阶段:handleBeginFrame
方法是用window.onBeginFrame
注册的,按注册顺序调用所有用scheduleFrameCallback
注册的transientCallbacks
, 其中包括所有驱动AnimationController
对象的Ticker实例,也就是说此时所有的活动Animation对象的tick在此刻回调。
2、Microtask: 在handleBeginFrame
放回后,任何被transientCallBacks
安排的微任务都会被执行。这通常包括自Ticker和AnimationController的完成该帧的Future回调。
3、构建阶段:重建widget树中所有的dirty元素(见 State.build),查阅State.setState了解更多关于标记一个widget dirty的构建细节,有关此步骤的更新信息,请参见BuildOwner。
4、布局阶段:系统中的所有标记dirty的 RenderObject
都会被布局。(参见 RenderObject.performLayout)。 请参阅RenderObject.markNeedsLayout, 了解关于标记一个对象以进行布局更多的细节。)
5、合成位阶段:更新任何标记dirty的RenderObject
对象上的 compositing bits。(请参见RenderObject.markNeedsCompositingBitsUpdate)。
6、绘制阶段:系统中的所有标记dirty的 RenderObject
都会被重新绘制,这将生产Layer树。 (请参阅RenderObject.markNeedsPaint,以获取有关将对象标记为dirty的更新详细信息)。
7、合成阶段:图层将被转化为一个Scene并发送给GPU。
8、语义阶段:系统中所有标记dirty的RenderObject的语义都会被更新(见 RenderObject.assembleSemanticsNode
)。这将生产SemanticsNode树。请参阅RenderObject.markNeedsSemanticsUpdate
以了解关于标记一个对象为dirty的语义的细节。
9、widget层的完成阶段:widget树已完成,这将导致在从widget树中删除的任何对象上调用State.dispose
。 请参见``BuildOwner.finalizeTree````
10、调度层的最终化阶段:在 drawFrame
返回后, handleDrawFrame
会调用postFrameCallbacks
回调(用 addPostFrameCallback注册的)