Flutter 视图渲染流程
flutter 中的 WidgetsFlutterBinding 集成了 GestureBinding、ServicesBinding、SchedulerBinding、PaintingBinding、SemanticsBinding、RendererBinding、WidgetsBinding 等 7 种 Binding,它们都有自己在功能上的划分,其中,RendererBinding 主要负责的是视图渲染相关的,与之密切相关的是 render 树,RendererBinding 所做的就是对 RenderBox 进行 layout、paint 等操作。
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;
initRenderView();
_handleSemanticsEnabledChanged();
assert(renderView != null);
addPersistentFrameCallback(_handlePersistentFrameCallback);
_mouseTracker = _createMouseTracker();
}
首先还是从 initInstances 开始看,主要还是包括以下几步:
- 创建 PipelineOwner,这个类在之后被用于集中管理渲染相关的操作
- 绑定 window 回调,比如 textScale(字体缩放大小) 改变的回调,platformBrightness(亮度)改变的回调等
- 创建 RenderView(render 树的入口),并与 PipelineOwner 绑定
- 添加视图绘制的回调(添加到 SchedulerBinding 中的 persistentCallback 中)
- 创建 MouseTracker,用于监听鼠标的操作,但这里不是为了触发事件,而是根据鼠标的位置更新 UI 等
PipelineOwner
_pipelineOwner = PipelineOwner(
onNeedVisualUpdate: ensureVisualUpdate,
onSemanticsOwnerCreated: _handleSemanticsOwnerCreated,
onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,
);
创建 PipelineOwner 时传了三个回调,分别用于更新视图(内部会调用 scheduleFrame,最终会回调至 SchedulerBinding 的 onDrawFrame 中,调用 persistentCallback)、初始化 Semantic、清除 Semantic。
在 PipelineOwner 内部,有 rootNode(render 树根节点,也就是 RenderView)、_nodesNeedingLayout(保存的待执行 layout 的 render 结点)、flushLayout(调用所有需要 layout 的结点的对应函数完成 layout 操作)等变量、函数。
总的来看,PipelineOwner 正如其名,是整个渲染过程中的管理者,所有需要重新渲染的结点首先要将自己注册到 PipelineOwner 中,然后再由 PipelineOwner 在合适的时机调用结点的函数完成渲染的部分工作。
window 回调
在 initInstances 中注册了五个回调函数,分别是
- onMetricsChanged
- onTextScaleFactorChanged
- onPlatformBrightnessChanged
- onSemanticsEnabledChanged
- onSemanticsAction
onMetricsChanged 对应设备尺寸发生变化,此时会更新 RenderView 的 ViewConfiguration,并强制刷新视图(摒除生命周期的影响)。
onTextScaleFactorChanged 对应字体缩放大小的变化,此时会将其通知给所有的监听者自行处理,比如 WidgetsApp 会调用 setState 进行刷新。
onPlatformBrightnessChanged 对应屏幕亮度的变化,它的操作与 onTextScaleFactorChanged 一致。
onSemanticsEnabledChanged 对应的是 SemanticsEnable 的变化,当 enable 为 true 时,PipelineOwner 就会创建出 SemanticsOwner,进而对应一系列的 Semantics 操作,当其为 false 时,就会屏蔽这一操作,如下:
void setSemanticsEnabled(bool enabled) {
if (enabled) {
_semanticsHandle ??= _pipelineOwner.ensureSemantics();
} else {
_semanticsHandle?.dispose();
_semanticsHandle = null;
}
}
这两步操作会分别创建/销毁 SemanticsOwner,而 SemanticsOwner 正是负责执行 Semantics 相关操作的管理者。
onSemanticsAction 对应的是 SemanticsAction 的传递,当 SemanticsOwner 存在时,它就会交给 SemanticsOwner 处理。
初始化 RenderView
初始化 RenderView 包括两部分,第一是先把 RenderView 创建出来,将其与 PipelineOwner 绑定,然后开始启动绘制流程。
void initRenderView() {
assert(renderView == null);
renderView = RenderView(configuration: createViewConfiguration(), window: window);
renderView.scheduleInitialFrame();
}
RenderView 的创建需要两个参数,ViewConfiguration 和 Window,前者用于确定整个 RenderView 绘制的基调,确定了绘制大小。后者作为接口调用 engine 层的功能。
set renderView(RenderView value) {
assert(value != null);
_pipelineOwner.rootNode = value;
}
set rootNode(AbstractNode value) {
if (_rootNode == value)
return;
_rootNode?.detach();
_rootNode = value;
_rootNode?.attach(this);
}
而 RenderView 的 set 函数,则是将其与 PipelineOwner 建立联系,同时 PipelineOwner 也会执行解绑旧的 RenderView(如果有的话)并绑定新的 RenderView。
接下来便是调用 scheduleInitialFrame 进行首帧的初始化:
void scheduleInitialFrame() {
assert(owner != null);
assert(_rootTransform == null);
scheduleInitialLayout();
scheduleInitialPaint(_updateMatricesAndCreateNewRootLayer());
assert(_rootTransform != null);
owner.requestVisualUpdate();
}
总共四个操作,scheduleInitialLayout 将 RenderView 加入到 PipelineOwner 的 _nodesNeedingLayout 中等待布局,_updateMatricesAndCreateNewRootLayer 创建一个 layer,scheduleInitialPaint 将 Layer 传递给 RenderView 的同时,还将 RenderView 加入到 PipelineOwner 的 _nodesNeedingPaint 中等待绘制,最后调用 requestVisualUpdate 请求刷新视图,最终会在回调函数 onDraw 中使用到以上数据完成渲染。
onDraw 回调
addPersistentFrameCallback 会将 _handlePersistentFrameCallback 函数加入到 ScheduerBinding 中的回调列表中,在之后的每一次 onDrawFrame 函数中调用,这个函数的实现就是调用 onDraw,也就是 RendererBinding 中真正执行绘制的地方:
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.
}
以上步骤会完成一个完整的绘制流程,会先后执行现在的 render 树中每一个待布局结点的 layout、待绘制结点的 paint,最后将其合成、输送到 GPU 中完成渲染等。
flushLayout
首先是调用 flushLayout,也就是 layout 过程,这里会对所有需要执行 layout 的 RenderObject 进行重新布局。所有的脏结点都被存放在 _nodesNeedingLayout 中。
脏结点是在 markNeedLayout 函数中添加的:
void markNeedsLayout() {
assert(_debugCanPerformMutations);
if (_needsLayout) {
assert(_debugSubtreeRelayoutRootAlreadyMarkedNeedsLayout());
return;
}
assert(_relayoutBoundary != null);
if (_relayoutBoundary != this) {
markParentNeedsLayout();
} else {
_needsLayout = true;
if (owner != null) {
assert(() {
if (debugPrintMarkNeedsLayoutStacks)
debugPrintStack(label: 'markNeedsLayout() called for $this');
return true;
}());
owner._nodesNeedingLayout.add(this);
owner.requestVisualUpdate();
}
}
}
它会先将当前结点加入到脏结点列表 _nodesNeedingLayout 中,然后调用 requestVisualUpdate,这个函数最终的会调用 scheduleFrame 请求下一帧。而 markNeedLayout 被调用的时机,一般就是 RenderObject 刷新的时候,比如对一个文字渲染 RenderObject 进行内容更新的时候,它就会调用自己的 markNeedLayout,再下一帧需要刷新文字的时候先更新自己的布局,这样才能更好地展示新的内容,关于这一点的具体调用过程,这里先不展开。
void flushLayout() {
if (!kReleaseMode) {
Timeline.startSync('Layout', arguments: timelineWhitelistArguments);
}
assert(() {
_debugDoingLayout = true;
return true;
}());
try {
// TODO(ianh): assert that we're not allowing previously dirty nodes to redirty themselves
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();
}
}
} finally {
assert(() {
_debugDoingLayout = false;
return true;
}());
if (!kReleaseMode) {
Timeline.finishSync();
}
}
}
对 RenderObject 重新布局调用的是 _layoutWithoutResize 函数:
void _layoutWithoutResize() {
assert(_relayoutBoundary == this);
RenderObject debugPreviousActiveLayout;
try {
performLayout();
markNeedsSemanticsUpdate();
} catch (e, stack) {
_debugReportException('performLayout', e, stack);
}
_needsLayout = false;
markNeedsPaint();
}
执行分为三步,首先调用 performLayout 进行布局操作,然后调用 markNeedsSemanticsUpdate 和 markNeedsPaint 标志当前结点需要更新 Semantics,同时还需要重新绘制。
performLayout 由 RenderObject 的子类各自实现,比较简单的以 RenderLimitedBox 为例,
void performLayout() {
if (child != null) {
child.layout(_limitConstraints(constraints), parentUsesSize: true);
size = constraints.constrain(child.size);
} else {
size = _limitConstraints(constraints).constrain(Size.zero);
}
}
没有子结点就以最小的作为 size,否则就以子结点的 size 为准,同时当子结点变更的时候,自己也会随之变化(parentUsesSize),由此可见,这是一个父节点受限于子结点的例子。
再看 RenderAspectRatio,
void performLayout() {
size = _applyAspectRatio(constraints);
if (child != null)
child.layout(BoxConstraints.tight(size));
}
首先,根据自己的 ratio 和 constraints 计算出合适的 size,然后再利用这个 size 计算子结点的 size,这是一个子结点受限于父节点的例子。
总结下来,flushLayout 过程的结果就是更新的 RenderObject 树中结点的 size,以便在 paint 阶段确定每一个 layer 的大小、位置等。
flushCompositingBits
这个过程会调用 _nodesNeedingCompositingBitsUpdate 中所有结点的 _updateCompositingBits 函数,结点被添加到列表中的时机就是调用 markNeedsCompositingBitsUpdate 时,
void markNeedsCompositingBitsUpdate() {
if (_needsCompositingBitsUpdate)
return;
_needsCompositingBitsUpdate = true;
if (parent is RenderObject) {
final RenderObject parent = this.parent;
if (parent._needsCompositingBitsUpdate)
return;
if (!isRepaintBoundary && !parent.isRepaintBoundary) {
parent.markNeedsCompositingBitsUpdate();
return;
}
}
// parent is fine (or there isn't one), but we are dirty
if (owner != null)
owner._nodesNeedingCompositingBitsUpdate.add(this);
}
这个函数在 adoptChild、dropChild 等函数中会被调用。
void _updateCompositingBits() {
if (!_needsCompositingBitsUpdate)
return;
final bool oldNeedsCompositing = _needsCompositing;
_needsCompositing = false;
visitChildren((RenderObject child) {
child._updateCompositingBits();
if (child.needsCompositing)
_needsCompositing = true;
});
if (isRepaintBoundary || alwaysNeedsCompositing)
_needsCompositing = true;
if (oldNeedsCompositing != _needsCompositing)
markNeedsPaint();
_needsCompositingBitsUpdate = false;
}
在 _updateCompositingBits 中,通过遍历子结点判断是否需要重新绘制,如果更新之后的值与之前不同,还需要调用 markNeedsPaint 请求重新绘制。从上面的流程可以看出,如果一个 RenderObject 的某个孩子 _needsCompositing 为 true,那么它的 _needsCompositing 也为 true,换句话说,就是能够保证所有 _needsCompositing 为 true 的结点最终都能够被绘制到。
而确定某一个结点的 _needsCompositing 为 true 有三种途径,即当前结点的 isRepaintBoundary、alwaysNeedsCompositing,以及子结点的 needsCompositing。对于 RenderObject 类,以上三个变量默认都是 false,而它的子类则会根据自己的特性重写这三个 get 函数,比如 RenderView 的 isRepaintBoundary 为 true,RenderAndroidView 的 alwaysNeedsCompositing 为 ture,而 needsCompositing 也可以被子类重写,比如 RenderMouseRengion 的 get 函数为:
@override
bool get needsCompositing => super.needsCompositing || _annotationIsActive;
总结下来,flushCompositingBits 过程确定了 RenderObject 树中每一个结点的有效性,而这会在 paint 阶段用于判断是否需要对其绘制。
flushPaint
这个过程会调用 PaintingContext.repaintCompositedChild 函数重绘所有脏结点,从前面的流程可以了解到,无论是 layout 阶段还是 updateCompositing 阶段,都有可能调用到 markNeedPaint,
void markNeedsPaint() {
assert(owner == null || !owner.debugDoingPaint);
if (_needsPaint)
return;
_needsPaint = true;
if (isRepaintBoundary) {
// If we always have our own layer, then we can just repaint
// ourselves without involving any other nodes.
assert(_layer is OffsetLayer);
if (owner != null) {
owner._nodesNeedingPaint.add(this);
owner.requestVisualUpdate();
}
} else if (parent is RenderObject) {
final RenderObject parent = this.parent;
parent.markNeedsPaint();
assert(parent == this.parent);
} else {
// If we're the root of the render tree (probably a RenderView),
// then we have to paint ourselves, since nobody else can paint
// us. We don't add ourselves to _nodesNeedingPaint in this
// case, because the root is always told to paint regardless.
if (owner != null)
owner.requestVisualUpdate();
}
}
在一个结点调用 markNeedsPaint 函数的时候,与上面的两个不同,它是直接向上寻找到一个 RepaintBondary 并将它加入到脏结点列表,所以很有可能多个结点调用这个函数最终添加的是同一个结点,最后还是调用 requestVisualUpdate 请求更新视图。
在 flushPaint 中,先对所有脏结点进行排序,以深度最深为先,如果结点的视图 layer 为 attached 状态,则调用 PaintingContext.repaintCompositedChild 函数,否则,调用它的 _skippedPaintingOnLayer 函数将 _needsPaint 置为 true,等待在 attach 之后再进行重绘。
而在 PaintingContext.repaintCompositedChild 函数中,
static void _repaintCompositedChild(
RenderObject child, {
bool debugAlsoPaintedParent = false,
PaintingContext childContext,
}) {
OffsetLayer childLayer = child._layer;
if (childLayer == null) {
assert(debugAlsoPaintedParent);
// Not using the `layer` setter because the setter asserts that we not
// replace the layer for repaint boundaries. That assertion does not
// apply here because this is exactly the place designed to create a
// layer for repaint boundaries.
child._layer = childLayer = OffsetLayer();
} else {
assert(childLayer is OffsetLayer);
assert(debugAlsoPaintedParent || childLayer.attached);
childLayer.removeAllChildren();
}
childContext ??= PaintingContext(child._layer, child.paintBounds);
child._paintWithContext(childContext, Offset.zero);
// Double-check that the paint method did not replace the layer (the first
// check is done in the [layer] setter itself).
assert(identical(childLayer, child._layer));
childContext.stopRecordingIfNeeded();
}
如上,首先保证 childLayer 的存在,然后创建 PaintingContext 实例,调用其 _paintWithContext 函数,在这个函数里还会调用 paint 函数,而 RenderObject 的子类则可以实现这个函数,进行自定义的绘制,最后调用 stopRecordingIfNeeded 进行收尾,完成一个 layer 的绘制工作。
首先看一下 PaintingContext 的构造函数,只是将 layer 和 estimatedBounds 这两个参数传递进去,然后是 _paintWithContext,
void _paintWithContext(PaintingContext context, Offset offset) {
// If we still need layout, then that means that we were skipped in the
// layout phase and therefore don't need painting. We might not know that
// yet (that is, our layer might not have been detached yet), because the
// same node that skipped us in layout is above us in the tree (obviously)
// and therefore may not have had a chance to paint yet (since the tree
// paints in reverse order). In particular this will happen if they have
// a different layer, because there's a repaint boundary between us.
if (_needsLayout)
return;
RenderObject debugLastActivePaint;
_needsPaint = false;
try {
paint(context, offset);
assert(!_needsLayout); // check that the paint() method didn't mark us dirty again
assert(!_needsPaint); // check that the paint() method didn't mark us dirty again
} catch (e, stack) {
_debugReportException('paint', e, stack);
}
}
首先把 _needsPaint 置为 false,然后调用 paint 函数,这是一个空实现函数,需要由各个子类按照自身情况单独实现。
比如 RenderClipRect,
void paint(PaintingContext context, Offset offset) {
if (child != null) {
_updateClip();
layer = context.pushClipRect(needsCompositing, offset, _clip, super.paint, clipBehavior: clipBehavior, oldLayer: layer);
} else {
layer = null;
}
}
这里的 needsCompositing 参数,就是之前在 flushCompositingBits 阶段得到的结果。
ClipRectLayer pushClipRect(bool needsCompositing, Offset offset, Rect clipRect, PaintingContextCallback painter, { Clip clipBehavior = Clip.hardEdge, ClipRectLayer oldLayer }) {
final Rect offsetClipRect = clipRect.shift(offset);
if (needsCompositing) {
final ClipRectLayer layer = oldLayer ?? ClipRectLayer();
layer
..clipRect = offsetClipRect
..clipBehavior = clipBehavior;
pushLayer(layer, painter, offset, childPaintBounds: offsetClipRect);
return layer;
} else {
clipRectAndPaint(offsetClipRect, clipBehavior, offsetClipRect, () => painter(this, offset));
return null;
}
}
如果 needsCompositing 为 false,表示这里的 layer 是不需要的,它会直接调用 clipRectAndPaint 裁去这块区域。然后先将 ClipRectLayer 的属性赋值,再调用 pushLayer 将这个 layer 加入到 layer 树中。
void pushLayer(ContainerLayer childLayer, PaintingContextCallback painter, Offset offset, { Rect childPaintBounds }) {
assert(painter != null);
// If a layer is being reused, it may already contain children. We remove
// them so that `painter` can add children that are relevant for this frame.
if (childLayer.hasChildren) {
childLayer.removeAllChildren();
}
stopRecordingIfNeeded();
appendLayer(childLayer);
final PaintingContext childContext = createChildContext(childLayer, childPaintBounds ?? estimatedBounds);
painter(childContext, offset);
childContext.stopRecordingIfNeeded();
}
从函数的实现了解到,childLayer 先是被加入到当前 PaintingContext 的 containerLayer 中(appendLayer),然后再以 childLayer 为参数创建了一个新的 PaintingContext,然后调用 painter 函数,在这个函数里又会进行 childLayer 内容的绘制,也会给 childLayer 上再添加一些其他的 layer,如此就构成了一个树的结构,所有的 layer 最终都会被包含在一个根 layer 中,与 RenderObject 树类似。
再往前回溯,创建的新的 PaintingContext 用于执行 painter 函数,这个函数在 RenderClipRect 中,就是 super.paint,
void paint(PaintingContext context, Offset offset) {
if (child != null)
context.paintChild(child, offset);
}
它会执行 child 的绘制,这里的 context 就是前面创建的 childContext,其对应的 layer 就是上面的 childLayer,也表明 RenderClipRect 的 child 都会绘制在新建的这个 ClipRectLayer 上,从某种角度来说,ClipRectLayer 属于功能性 layer,只是给 child 提供容器 layer,而没有绘制实际的内容。
而 RenderImage,可以绘制一个图片。它的 paint 函数调用 paintImage,
void paintImage({
@required Canvas canvas,
@required Rect rect,
@required ui.Image image,
double scale = 1.0,
ColorFilter colorFilter,
BoxFit fit,
Alignment alignment = Alignment.center,
Rect centerSlice,
ImageRepeat repeat = ImageRepeat.noRepeat,
bool flipHorizontally = false,
bool invertColors = false,
FilterQuality filterQuality = FilterQuality.low,
}) {
assert(canvas != null);
assert(image != null);
assert(alignment != null);
assert(repeat != null);
assert(flipHorizontally != null);
if (rect.isEmpty)
return;
Size outputSize = rect.size;
Size inputSize = Size(image.width.toDouble(), image.height.toDouble());
Offset sliceBorder;
if (centerSlice != null) {
sliceBorder = Offset(
centerSlice.left + inputSize.width - centerSlice.right,
centerSlice.top + inputSize.height - centerSlice.bottom,
);
outputSize -= sliceBorder;
inputSize -= sliceBorder;
}
fit ??= centerSlice == null ? BoxFit.scaleDown : BoxFit.fill;
assert(centerSlice == null || (fit != BoxFit.none && fit != BoxFit.cover));
final FittedSizes fittedSizes = applyBoxFit(fit, inputSize / scale, outputSize);
final Size sourceSize = fittedSizes.source * scale;
Size destinationSize = fittedSizes.destination;
if (centerSlice != null) {
outputSize += sliceBorder;
destinationSize += sliceBorder;
// We don't have the ability to draw a subset of the image at the same time
// as we apply a nine-patch stretch.
assert(sourceSize == inputSize, 'centerSlice was used with a BoxFit that does not guarantee that the image is fully visible.');
}
if (repeat != ImageRepeat.noRepeat && destinationSize == outputSize) {
// There's no need to repeat the image because we're exactly filling the
// output rect with the image.
repeat = ImageRepeat.noRepeat;
}
final Paint paint = Paint()..isAntiAlias = false;
if (colorFilter != null)
paint.colorFilter = colorFilter;
if (sourceSize != destinationSize) {
paint.filterQuality = filterQuality;
}
paint.invertColors = invertColors;
final double halfWidthDelta = (outputSize.width - destinationSize.width) / 2.0;
final double halfHeightDelta = (outputSize.height - destinationSize.height) / 2.0;
final double dx = halfWidthDelta + (flipHorizontally ? -alignment.x : alignment.x) * halfWidthDelta;
final double dy = halfHeightDelta + alignment.y * halfHeightDelta;
final Offset destinationPosition = rect.topLeft.translate(dx, dy);
final Rect destinationRect = destinationPosition & destinationSize;
final bool needSave = repeat != ImageRepeat.noRepeat || flipHorizontally;
if (needSave)
canvas.save();
if (repeat != ImageRepeat.noRepeat)
canvas.clipRect(rect);
if (flipHorizontally) {
final double dx = -(rect.left + rect.width / 2.0);
canvas.translate(-dx, 0.0);
canvas.scale(-1.0, 1.0);
canvas.translate(dx, 0.0);
}
if (centerSlice == null) {
final Rect sourceRect = alignment.inscribe(
sourceSize, Offset.zero & inputSize,
);
if (repeat == ImageRepeat.noRepeat) {
canvas.drawImageRect(image, sourceRect, destinationRect, paint);
} else {
for (Rect tileRect in _generateImageTileRects(rect, destinationRect, repeat))
canvas.drawImageRect(image, sourceRect, tileRect, paint);
}
} else {
if (repeat == ImageRepeat.noRepeat) {
canvas.drawImageNine(image, centerSlice, destinationRect, paint);
} else {
for (Rect tileRect in _generateImageTileRects(rect, destinationRect, repeat))
canvas.drawImageNine(image, centerSlice, tileRect, paint);
}
}
if (needSave)
canvas.restore();
}
函数比较长,只需关注重点 canvas.drawImageRect,它会调用 Canvas 的函数进行图片绘制,与 android 平台的 canvas 使用基本一致。而 canvas 也是依赖一个 layer 生成的,具体可以看 PaintContext 中 canvsd 的 get 函数:
Canvas get canvas {
if (_canvas == null)
_startRecording();
return _canvas;
}
void _startRecording() {
assert(!_isRecording);
_currentLayer = PictureLayer(estimatedBounds);
_recorder = ui.PictureRecorder();
_canvas = Canvas(_recorder);
_containerLayer.append(_currentLayer);
}
在 _startRecording 中,首先创建一个 PictureLayer,然后是 PictureRecorder、Canvas,最后将新建的 PictureLayer 加入到 containerLayer 中,作为一个 child 存在。
所以说,每一次新调用 get canvas,都会创建一个 layer,然后所有对于 canvas 的操作,最终都会存储这里创建的 PictureRecorder 中,此时 layer 与 canvas 的关系还没有体现出来。然后就要提到在前面经常看到的 stopRecordingIfNeeded 函数,
void stopRecordingIfNeeded() {
if (!_isRecording)
return;
_currentLayer.picture = _recorder.endRecording();
_currentLayer = null;
_recorder = null;
_canvas = null;
}
set picture(ui.Picture picture) {
markNeedsAddToScene();
_picture = picture;
}
这个函数与 _startRecording 是相对应的,endRecording 函数表示结束 Canvas 的绘制,并返回一个 Picture 实例,存放在 currentLayer 中,也就是在 _startRecording 中新建的 layer,而 layer 又被添加到了 containerLayer 中,从而,这里使得 canvas 的操作最终还是反映到了 layer 树中。picture 的 set 函数,调用了 markNeedsAddToScene,可以猜测,这个就是标志着 layer 的结果需要被添加到最终生成的视图中。
总结下来,flushPaint 过程的结果就是刷新了 layer 树,以 RenderView 的 layer 为根结点向下散发出去,有 ClipRectLayer 等负责承载 children 的功能性 layer,还有 PictureLayer 等负责绘制内容。
compositeFrame
这个过程就是将上一步 flushPain 生成的 layer 树传递给 GPU 进行绘制,从而能够在屏幕中看到渲染后的视图。
void compositeFrame() {
Timeline.startSync('Compositing', arguments: timelineWhitelistArguments);
try {
final ui.SceneBuilder builder = ui.SceneBuilder();
final ui.Scene scene = layer.buildScene(builder);
if (automaticSystemUiAdjustment)
_updateSystemChrome();
_window.render(scene);
scene.dispose();
} finally {
Timeline.finishSync();
}
}
首先创建了一个 SceneBuilder 实例,然后调用 layer.buildScene 将所有的 layer 装载到其中,生成 Scene 实例,最后调用 _window.render 进入 engine 层,完成最后的渲染操作。
ui.Scene buildScene(ui.SceneBuilder builder) {
List<PictureLayer> temporaryLayers;
updateSubtreeNeedsAddToScene();
addToScene(builder);
// Clearing the flag _after_ calling `addToScene`, not _before_. This is
// because `addToScene` calls children's `addToScene` methods, which may
// mark this layer as dirty.
_needsAddToScene = false;
final ui.Scene scene = builder.build();
return scene;
}
updateSubtreeNeedsAddToScene 会整合 layer 树中结点的 _needsAddToScene 值,考虑到 alwaysNeedsAddToScene 参数和子 layer 的 _needsAddToScene。
再看 addToScene,这是一个抽象函数,每一个 Layer 需要有自己的实现,在 ContainerLayer 中的实现就是调用addChildrenToScene 将所有 children 添加进去。在 ClipRectLayer 中,
void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) {
assert(clipRect != null);
assert(clipBehavior != null);
bool enabled = true;
if (enabled) {
final Rect shiftedClipRect = layerOffset == Offset.zero ? clipRect : clipRect.shift(layerOffset);
engineLayer = builder.pushClipRect(shiftedClipRect, clipBehavior: clipBehavior, oldLayer: _engineLayer);
} else {
engineLayer = null;
}
addChildrenToScene(builder, layerOffset);
if (enabled)
builder.pop();
}
ClipRectEngineLayer pushClipRect(Rect rect, {Clip clipBehavior = Clip.antiAlias, ClipRectEngineLayer oldLayer }) {
assert(clipBehavior != null);
assert(clipBehavior != Clip.none);
assert(_debugCheckCanBeUsedAsOldLayer(oldLayer, 'pushClipRect'));
final ClipRectEngineLayer layer = ClipRectEngineLayer._(_pushClipRect(rect.left, rect.right, rect.top, rect.bottom, clipBehavior.index));
assert(_debugPushLayer(layer));
return layer;
}
EngineLayer _pushClipRect(double left,
double right,
double top,
double bottom,
int clipBehavior) native 'SceneBuilder_pushClipRect';
最终还是会将 layer 拆解成一系列属性,传递给 engine 层进行绘制,而在之前的 paint 过程中,则是将一个个 RenderObject 转换成了 layer 进行保存,而在这里传递给 engine 层的时候并没有直接使用 layer 对象传值,而是依旧使用拆分之后零散的数据,从返回值来看,这些属性在 engine 中会再被组装成 layer 实例,返回的 EngineLayer 应该是指向 engine 层 layer 地址。
其他的比如 PictureLayer,也是类似的:
void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) {
builder.addPicture(layerOffset, picture, isComplexHint: isComplexHint, willChangeHint: willChangeHint);
}
void addPicture(Offset offset, Picture picture, { bool isComplexHint = false, bool willChangeHint = false }) {
int hints = 0;
if (isComplexHint)
hints |= 1;
if (willChangeHint)
hints |= 2;
_addPicture(offset.dx, offset.dy, picture, hints);
}
void _addPicture(double dx, double dy, Picture picture, int hints) native 'SceneBuilder_addPicture';
涉及到 engine 层的暂时不说,这里先大致了解这整个流程。
总结下来,compositeFrame 过程就是将之前 paint 过程生成的 layer 传递到 engine 层,然后再调用 _window.render 使用 GPU 进行渲染,完成视图的绘制。这里的 layer 可以分为两种,一种是没有内容,只是作为其他 layer 容器,这些是以 ContainerLayer 为父类,包括 ClipRectLayer、ClipRRectLayer 在内的一系列 layer,另一类是如 PictureLayer、TextureLayer、PlatformViewLayer 这些的,需要使用 Canvas 进行绘制,或者使用 texture 进行绘制的 layer,这些承载着用户真正看到的东西。
flushSemantic
这个过程是将需要更新 semantic 的结点进行更新,在前面的过程中,通过 markNeedsSemanticsUpdate 对结点进行标识,表示结点的某些信息发生了变化,比如更新 RenderOpacity 的 opacity 之后,会对比设置前后结点的可视性,当其发生变化的时候就会调用 markNeedsSemanticsUpdate。
void markNeedsSemanticsUpdate() {
assert(!attached || !owner._debugDoingSemantics);
if (!attached || owner._semanticsOwner == null) {
_cachedSemanticsConfiguration = null;
return;
}
// Dirty the semantics tree starting at `this` until we have reached a
// RenderObject that is a semantics boundary. All semantics past this
// RenderObject are still up-to date. Therefore, we will later only rebuild
// the semantics subtree starting at the identified semantics boundary.
final bool wasSemanticsBoundary = _semantics != null && _cachedSemanticsConfiguration?.isSemanticBoundary == true;
_cachedSemanticsConfiguration = null;
bool isEffectiveSemanticsBoundary = _semanticsConfiguration.isSemanticBoundary && wasSemanticsBoundary;
RenderObject node = this;
while (!isEffectiveSemanticsBoundary && node.parent is RenderObject) {
if (node != this && node._needsSemanticsUpdate)
break;
node._needsSemanticsUpdate = true;
node = node.parent;
isEffectiveSemanticsBoundary = node._semanticsConfiguration.isSemanticBoundary;
if (isEffectiveSemanticsBoundary && node._semantics == null) {
// We have reached a semantics boundary that doesn't own a semantics node.
// That means the semantics of this branch are currently blocked and will
// not appear in the semantics tree. We can abort the walk here.
return;
}
}
if (node != this && _semantics != null && _needsSemanticsUpdate) {
// If `this` node has already been added to [owner._nodesNeedingSemantics]
// remove it as it is no longer guaranteed that its semantics
// node will continue to be in the tree. If it still is in the tree, the
// ancestor `node` added to [owner._nodesNeedingSemantics] at the end of
// this block will ensure that the semantics of `this` node actually gets
// updated.
// (See semantics_10_test.dart for an example why this is required).
owner._nodesNeedingSemantics.remove(this);
}
if (!node._needsSemanticsUpdate) {
node._needsSemanticsUpdate = true;
if (owner != null) {
assert(node._semanticsConfiguration.isSemanticBoundary || node.parent is! RenderObject);
owner._nodesNeedingSemantics.add(node);
owner.requestVisualUpdate();
}
}
}
在 markNeedsSemanticsUpdate 中可以看到,在标识一个结点为 _needsSemanticsUpdate 时,还会向上标识,最终只会将最上层的结点加入到 _nodesNeedingSemantics 中,它的所有子结点只会被标记为 _needsSemanticsUpdate。
在 PipelineOwner 中,每一个结点调用 _updateSemantics 进行更新,同时还会统计数据发生变化的结点,最后调用 sendSemanticsUpdate 统一更新。
void flushSemantics() {
if (_semanticsOwner == null)
return;
if (!kReleaseMode) {
Timeline.startSync('Semantics');
}
assert(_semanticsOwner != null);
assert(() { _debugDoingSemantics = true; return true; }());
try {
final List<RenderObject> nodesToProcess = _nodesNeedingSemantics.toList()
..sort((RenderObject a, RenderObject b) => a.depth - b.depth);
_nodesNeedingSemantics.clear();
for (RenderObject node in nodesToProcess) {
if (node._needsSemanticsUpdate && node.owner == this)
node._updateSemantics();
}
_semanticsOwner.sendSemanticsUpdate();
} finally {
assert(_nodesNeedingSemantics.isEmpty);
assert(() { _debugDoingSemantics = false; return true; }());
if (!kReleaseMode) {
Timeline.finishSync();
}
}
}
首先看 _updateSemantics 步骤,
void _updateSemantics() {
assert(_semanticsConfiguration.isSemanticBoundary || parent is! RenderObject);
if (_needsLayout) {
// There's not enough information in this subtree to compute semantics.
// The subtree is probably being kept alive by a viewport but not laid out.
return;
}
final _SemanticsFragment fragment = _getSemanticsForParent(
mergeIntoParent: _semantics?.parent?.isPartOfNodeMerging ?? false,
);
assert(fragment is _InterestingSemanticsFragment);
final _InterestingSemanticsFragment interestingFragment = fragment;
final SemanticsNode node = interestingFragment.compileChildren(
parentSemanticsClipRect: _semantics?.parentSemanticsClipRect,
parentPaintClipRect: _semantics?.parentPaintClipRect,
elevationAdjustment: _semantics?.elevationAdjustment ?? 0.0,
).single;
// Fragment only wants to add this node's SemanticsNode to the parent.
assert(interestingFragment.config == null && node == _semantics);
}
这个部分也分为两部分,首先调用 _getSemanticsForParent 生成 _SemanticsFragment,然后调用 compileChildren 生成 SemanticsNode,且在这个过程中会统计出需要更新的结点,在 sendSemanticsUpdate 阶段将它的数据发送出去。
_getSemanticsForParent
这个函数主要用于生成 _SemanticsFragment,这是一个递归的过程,在生成每一个 RenderObject 的 _SemanticsFragment 之前,先生成所有子结点的 _SemanticsFragment,然后再将这些子结点的 _SemanticsFragment 整合到当前结点的 _SemanticsFragment 中,整合的过程就是一个合并的过程,可以具体来看:
_SemanticsFragment _getSemanticsForParent({
@required bool mergeIntoParent,
}) {
assert(mergeIntoParent != null);
assert(!_needsLayout, 'Updated layout information required for $this to calculate semantics.');
final SemanticsConfiguration config = _semanticsConfiguration;
bool dropSemanticsOfPreviousSiblings = config.isBlockingSemanticsOfPreviouslyPaintedNodes;
final bool producesForkingFragment = !config.hasBeenAnnotated && !config.isSemanticBoundary;
final List<_InterestingSemanticsFragment> fragments = <_InterestingSemanticsFragment>[];
final Set<_InterestingSemanticsFragment> toBeMarkedExplicit = <_InterestingSemanticsFragment>{};
final bool childrenMergeIntoParent = mergeIntoParent || config.isMergingSemanticsOfDescendants;
// When set to true there's currently not enough information in this subtree
// to compute semantics. In this case the walk needs to be aborted and no
// SemanticsNodes in the subtree should be updated.
// This will be true for subtrees that are currently kept alive by a
// viewport but not laid out.
bool abortWalk = false;
visitChildrenForSemantics((RenderObject renderChild) {
if (abortWalk || _needsLayout) {
abortWalk = true;
return;
}
final _SemanticsFragment parentFragment = renderChild._getSemanticsForParent(
mergeIntoParent: childrenMergeIntoParent,
);
if (parentFragment.abortsWalk) {
abortWalk = true;
return;
}
if (parentFragment.dropsSemanticsOfPreviousSiblings) {
fragments.clear();
toBeMarkedExplicit.clear();
if (!config.isSemanticBoundary)
dropSemanticsOfPreviousSiblings = true;
}
// Figure out which child fragments are to be made explicit.
for (_InterestingSemanticsFragment fragment in parentFragment.interestingFragments) {
fragments.add(fragment);
fragment.addAncestor(this);
fragment.addTags(config.tagsForChildren);
if (config.explicitChildNodes || parent is! RenderObject) {
fragment.markAsExplicit();
continue;
}
if (!fragment.hasConfigForParent || producesForkingFragment)
continue;
if (!config.isCompatibleWith(fragment.config))
toBeMarkedExplicit.add(fragment);
for (_InterestingSemanticsFragment siblingFragment in fragments.sublist(0, fragments.length - 1)) {
if (!fragment.config.isCompatibleWith(siblingFragment.config)) {
toBeMarkedExplicit.add(fragment);
toBeMarkedExplicit.add(siblingFragment);
}
}
}
});
if (abortWalk) {
return _AbortingSemanticsFragment(owner: this);
}
for (_InterestingSemanticsFragment fragment in toBeMarkedExplicit)
fragment.markAsExplicit();
_needsSemanticsUpdate = false;
_SemanticsFragment result;
if (parent is! RenderObject) {
assert(!config.hasBeenAnnotated);
assert(!mergeIntoParent);
result = _RootSemanticsFragment(
owner: this,
dropsSemanticsOfPreviousSiblings: dropSemanticsOfPreviousSiblings,
);
} else if (producesForkingFragment) {
result = _ContainerSemanticsFragment(
dropsSemanticsOfPreviousSiblings: dropSemanticsOfPreviousSiblings,
);
} else {
result = _SwitchableSemanticsFragment(
config: config,
mergeIntoParent: mergeIntoParent,
owner: this,
dropsSemanticsOfPreviousSiblings: dropSemanticsOfPreviousSiblings,
);
if (config.isSemanticBoundary) {
final _SwitchableSemanticsFragment fragment = result;
fragment.markAsExplicit();
}
}
result.addAll(fragments);
return result;
}
如上,首先会得到当前结点的 SemanticsConfiguration,这个类基本就包含了当前结点的所有信息,后面的所有操作也几乎都是围绕着整合这个类来做。
SemanticsConfiguration get _semanticsConfiguration {
if (_cachedSemanticsConfiguration == null) {
_cachedSemanticsConfiguration = SemanticsConfiguration();
describeSemanticsConfiguration(_cachedSemanticsConfiguration);
}
return _cachedSemanticsConfiguration;
}
void describeSemanticsConfiguration(SemanticsConfiguration config) {
// Nothing to do by default.
}
这个变量的 get 函数,会先判断是否有缓存的 SemanticsConfiguration,没有的话就创建一个新的 SemanticsConfiguration 并调用 describeSemanticsConfiguration 填充数据,然后存在 _cachedSemanticsConfiguration 中,且这个缓存在 RenderObject 被标记需要更新 semantic 的时候是要先置空的,从而保证它只是缓存而不是过时数据。
describeSemanticsConfiguration 由 RenderObject 子类实现,主要就是把自己的数据存在 SemanticsConfiguration 中,以 RenderSlider 为例:
void describeSemanticsConfiguration(SemanticsConfiguration config) {
super.describeSemanticsConfiguration(config);
config.isSemanticBoundary = isInteractive;
if (isInteractive) {
config.textDirection = textDirection;
config.onIncrease = _increaseAction;
config.onDecrease = _decreaseAction;
if (semanticFormatterCallback != null) {
config.value = semanticFormatterCallback(_state._lerp(value));
config.increasedValue = semanticFormatterCallback(_state._lerp((value + _semanticActionUnit).clamp(0.0, 1.0)));
config.decreasedValue = semanticFormatterCallback(_state._lerp((value - _semanticActionUnit).clamp(0.0, 1.0)));
} else {
config.value = '${(value * 100).round()}%';
config.increasedValue = '${((value + _semanticActionUnit).clamp(0.0, 1.0) * 100).round()}%';
config.decreasedValue = '${((value - _semanticActionUnit).clamp(0.0, 1.0) * 100).round()}%';
}
}
}
它赋值了 SemanticsConfiguration 的 isSemanticBoundary、textDirection、onIncrease 等数据。
在取得 SemanticsConfiguration 之后,又基于 SemanticsConfiguration 的数据进行了一些判断,然后就是对所有 children 进行递归遍历,对于单个 child 而言,先生成这个结点的 _SemanticsFragment,然后进行一些判断之类的,与当前结点先建立联系,最后,遍历完整个 children,将它们的 fragment 都加到当前结点的 _SemanticsFragment 中,不同的 _SemanticsFragment 有不同的实现,分为两大阵营,一类是 _ContainerSemanticsFragment,一类是 _InterestingSemanticsFragment,前者会将子结点的 fragment 都加入到 interestingFragments 中,后者则是将其加入到 _children 变量中,这里的区别在于 _ContainerSemanticsFragment 只能算是一个中间者,再继续向上传递 _SemanticsFragment 的时候,_ContainerSemanticsFragment 还是会将它内部的所有 interestingFragments 都提供给自己的上层 _SemanticsFragment,但 _InterestingSemanticsFragment 只会将自己提供出去,同时 SemanticsNode 也只能由 _InterestingSemanticsFragment 生成。
所以再反观函数名 _getSemanticsForParent,倒也贴切,虽然每一个函数最终得到的都是当前结点的 _SemanticsFragment,但它们最终都是(除了最上层的)需要将这个 _SemanticsFragment 提供给上层使用的。
compileChildren
完了之后回到 _updateSemantics,下一步就是调用前面生成的 _InterestingSemanticsFragment compileChildren 函数,汇编子结点,生成最终的 SemanticsNode。
参与这一步骤的只有 _RootSemanticsFragment 和 _SwitchableSemanticsFragment 两者,前者毋庸置疑,就是作为一个 _SemanticsFragment 根节点的存在,所以它肯定是能够生成一个 SemanticsNode 的,而 _SwitchableSemanticsFragment 从名字来看可以知道它是一个可以从“结点”和“中间者”之间进行切换的,而决定它的类型的就是 _isExplicit 变量,这个变量是在 _getSemanticsForParent 阶段确定的,保存在 toBeMarkedExplicit 中,所有 SemanticsConfiguration 不能与父结点、兄弟结点进行兼容的 _SemanticsFragment 都会被标记成 explicit,因为在 compileChildren 阶段,本质上就是将父结点与子结点的 SemanticsConfiguration 合并成 SemanticsNode 树的过程,所以这里先要将不能相容的都分开,而相容的本质,就是两个 SemanticsConfiguration 的数据不能发生冲突,具体可以看函数的实现:
bool isCompatibleWith(SemanticsConfiguration other) {
if (other == null || !other.hasBeenAnnotated || !hasBeenAnnotated)
return true;
if (_actionsAsBits & other._actionsAsBits != 0)
return false;
if ((_flags & other._flags) != 0)
return false;
if (_platformViewId != null && other._platformViewId != null) {
return false;
}
if (_value != null && _value.isNotEmpty && other._value != null && other._value.isNotEmpty)
return false;
return true;
}
所以能够进行合并的要求是不能导致有数据丢失,否则就需要分成两个 SemanticsNode。所以对于 _SwitchableSemanticsFragment 而言,它的 compileChildren 有两种实现:
Iterable<SemanticsNode> compileChildren({ Rect parentSemanticsClipRect, Rect parentPaintClipRect, double elevationAdjustment }) sync* {
if (!_isExplicit) {
owner._semantics = null;
for (_InterestingSemanticsFragment fragment in _children) {
assert(_ancestorChain.first == fragment._ancestorChain.last);
fragment._ancestorChain.addAll(_ancestorChain.sublist(1));
yield* fragment.compileChildren(
parentSemanticsClipRect: parentSemanticsClipRect,
parentPaintClipRect: parentPaintClipRect,
// The fragment is not explicit, its elevation has been absorbed by
// the parent config (as thickness). We still need to make sure that
// its children are placed at the elevation dictated by this config.
elevationAdjustment: elevationAdjustment + _config.elevation,
);
}
return;
}
final _SemanticsGeometry geometry = _needsGeometryUpdate
? _SemanticsGeometry(parentSemanticsClipRect: parentSemanticsClipRect, parentPaintClipRect: parentPaintClipRect, ancestors: _ancestorChain)
: null;
if (!_mergeIntoParent && (geometry?.dropFromTree == true))
return; // Drop the node, it's not going to be visible.
owner._semantics ??= SemanticsNode(showOnScreen: owner.showOnScreen);
final SemanticsNode node = owner._semantics
..isMergedIntoParent = _mergeIntoParent
..tags = _tagsForChildren;
node.elevationAdjustment = elevationAdjustment;
if (elevationAdjustment != 0.0) {
_ensureConfigIsWritable();
_config.elevation += elevationAdjustment;
}
if (geometry != null) {
assert(_needsGeometryUpdate);
node
..rect = geometry.rect
..transform = geometry.transform
..parentSemanticsClipRect = geometry.semanticsClipRect
..parentPaintClipRect = geometry.paintClipRect;
if (!_mergeIntoParent && geometry.markAsHidden) {
_ensureConfigIsWritable();
_config.isHidden = true;
}
}
final List<SemanticsNode> children = _children
.expand((_InterestingSemanticsFragment fragment) => fragment.compileChildren(
parentSemanticsClipRect: node.parentSemanticsClipRect,
parentPaintClipRect: node.parentPaintClipRect,
elevationAdjustment: 0.0,
))
.toList();
if (_config.isSemanticBoundary) {
owner.assembleSemanticsNode(node, _config, children);
} else {
node.updateWith(config: _config, childrenInInversePaintOrder: children);
}
yield node;
}
如果没有被标记 explicit,那它的 SemanticsConfiguration 最终还是要被父结点吸收,所以直接将自己的每一个子结点的 compileChildren 结果返回,而如果自己不能与父结点相容,就要执行下面的操作,先将子结点的 SemanticsNode 都生成,再将它们合并到自己单独创建的一个 SemanticsNode 中返回,_RootSemanticsFragment 的 compileChildren 也就是与下面的这个类似。
合并 SemanticsNode 的函数为 updateWith,
void updateWith({
@required SemanticsConfiguration config,
List<SemanticsNode> childrenInInversePaintOrder,
}) {
config ??= _kEmptyConfig;
if (_isDifferentFromCurrentSemanticAnnotation(config))
_markDirty();
_label = config.label;
_decreasedValue = config.decreasedValue;
_value = config.value;
_increasedValue = config.increasedValue;
_hint = config.hint;
_hintOverrides = config.hintOverrides;
_elevation = config.elevation;
_thickness = config.thickness;
_flags = config._flags;
_textDirection = config.textDirection;
_sortKey = config.sortKey;
_actions = Map<SemanticsAction, _SemanticsActionHandler>.from(config._actions);
_customSemanticsActions = Map<CustomSemanticsAction, VoidCallback>.from(config._customSemanticsActions);
_actionsAsBits = config._actionsAsBits;
_textSelection = config._textSelection;
_isMultiline = config.isMultiline;
_scrollPosition = config._scrollPosition;
_scrollExtentMax = config._scrollExtentMax;
_scrollExtentMin = config._scrollExtentMin;
_mergeAllDescendantsIntoThisNode = config.isMergingSemanticsOfDescendants;
_scrollChildCount = config.scrollChildCount;
_scrollIndex = config.scrollIndex;
indexInParent = config.indexInParent;
_platformViewId = config._platformViewId;
_replaceChildren(childrenInInversePaintOrder ?? const <SemanticsNode>[]);
}
_isDifferentFromCurrentSemanticAnnotation 将新的 config 与原先的对比,如果有不同就标记为脏结点,然后将这些新值给拷贝过来,最后调用 _replaceChildren 重建树。
void _replaceChildren(List<SemanticsNode> newChildren) {
// The goal of this function is updating sawChange.
if (_children != null) {
for (SemanticsNode child in _children)
child._dead = true;
}
if (newChildren != null) {
for (SemanticsNode child in newChildren) {
assert(!child.isInvisible, 'Child $child is invisible and should not be added as a child of $this.');
child._dead = false;
}
}
bool sawChange = false;
if (_children != null) {
for (SemanticsNode child in _children) {
if (child._dead) {
if (child.parent == this) {
// we might have already had our child stolen from us by
// another node that is deeper in the tree.
dropChild(child);
}
sawChange = true;
}
}
}
if (newChildren != null) {
for (SemanticsNode child in newChildren) {
if (child.parent != this) {
if (child.parent != null) {
// we're rebuilding the tree from the bottom up, so it's possible
// that our child was, in the last pass, a child of one of our
// ancestors. In that case, we drop the child eagerly here.
// TODO(ianh): Find a way to assert that the same node didn't
// actually appear in the tree in two places.
child.parent?.dropChild(child);
}
assert(!child.attached);
adoptChild(child);
sawChange = true;
}
}
}
if (!sawChange && _children != null) {
assert(newChildren != null);
assert(newChildren.length == _children.length);
// Did the order change?
for (int i = 0; i < _children.length; i++) {
if (_children[i].id != newChildren[i].id) {
sawChange = true;
break;
}
}
}
_children = newChildren;
if (sawChange)
_markDirty();
}
以上步骤完成了将旧的子结点移除,新的子结点加载上去的操作,由于考虑了二者之间会有重复的部分,所以整个过程经历了四次遍历。_markDirty 负责将结点标记为脏结点,并将其加入到 _dirtyNodes 中:
void _markDirty() {
if (_dirty)
return;
_dirty = true;
if (attached) {
assert(!owner._detachedNodes.contains(this));
owner._dirtyNodes.add(this);
}
}
总结上面的过程,这里有着两次转换,第一次在 _getSemanticsForParent 阶段,将 RenderObject 树转换成了 _SemanticsFragment 树,这个过程会丢失一部分的结点,因为中间生成的 _ContainerSemanticsFragment 不会作为 _SemanticsFragment 树中的一员,而是直接将自己的所有子结点提供作为自己父类的子结点,然后自己在递归完成之后便消失了。然后,在 compileChildren 阶段,将 _SemanticsFragment 树转换成了 SemanticsNode 树,在这个过程中还会丢失一部分结点,就是没有被标记为 explicit 的,它们的功能也与 _ContainerSemanticsFragment 类似,在生成 SemanticsNode 树的过程中自己不会产生一个对应的 SemanticsNode,而是直接讲自己子结点生成的 SemanticsNode 都直接提供给父结点,作为父结点的子结点。
sendSemanticsUpdate
这个过程相对来说比较简单,首先收集所有需要更新的 SemanticsNode,这中间会根据脏结点的 isPartOfNodeMerging 变量判断是否将父结点也标记为脏结点。
然后创建 SemanticsUpdateBuilder 实例,然后调用所有脏结点 _addToUpdate 函数,一方面将自己的数据存在 SemanticsUpdateBuilder 中,另一方面收集 customSemanticsActionIds,当然这个数据在最后还是要加入到 SemanticsUpdateBuilder 中,之所以这里单独存放,而不是在 _addToUpdate 中直接调用 updateCustomAction,从 customSemanticsActionIds 的数据结构为 Set 就可以推测,actionId 在整个 SemanticsNode 树中应该是存在重复使用的,所以通过这一步进行去重。
void sendSemanticsUpdate() {
if (_dirtyNodes.isEmpty)
return;
final Set<int> customSemanticsActionIds = <int>{};
final List<SemanticsNode> visitedNodes = <SemanticsNode>[];
while (_dirtyNodes.isNotEmpty) {
final List<SemanticsNode> localDirtyNodes = _dirtyNodes.where((SemanticsNode node) => !_detachedNodes.contains(node)).toList();
_dirtyNodes.clear();
_detachedNodes.clear();
localDirtyNodes.sort((SemanticsNode a, SemanticsNode b) => a.depth - b.depth);
visitedNodes.addAll(localDirtyNodes);
for (SemanticsNode node in localDirtyNodes) {
if (node.isPartOfNodeMerging) {
// if we're merged into our parent, make sure our parent is added to the dirty list
if (node.parent != null && node.parent.isPartOfNodeMerging)
node.parent._markDirty(); // this can add the node to the dirty list
}
}
}
visitedNodes.sort((SemanticsNode a, SemanticsNode b) => a.depth - b.depth);
final ui.SemanticsUpdateBuilder builder = ui.SemanticsUpdateBuilder();
for (SemanticsNode node in visitedNodes) {
if (node._dirty && node.attached)
node._addToUpdate(builder, customSemanticsActionIds);
}
_dirtyNodes.clear();
for (int actionId in customSemanticsActionIds) {
final CustomSemanticsAction action = CustomSemanticsAction.getAction(actionId);
builder.updateCustomAction(id: actionId, label: action.label, hint: action.hint, overrideId: action.action?.index ?? -1);
}
SemanticsBinding.instance.window.updateSemantics(builder.build());
notifyListeners();
}
然后在 _addToUpdate 中,SemanticsNode 中的数据会先被提取出来,构成 SemanticsData,这里就要说一下 SemanticsData 和 SemanticsConfiguration 的区别,关于这个数据,它的传递流程是这样的:RenderObject -> SemanticsConfiguration -> SemanticsNode -> SemanticsData,总共经历了三次转换,在第一次转换中,是将 RenderObject 中一些零散的属性整合在了一起,形成了 SemanticsConfiguration,而 SemanticsConfiguration 主要的作用还是用于生成 _SemanticsFragment,在转换到 SemanticsNode 的过程中,则是将 SemanticsConfiguration 进行筛检,只保留了一些必要的属性,但是第三次转换成的 SemanticsData,感觉就没有之前两次这么有必要,第一点,SemanticsData 只是对这些数据进行了保存,而没有额外的处理,从这一点看,SemanticsData 是可有可无的,第二点,既然使用的 SemanticsData,却总感觉使用的不彻底,为什么没有直接在 SemanticsNode 中使用 SemanticsData 保存这些数据,也避免了这么多的变量,所以 SemanticsData 位于一个比较尴尬的位置,而且,得到的 SemanticsData 在调用 builder.updateNode 的时候,还是一个一个传递 SemanticsData 变量,这也进一步降低 SemanticsData 存在的必要性。
_addToUpdate 执行完后,builder.build 生成一个 SemanticsUpdate 实例,然后调用 SemanticsBinding.instance.window.updateSemantics 进入 engine 层,最终传递到 native 层中去。
以上就是 onDraw 回调的全过程,相对来说,前四步都是紧密相关的,首先布局 RenderObject,然后确定需要绘制的结点,再进行绘制,这一步主要还是记录绘制操作,生成 layer 树,最后将这些 layer 的绘制传递到底层,由 GPU 完成最终的绘制操作。最后一步有些脱节,感觉并不属于 RendererBinding 中的内容,本应该放在 SemanticsBinding 实现才对,但是也有可能这一步跟 RenderObject 树联系比较密切吧,总之就是把当前整个 RenderObject 树的某些信息整合之后的结果,具体干什么用,我现在并不知道。另外,这个过程也是可以通过 _handleSemanticsEnabledChanged 函数里面关闭。
创建 MouseTracker
最后,在 RendererBinding 中还创建了一个 MouseTracker:
MouseTracker _createMouseTracker() {
return MouseTracker(pointerRouter, renderView.hitTestMouseTrackers);
}
MouseTracker 需要两个参数,PointerRouter 和 hitTestMouseTrackers,所以这也是一个用于事件分发的类,而且是专门用于处理鼠标的事件,PointerRouter 是 MouseTracker 监听鼠标事件的入口,主要体现在调用 addGlobalRoute 添加了一个回调。hitTestMouseTrackers 也类似于 hitTest 函数,只不过这里专注于找到所有能够接收到鼠标事件的 MouseTrackerAnnotation。
Iterable<MouseTrackerAnnotation> hitTestMouseTrackers(Offset position) {
// Layer hit testing is done using device pixels, so we have to convert
// the logical coordinates of the event location back to device pixels
// here.
return layer.findAll<MouseTrackerAnnotation>(position * configuration.devicePixelRatio);
}
从实现来看,它直接从 RenderView 的 layer 中去寻找,但是 MouseTrackerAnnotation 还是在 RenderObject 的子类 RenderMouseRegion 中创建的,再往上追溯,RenderMouseRegion 是由 MouseRegion 创建的,这是一个用于监听鼠标事件的 widget,反过来,MouseRegion 创建 RenderMouseRegion,RenderMouseRegion 中创建 MouseTrackerAnnotation,并将其传递给 AnnotatedRegionLayer,也即是 layerTree 的一员,在 layer.findAll 中便可以从 AnnotatedRegionLayer 找出 MouseTrackerAnnotation。
再看 MouseTracker 构造函数:
MouseTracker(PointerRouter router, this.annotationFinder)
: assert(router != null),
assert(annotationFinder != null) {
router.addGlobalRoute(_handleEvent);
}
PointerRouter 在 GestureBinding 中讲过,用于事件的分发,当新的事件到来时,会传递到所有的 route 中,在这里就是调用 _handleEvent 函数。
void _handleEvent(PointerEvent event) {
if (event.kind != PointerDeviceKind.mouse) {
return;
}
final int deviceId = event.device;
if (event is PointerAddedEvent) {
// If we are adding the device again, then we're not removing it anymore.
_pendingRemovals.remove(deviceId);
_addMouseEvent(deviceId, event);
return;
}
if (event is PointerRemovedEvent) {
_removeMouseEvent(deviceId, event);
// If the mouse was removed, then we need to schedule one more check to
// exit any annotations that were active.
_scheduleMousePositionCheck();
} else {
if (event is PointerMoveEvent || event is PointerHoverEvent || event is PointerDownEvent) {
if (!_lastMouseEvent.containsKey(deviceId) || _lastMouseEvent[deviceId].position != event.position) {
// Only schedule a frame if we have our first event, or if the
// location of the mouse has changed, and only if there are tracked annotations.
_scheduleMousePositionCheck();
}
_addMouseEvent(deviceId, event);
}
}
}
整体看下来也很简单,首先排除出了鼠标事件以外的,然后就 PointerAddedEvent 和 PointerRemovedEvent 分别做添加/删除操作,注意,这里并没有直接调用各接受者的对应回调,而是先把这些数据保存下来,_pendingRemovals 保存已经移除的 deviceId,_lastMouseEvent 保存每一个 deviceId 对应的最新的事件,然后在 _scheduleMousePositionCheck 中,它会将下一步的处理函数放在 postFrameCallback 中去执行,这个函数在 SchedulerBinding 中有讲,这里的考量可能是为了不妨碍 UI 的渲染。
具体的处理操作在 collectMousePositions 中,
void collectMousePositions() {
void exitAnnotation(_TrackedAnnotation trackedAnnotation, int deviceId) {
if (trackedAnnotation.annotation?.onExit != null && trackedAnnotation.activeDevices.contains(deviceId)) {
final PointerEvent event = _lastMouseEvent[deviceId] ?? _pendingRemovals[deviceId];
assert(event != null);
trackedAnnotation.annotation.onExit(PointerExitEvent.fromMouseEvent(event));
trackedAnnotation.activeDevices.remove(deviceId);
}
}
void exitAllDevices(_TrackedAnnotation trackedAnnotation) {
if (trackedAnnotation.activeDevices.isNotEmpty) {
final Set<int> deviceIds = trackedAnnotation.activeDevices.toSet();
for (int deviceId in deviceIds) {
exitAnnotation(trackedAnnotation, deviceId);
}
}
}
try {
// This indicates that all mouse pointers were removed, or none have been
// connected yet. If no mouse is connected, then we want to make sure that
// all active annotations are exited.
if (!mouseIsConnected) {
_trackedAnnotations.values.forEach(exitAllDevices);
return;
}
for (int deviceId in _lastMouseEvent.keys) {
final PointerEvent lastEvent = _lastMouseEvent[deviceId];
final Iterable<MouseTrackerAnnotation> hits = annotationFinder(lastEvent.position);
// No annotations were found at this position for this deviceId, so send an
// exit to all active tracked annotations, since none of them were hit.
if (hits.isEmpty) {
// Send an exit to all tracked animations tracking this deviceId.
for (_TrackedAnnotation trackedAnnotation in _trackedAnnotations.values) {
exitAnnotation(trackedAnnotation, deviceId);
}
continue;
}
final Set<_TrackedAnnotation> hitAnnotations = hits.map<_TrackedAnnotation>((MouseTrackerAnnotation hit) => _findAnnotation(hit)).toSet();
for (_TrackedAnnotation hitAnnotation in hitAnnotations) {
if (!hitAnnotation.activeDevices.contains(deviceId)) {
// A tracked annotation that just became active and needs to have an enter
// event sent to it.
hitAnnotation.activeDevices.add(deviceId);
if (hitAnnotation.annotation?.onEnter != null) {
hitAnnotation.annotation.onEnter(PointerEnterEvent.fromMouseEvent(lastEvent));
}
}
if (hitAnnotation.annotation?.onHover != null && lastEvent is PointerHoverEvent) {
hitAnnotation.annotation.onHover(lastEvent);
}
// Tell any tracked annotations that weren't hit that they are no longer
// active.
for (_TrackedAnnotation trackedAnnotation in _trackedAnnotations.values) {
if (hitAnnotations.contains(trackedAnnotation)) {
continue;
}
if (trackedAnnotation.activeDevices.contains(deviceId)) {
if (trackedAnnotation.annotation?.onExit != null) {
trackedAnnotation.annotation.onExit(PointerExitEvent.fromMouseEvent(lastEvent));
}
trackedAnnotation.activeDevices.remove(deviceId);
}
}
}
}
} finally {
_pendingRemovals.clear();
}
}
简单来说就是,它会先判断下当前是否又鼠标设备连接,没有的话就调用事先定义的 exitAllDevices 函数,然后遍历所有 device 事件,通过 annotationFinder 找出所有能够处理这个事件的 MouseTrackerAnnotation,然后与所有已经 attached 的 _trackedAnnotations 进行比较(_TrackedAnnotation 可以看作是所有 MouseTrackerAnnotation 在 MouseTracker 中的备份,它除了对应一个 MouseTrackerAnnotation,还保存了活跃的 deviceId,以此为依据来判断 onEnter 和 onExit 的执行),有这么几个分支:
- _TrackedAnnotation activeDevicies 中不存在但是本次接收到某一个 deviceId 的事件,调用 onEnter
- _TrackedAnnotation activeDevicies 中存在,但本次没有接收到这个 deviceId 的事件,调用 onExit
- _TrackedAnnotation activeDevicies 中存在且接收到这个 deviceId 的事件,且时间类型是 PointerHoverEvent,调用 onHover
- 其他情况无需处理
以上就是 MouseTracker 的整个处理逻辑,还有一点,_TrackedAnnotation 是在 attachAnnotation 函数中生成,这个函数一般是 RenderMouseRegion 进行 attach 和鼠标连接状态切换时进行调用,还有一个对应的函数 detachAnnotation。
其实单从功能上来讲,MouseTracker 我认为更应该与其他手势检测一样,放在 GestureBinding 中实现更合理,但是从上面来看,MouseTracker 的实现较多了依赖了 RenderObject 体系的东西,比如 RenderView 的 hitTestMouseTrackers 函数,且 MouseTracker 中依赖的 MouseTrackerAnnotation 也是在 RenderMouseRegion 中创建的,可能正是这些东西使得 MouseTracker 放在了 RendererBinding 中去做。但是再往前看,GestureBinding 也是依赖 RenderObject 实现的,它则是使用了在 RendererBinding 中重写 hitTest 来联系到 RenderObject 树的,所以其实按理说,MouseTracker 也应该使用这种方式实现,放在 GestureBinding 中,这样就与手势识别相关功能的统一起来,这也应该是更合理的方式。
最后,以上就是 RendererBinding 中的全部功能,总体来看这是一个非常庞大的体系,其实这里面专属于 render 相关的其实也不是很多,主要就是包括 RenderView 和 PipelineOwner 在内的一些关于绘制的处理,也就是 layout、paint、composite 这几个阶段,但是造成 RendererBinding 庞大的原因,应该还是把一些本不必要的东西都放到了这边来实现,比如跟渲染没有太大关系的 semantic 体系,再比如应该在我看来应该放在 GestureBinding 中的 MouseTracker,从这一点来看,我认为这是 flutter 区分的不够好的地方,还有待进一步改善吧。另外,关于渲染这一部分真正复杂的应该是在 engine 层,那里也有 layer 合成、光栅化、绘制等更完善的过程,dart 中的 paint 本质上还只是将渲染的操作保存了下来,后续的还是要传到 engine 层,再传到更底层才能完成渲染。
再回顾关于视图渲染的逻辑,从最开始将 Element 标记成脏结点(这一步是在 WidgetsBinding 中做的),然后生成 RenderObject 树,于是开始了 layout、paint 等操作,随处可见的 markNeedXXXX,为 drawFrame 中的分步处理奠定基调,一步步向前,最终确定需要合成的 layerTree,这个过程也算是一种优化,细致地确定了需要被处理的结点而避免重复,在 flutter 这样一个跨平台的框架中更显重要。