Flutter开发进阶之瞧瞧RenderObject
通过上回我们了解到Flutter执行build
Tree的逻辑线,当Tree构建完成后会交给Flutter底层的渲染事件循环去执行将内容渲染到屏幕的操作。
但是渲染的操作到底是如何串起来的呢?这篇文章将会从Element
联系到RenderObject
去瞧瞧逻辑线形成闭环。
在Element的源码中有一个方法,如下。
/*
### Rebuilding
Dirty elements are rebuilt during the next frame. Precisely how this is
done depends on the kind of element. A [StatelessElement] rebuilds by
using its widget's [StatelessWidget.build] method. A [StatefulElement]
rebuilds by using its widget's state's [State.build] method. A
[RenderObjectElement] rebuilds by updating its [RenderObject].
In many cases, the end result of rebuilding is a single child widget
or (for [MultiChildRenderObjectElement]s) a list of children widgets.
These child widgets are used to update the [widget] property of the
element's child (or children) elements. The new [Widget] is considered to
correspond to an existing [Element] if it has the same [Type] and [Key].
(In the case of [MultiChildRenderObjectElement]s, some effort is put into
tracking widgets even when they change order; see
[RenderObjectElement.updateChildren].)
*/
('vm:prefer-inline')
void rebuild({bool force = false})
我们通过注释可知,Element在构建好Tree后,Dirty elements
在下一帧重建,无论是多子Element还是单子Element都是通过RenderObjectElement
更新它的RenderObject
完成。
让我们来看看RenderObjectElement
的mount
方法,以下。
void mount(Element? parent, Object? newSlot) {
super.mount(parent, newSlot);
assert(() {
_debugDoingBuild = true;
return true;
}());
_renderObject = (widget as RenderObjectWidget).createRenderObject(this);
assert(!_renderObject!.debugDisposed!);
assert(() {
_debugDoingBuild = false;
return true;
}());
assert(() {
_debugUpdateRenderObjectOwner();
return true;
}());
assert(_slot == newSlot);
attachRenderObject(newSlot);
super.performRebuild(); // clears the "dirty" flag
}
void attachRenderObject(Object? newSlot) {
assert(_ancestorRenderObjectElement == null);
_slot = newSlot;
_ancestorRenderObjectElement = _findAncestorRenderObjectElement();
_ancestorRenderObjectElement?.insertRenderObjectChild(renderObject, newSlot);
final ParentDataElement<ParentData>? parentDataElement = _findAncestorParentDataElement();
if (parentDataElement != null) {
_updateParentData(parentDataElement.widget as ParentDataWidget<ParentData>);
}
}
在RenderObjectElement
装载RenderObject
的过程中,会创建一个RenderObject
与其对应,然后将RenderObject
插入对应的卡槽(Slot
),并且还需保证子RenderObject
遵循上级Widget
的相关配置。
根据事件线的逻辑,接下来将看到RenderObject
的逻辑,源码代码太长就不一一贴了。
abstract class RenderObject
with DiagnosticableTreeMixin
implements HitTestTarget
RenderObject
不仅负责Layout、Paint
还需要负责hit
,这里我们只考虑Layout和Paint
。
让我们看看markNeedsLayout
,以下。
void markNeedsLayout() {
assert(_debugCanPerformMutations);
if (_needsLayout) {
assert(_debugSubtreeRelayoutRootAlreadyMarkedNeedsLayout());
return;
}
if (_relayoutBoundary == null) {
_needsLayout = true;
if (parent != null) {
// _relayoutBoundary is cleaned by an ancestor in RenderObject.layout.
// Conservatively mark everything dirty until it reaches the closest
// known relayout boundary.
markParentNeedsLayout();
}
return;
}
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();
}
}
}
可以看到渲染的逻辑线是推迟到parent
的,当合成的Layout
需要执行渲染时,会交给owner
并添加进需要Layout
的集合里,然后通过owner!.requestVisualUpdate();
去通知渲染管线进行渲染。
让我们看看源码中怎么说,以下。
/// The owner for this render object (null if unattached).
///
/// The entire render tree that this render object belongs to
/// will have the same owner.
PipelineOwner? get owner => _owner;
PipelineOwner? _owner;
可知在同一个Tree以下都对应同一个PipelineOwner
,这个owner负责管理具体的渲染工作,相当于前文说到的Element
与BuildOwner
的关系。
接下来来我们看看markNeedsPaint
方法,以下。
void markNeedsPaint() {
assert(!_debugDisposed);
assert(owner == null || !owner!.debugDoingPaint);
if (_needsPaint) {
return;
}
_needsPaint = true;
// If this was not previously a repaint boundary it will not have
// a layer we can paint from.
if (isRepaintBoundary && _wasRepaintBoundary) {
assert(() {
if (debugPrintMarkNeedsPaintStacks) {
debugPrintStack(label: 'markNeedsPaint() called for $this');
}
return true;
}());
// If we always have our own layer, then we can just repaint
// ourselves without involving any other nodes.
assert(_layerHandle.layer is OffsetLayer);
if (owner != null) {
owner!._nodesNeedingPaint.add(this);
owner!.requestVisualUpdate();
}
} else if (parent is RenderObject) {
parent!.markNeedsPaint();
} else {
assert(() {
if (debugPrintMarkNeedsPaintStacks) {
debugPrintStack(
label: 'markNeedsPaint() called for $this (root of render tree)');
}
return true;
}());
// If we are the root of the render tree and not a repaint boundary
// 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.
//
// Trees rooted at a RenderView do not go through this
// code path because RenderViews are repaint boundaries.
if (owner != null) {
owner!.requestVisualUpdate();
}
}
}
在markNeedsPaint
中RepaintBoundary
会将渲染子Tree限制在自己的范围内,与markNeedsLayout
类似,而且也会通过owner去执行渲染管线的管理。
让我们看看PipelineOwner
是如何执行的?
/// Calls [onNeedVisualUpdate] if [onNeedVisualUpdate] is not null.
///
/// Used to notify the pipeline owner that an associated render object wishes
/// to update its visual appearance.
void requestVisualUpdate() {
if (onNeedVisualUpdate != null) {
onNeedVisualUpdate!();
} else {
_manifold?.requestVisualUpdate();
}
}
在Flutter中,PipelineOwner
负责渲染管线的各个方面,是具体与WidgetsBinding
中RendererBinding
帧事件循环的交互。
让我们看看onNeedVisualUpdate
的注释,以下。
/// Called when a render object associated with this pipeline owner wishes to
/// update its visual appearance.
///
/// Typical implementations of this function will schedule a task to flush the
/// various stages of the pipeline. This function might be called multiple
/// times in quick succession. Implementations should take care to discard
/// duplicate calls quickly.
///
/// When the [PipelineOwner] is attached to a [PipelineManifold] and
/// [onNeedVisualUpdate] is provided, the [onNeedVisualUpdate] callback is
/// invoked instead of calling [PipelineManifold.requestVisualUpdate].
final VoidCallback? onNeedVisualUpdate;
可知作为一个调度任务可能被多次重复调用,当PipelineOwner
连接到PipelineManifold
并提供onNeedVisualUpdate
时,会直接执行onNeedVisualUpdate
。
继续查看PipelineManifold
,以下。
abstract class PipelineManifold implements Listenable {
/// Whether [PipelineOwner]s connected to this [PipelineManifold] should
/// collect semantics information and produce a semantics tree.
///
/// The [PipelineManifold] notifies its listeners (managed with [addListener]
/// and [removeListener]) when this property changes its value.
///
/// See also:
///
/// * [SemanticsBinding.semanticsEnabled], which [PipelineManifold]
/// implementations typically use to back this property.
bool get semanticsEnabled;
/// Called by a [PipelineOwner] connected to this [PipelineManifold] when a
/// [RenderObject] associated with that pipeline owner wishes to update its
/// visual appearance.
///
/// Typical implementations of this function will schedule a task to flush the
/// various stages of the pipeline. This function might be called multiple
/// times in quick succession. Implementations should take care to discard
/// duplicate calls quickly.
///
/// A [PipelineOwner] connected to this [PipelineManifold] will call
/// [PipelineOwner.onNeedVisualUpdate] instead of this method if it has been
/// configured with a non-null [PipelineOwner.onNeedVisualUpdate] callback.
///
/// See also:
///
/// * [SchedulerBinding.ensureVisualUpdate], which [PipelineManifold]
/// implementations typically call to implement this method.
void requestVisualUpdate();
}
在Flutter中,PipelineManifold
通常不会暴露在外,它管理PipelineOwner Tree下所有PipelineOwner
,它实现了PipelineOwner
访问共享,PipelineManifold
通过调用SchedulerBinding.ensureVisualUpdate
实现通知渲染执行。
让我们继续进行下一步的探索,以下。
/// Schedules a new frame using [scheduleFrame] if this object is not
/// currently producing a frame.
///
/// Calling this method ensures that [handleDrawFrame] will eventually be
/// called, unless it's already in progress.
///
/// This has no effect if [schedulerPhase] is
/// [SchedulerPhase.transientCallbacks] or [SchedulerPhase.midFrameMicrotasks]
/// (because a frame is already being prepared in that case), or
/// [SchedulerPhase.persistentCallbacks] (because a frame is actively being
/// rendered in that case). It will schedule a frame if the [schedulerPhase]
/// is [SchedulerPhase.idle] (in between frames) or
/// [SchedulerPhase.postFrameCallbacks] (after a frame).
void ensureVisualUpdate() {
switch (schedulerPhase) {
case SchedulerPhase.idle:
case SchedulerPhase.postFrameCallbacks:
scheduleFrame();
return;
case SchedulerPhase.transientCallbacks:
case SchedulerPhase.midFrameMicrotasks:
case SchedulerPhase.persistentCallbacks:
return;
}
}
继续沿SchedulerBinding
的执行路径,以下。
void scheduleFrame() {
if (_hasScheduledFrame || !framesEnabled) {
return;
}
assert(() {
if (debugPrintScheduleFrameStacks) {
debugPrintStack(label: 'scheduleFrame() called. Current phase is $schedulerPhase.');
}
return true;
}());
ensureFrameCallbacksRegistered();
platformDispatcher.scheduleFrame();
_hasScheduledFrame = true;
}
最终void scheduleFrame()
是具体下一帧绘制的方法,由此完成闭环。
综上所述,我们了解到,Flutter具体执行渲染时会构建RenderObject
Tree(通过将RenderObject
插入Slot
),具体操作交给PipelineOwner
管理,而同一Tree下的PipelineOwner
对接唯一的PipelineManifold
,最后通知SchedulerBinding
去执行帧绘制的操作。