前言
前一节《Widget Element 和 RenderObject》 我们分析了 Widget Element 和 RenderObject 之间的关系。后续继续分析 Flutter 的渲染流程。
从这张图可以看出,整个渲染流程分为多个阶段,今天我们来看第一个阶段——build。
请求 Vsync
当我们调用 State.setState() 会引起 StateFullWidget 的 rebuild,我们从 setState()开始。
State#setState();
@protected
void setState(VoidCallback fn) {
...
_element.markNeedsBuild();
}
这里会调用 Element 的 markNeedsBuild()。
Element#markNeedsBuild()
void markNeedsBuild() {
// 非 active 状态直接返回
if (!_active)
return;
//如果已经标记为 dirty 也直接返回
if (dirty)
return;
_dirty = true;
owner.scheduleBuildFor(this);
}
标记为 dirty 之后会调用 BuildOwner 的 scheduleBuildFor() 方法。按字面意思理解应该是请求 Vsync 信号,我们来看一下它:
BuildOwner#scheduleBuildFor(Element element)
void scheduleBuildFor(Element element) {
if (element._inDirtyList) {
_dirtyElementsNeedsResorting = true;
return;
}
if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
_scheduledFlushDirtyElements = true;
onBuildScheduled();
}
_dirtyElements.add(element);
element._inDirtyList = true;
}
这里做了两个操作:onBuildScheduled() 和把 Element 添加进 BuildOwner 的 _dirtyElements 数组。
而 onBuildScheduled() 是一个回调,它是在 Binding 时被设置的:
mixin WidgetsBinding on BindingBase, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
@override
void initInstances() {
super.initInstances();
_instance = this;
buildOwner.onBuildScheduled = _handleBuildScheduled;
...
}
继续看一下这个回调函数里做了什么:
void _handleBuildScheduled() {
// If we're in the process of building dirty elements, then changes
// should not trigger a new frame.
ensureVisualUpdate();
}
这里有一段注释:
如果已经在 building 流程中,没有必要触发新的一帧。 继续:
SchedulerBinding.ensureVisualUpdate()
void ensureVisualUpdate() {
switch (schedulerPhase) {
case SchedulerPhase.idle:
case SchedulerPhase.postFrameCallbacks:
scheduleFrame();
return;
case SchedulerPhase.transientCallbacks:
case SchedulerPhase.midFrameMicrotasks:
case SchedulerPhase.persistentCallbacks:
return;
}
}
这个方法会判断当前的状态,如果是 idle 或 postFrameCallbacks 则请求新的一帧,如果是其他状态说明正在渲染流程中直接返回。
SchedulerBinding.scheduleFrame()
void scheduleFrame() {
if (_hasScheduledFrame || !_framesEnabled)
return;
window.scheduleFrame();
_hasScheduledFrame = true;
}
这里首先首先做一个判断 _hasScheduledFrame 是否已经请求了。_framesEnabled 代表 App 当前的状态,也就是说当前 App 是否允许刷新。 具体含义不细说了(我也没弄明白),以后有机会补上。
接下来就是调用 window.scheduleFrame() 以及设置标志位。
Window.scheduleFrame()
/// Requests that, at the next appropriate opportunity, the [onBeginFrame] and [onDrawFrame] callbacks be invoked.
void scheduleFrame() native 'Window_scheduleFrame';
这是一个 native 方法,注释中有说明垂直同步事件来了之后 onBeginFrame 和 onDrawFrame 会被触发。
收到 Vsync 之后
在 SchedulerBinding 中这两个方法分别是 _handleBeginFrame 和 _handleDrawFrame。
SchedulerBinding.handleBeginFrame()
void handleBeginFrame(Duration rawTimeStamp) {
Timeline.startSync('Frame', arguments: timelineWhitelistArguments);
_firstRawTimeStampInEpoch ??= rawTimeStamp;
_currentFrameTimeStamp = _adjustForEpoch(rawTimeStamp ?? _lastRawTimeStamp);
if (rawTimeStamp != null)
_lastRawTimeStamp = rawTimeStamp;
_hasScheduledFrame = false;
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;
}
}
注释里写的很清楚,这里主要处理一些 TRANSIENT 「临时的」回调,代码中 _transientCallbacks 被置为空了。如果下一帧仍需要回调,需要再次设置。 这些回调跟动画有关,后续我们再分析。
这个方法完成之后会走 handleDrawFrame()。
SchedulerBinding.handleDrawFrame()
void handleDrawFrame() {
try {
// PERSISTENT FRAME CALLBACKS
_schedulerPhase = SchedulerPhase.persistentCallbacks;
for (FrameCallback callback in _persistentCallbacks)
_invokeFrameCallback(callback, _currentFrameTimeStamp);
// POST-FRAME CALLBACKS
_schedulerPhase = SchedulerPhase.postFrameCallbacks;
final List<FrameCallback> localPostFrameCallbacks =
List<FrameCallback>.from(_postFrameCallbacks);
_postFrameCallbacks.clear();
for (FrameCallback callback in localPostFrameCallbacks)
_invokeFrameCallback(callback, _currentFrameTimeStamp);
} finally {
_schedulerPhase = SchedulerPhase.idle;
Timeline.finishSync(); // end the Frame
_currentFrameTimeStamp = null;
}
}
handleDrawFrame 中处理了两类回调 Persistent 和 Post-Frame 。“Persistent”字面意思是永久的。这类回调一旦注册以后是不能取消的。主要用来驱动渲染流水线。渲染流水线的构建(build),布局(layout)和绘制(paint)阶段都是在其中一个回调里的。
“Post-Frame”回调主要是在新帧渲染完成以后的一类调用,此类回调只会被调用一次。
在运行“Persistent”回调之前_schedulerPhase状态变为SchedulerPhase.persistentCallbacks。在运行“Post-Frame”回调之前_schedulerPhase状态变为SchedulerPhase.postFrameCallbacks。最终状态变为SchedulerPhase.idle。
这里我们关注的一个回调是 _handlePersistentFrameCallback(Duration timeStamp),它是在 RenderBinding 绑定时注册的。
RenderBinding._handlePersistentFrameCallback()
void _handlePersistentFrameCallback(Duration timeStamp) {
drawFrame();
}
可以看到最终会调用 drawFrame(),WidgetsBinding mixin 了 RenderBinding。这两个类里的 drawFrame 都会被调用,但是它们侧重点不同,WidgetsBinding 作用于 build 阶段,RenderBinding 作用于 layout 以及渲染阶段。
WidgetsBinding.drawFrame()
@override
void drawFrame() {
try {
if (renderViewElement != null)
buildOwner.buildScope(renderViewElement);
super.drawFrame();
buildOwner.finalizeTree();
} finally {
return true;
}());
}
}
这里调用了 BuildOwner 的 buildScope() 参数 renderViewElement 其实是 Element 树的根节点。
随后还调用了父类的方法,也就是 layout 以及 paint 等,我们后续再分析。
最后是 buildOwner.finalizeTree() ,这里做了什么,先挖个坑,后续再分析。
build 阶段
void buildScope(Element context, [ VoidCallback callback ]) {
if (callback == null && _dirtyElements.isEmpty)
return;
try {
_scheduledFlushDirtyElements = true;
//排序
_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;
}
} finally {
_dirtyElements.clear();
_scheduledFlushDirtyElements = false;
_dirtyElementsNeedsResorting = null;
}
}
还记得在请求 Vsync 之前把 dirty 的 Element 放进了 BuildOwner 的 _dirtyElements 数组里。因为父 Element 重建的时候其子节点也可能需要重建。父节点在前避免了重复的 build。
接下来就是 rebuild() Element.rebuild()
void rebuild() {
if (!_active || !_dirty)
return;
performRebuild();
assert(!_dirty);
}
Element 的 performRebuild()由其子类实现。我们本文的出发点是 State.setState() 那我们看下 StateFullElement 是怎么实现的。其实现在其父类 ComponentElement 里。 ComponentElement.performRebuild()
@override
void performRebuild() {
Widget built;
try {
built = build();
debugWidgetBuilderValue(widget, built);
} catch (e, stack) {
} finally {
_dirty = false;
}
try {
_child = updateChild(_child, built, slot);
} catch (e, stack) {
_child = updateChild(null, built, slot);
}
}
这里的 build 最终会调用 State.build。返回的就是我们创建的 widget,然后去更新 updateChild,这个上篇文章里我们说过,更新规则如下: | | newWidget == null | newWidget != null | | :—————–: | :——————— | :———————- | | child == null | Returns null. | Returns new [Element]. | | child != null | Old child is removed, returns null. | Old child updated if possible, returns child or new [Element]. |
我们这里是从 setState 过来,所以是更新,也就是说 child 的 update 会被调用。这个 update 在 RenderObjectElement 和普通 Element 里有些不同。 Element.update()
void update(covariant Widget newWidget) {
_widget = newWidget;
}
RenderObjectElement.update()
@override
void update(covariant RenderObjectWidget newWidget) {
super.update(newWidget);
widget.updateRenderObject(this, renderObject);
_dirty = false;
}
updateRenderObject 也就是把最新的配置到 RenderObject 上。 StateFullElement.update()
@override
void update(StatefulWidget newWidget) {
super.update(newWidget);
final StatefulWidget oldWidget = _state._widget;
rebuild();
}
最后子节点又回进行 rebuild——>performRebuild()——>updateChild()——>child.update() 这样一直沿着 Element 树走下去。
至此 build 阶段已经跑完了,后续将继续分析渲染的其他阶段。
总结
- setState 会把当前的 Element 放入 BuildOwner 的 dirtyElements 数组里,然后请求 Vsync 事件。
- drawFrame 在 WidgetsBinding 里主要作 rebuild 也就是 widget 的重建。
- drawFrame 在 RenderBinding 里回进行 layout、paint 等操作,我们后续再分析。
- build 阶段会沿着被标为 dirty 的 Element 进行 rebuild。