Flutter渲染机制分析(三) —— Build构建
Flutter渲染机制分析(一) —— setState详解
前情回顾
文章开头我们先来回顾一个方法:
mixin RenderBinding {
@protected
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;
}
}
}
上篇文章我们讲到调度一帧后,方法最终走到了drawFrame。这里我们看到drawFrame方法是在mixin类RdnerBinding中,通篇看下来我们似乎没有看到一个很重要的步骤Build构建,也就是说很有可能有子类对drawFrame进行了覆写,下面我们就来看看能不能在子类中找到它呢。
WidgetsBinding
drawFrame
在runApp中我们知道,WidgetsBinding混入了RenderBinding,我们来找找看,WidgetsBinding到底有没有自己实现drawFrame方法呢。
mixin WidgetsBinding {
@override
void drawFrame() {
try {
if (renderViewElement != null) {
buildOwner!.buildScope(renderViewElement!);
}
super.drawFrame();
buildOwner!.finalizeTree();
} finally {
assert(() {
debugBuildingDirtyElements = false;
return true;
}());
}
}
在WidgetsBinding中我们找到了drawFrame,去掉断言和与首帧渲染有关的代码后,我们找到了buildScope,这里就是我们在寻找的Build构建。
小提示Tips
/// Phases that can be reached by [WidgetTester.pumpWidget] and
/// [TestWidgetsFlutterBinding.pump].
///
/// See [WidgetsBinding.drawFrame] for a more detailed description of some of
/// these phases.
enum EnginePhase {
/// The build phase in the widgets library. See [BuildOwner.buildScope].
build,
/// The layout phase in the rendering library. See [PipelineOwner.flushLayout].
layout,
/// The compositing bits update phase in the rendering library. See
/// [PipelineOwner.flushCompositingBits].
compositingBits,
/// The paint phase in the rendering library. See [PipelineOwner.flushPaint].
paint,
/// The compositing phase in the rendering library. See
/// [RenderView.compositeFrame]. This is the phase in which data is sent to
/// the GPU. If semantics are not enabled, then this is the last phase.
composite,
/// The semantics building phase in the rendering library. See
/// [PipelineOwner.flushSemantics].
flushSemantics,
/// The final phase in the rendering library, wherein semantics information is
/// sent to the embedder. See [SemanticsOwner.sendSemanticsUpdate].
sendSemanticsUpdate,
}
flutter/packages/flutter_test/lib/src/binding.dart文件的开头,我们可以找到这段代码及描述,里面详细描述了,各个阶段对应的具体执行方法.
BuildScope 做了什么
BuildOwner的创建和RenderViewElement前面都已经介绍过,这里就不再赘述。
buildOwner!.buildScope(renderViewElement!);
可以看到buildScope中传入了根节点renderViewElement。
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) {
final Element element = _dirtyElements[index];
try {
element.rebuild();
} catch (e, stack) {
}
index += 1;
if (dirtyCount < _dirtyElements.length || _dirtyElementsNeedsResorting!) {
_dirtyElements.sort(Element._sort);
_dirtyElementsNeedsResorting = false;
dirtyCount = _dirtyElements.length;
while (index > 0 && _dirtyElements[index - 1].dirty) {
// It is possible for previously dirty but inactive widgets to move right in the list.
// We therefore have to move the index left in the list to account for this.
// We don't know how many could have moved. However, we do know that the only possible
// change to the list is that nodes that were previously to the left of the index have
// now moved to be to the right of the right-most cleaned node, and we do know that
// all the clean nodes were to the left of the index. So we move the index left
// until just after the right-most clean node.
index -= 1;
}
}
}
} finally {
for (final Element element in _dirtyElements) {
element._inDirtyList = false;
}
_dirtyElements.clear();
_scheduledFlushDirtyElements = false;
_dirtyElementsNeedsResorting = null;
}
}
方法比较长,这里我们去掉断言和无关代码。通篇看下来该方法是围绕_dirtyElements“脏”Element来执行的,所以这里我们就要弄清楚_dirtyElements是什么。
关于dirtyElements是什么可以看上一篇文章BuildOwner做了什么,对dirtyElements做了介绍。
这里我们主要对BuildScope方法进行分析。
Element.sort “脏”Element排序
static int _sort(Element a, Element b) {
final int diff = a.depth - b.depth;
if (diff != 0) {
return diff;
final bool isBDirty = b.dirty;
if (a.dirty != isBDirty) {
return isBDirty ? -1 : 1;
}
return 0;
}
int get depth {
return _depth;
}
对“脏”Elements列表进行排序后,接着对其进行遍历,依次取出其中的“脏”Element执行rebuild方法.
下面我们来看看rebuild方法具体逻辑.
Element.rebuild 重新构建
abstract class Element extends DiagnosticableTree implements BuildContext {
void rebuild({bool force = false}) {
if (_lifecycleState != _ElementLifecycle.active || (!_dirty && !force)) {
return;
}
performRebuild();
}
void performRebuild() {
_dirty = false;
}
}
rebuild做的很简单,当前生命周期是active并且Element是”脏的“,执行了performReBuild,这个方法也很简单,将_dirty置为 false,主要实现逻辑在其子类RenderObjectElement中。
RenderObjectElement、RenderObjectWidget
abstract class RenderObjectElement extends Element {
/// Creates an element that uses the given widget as its configuration.
RenderObjectElement(RenderObjectWidget super.widget);
@override
void performRebuild() { // ignore: must_call_super, _performRebuild calls super.
_performRebuild(); // calls widget.updateRenderObject()
}
@pragma('vm:prefer-inline')
void _performRebuild() {
(widget as RenderObjectWidget).updateRenderObject(this, renderObject);
super.performRebuild(); // clears the "dirty" flag
}
}
RenderObjectElement中执行的方法也很简单,实际调用了RenderObjectWidget的updateRenderObject方法传入当前renderObject
小提示Tips:关于renderObject的创建可以在runApp的scheduleAttachRootWidget
中找到答案,在这里就不分析了,后面会写一篇Flutter三棵树的文章,在里面介绍.
下面我们来看看updateRenderObject又做了什么
abstract class RenderObjectWidget extends Widget {
@protected
void updateRenderObject(BuildContext context, covariant RenderObject renderObject) { }
}
RenderObjectWidget是一个抽象类,里面所有的方法都是由其子类实现的。RenderObjectWidget子类有很多,这里我们就分析一种,它的子类SingleChildRenderObject
abstract class SingleChildRenderObjectWidget extends RenderObjectWidget {
/// Abstract const constructor. This constructor enables subclasses to provide
/// const constructors so that they can be used in const expressions.
const SingleChildRenderObjectWidget({ super.key, this.child });
/// The widget below this widget in the tree.
///
/// {@macro flutter.widgets.ProxyWidget.child}
final Widget? child;
@override
SingleChildRenderObjectElement createElement() => SingleChildRenderObjectElement(this);
}
SingleChildRenderObectWidget同样是一个抽象类,并没有updateRenderObject的实现。我们同样要将目光转到其子类上。
查看其直接子类,我们发现了平时很常用的DecoratedBox装饰盒
class DecoratedBox extends SingleChildRenderObjectWidget {
const DecoratedBox({
super.key,
required this.decoration,
this.position = DecorationPosition.background,
super.child,
})
@override
void updateRenderObject(BuildContext context, RenderDecoratedBox renderObject) {
renderObject
..decoration = decoration
..configuration = createLocalImageConfiguration(context)
..position = position;
}
}
DecoratedBox中updateRenderObject的实现非常简单,就是把新的参数值重新赋值给传入的renderObject。
到这里一个Element的rebuild就结束了,我们把目光放回到BuildScope中,接着往看下面的代码
if (dirtyCount < _dirtyElements.length || _dirtyElementsNeedsResorting!) {
_dirtyElements.sort(Element._sort);
_dirtyElementsNeedsResorting = false;
dirtyCount = _dirtyElements.length;
while (index > 0 && _dirtyElements[index - 1].dirty) {
index -= 1;
}
}
当前dirtyCount与_dirtyElements数量不相等时,会重新获取dirtyCount,对dirtyElements,索引回退到第一个“脏”Element。也就是说当调用setState重新构建页面时,如果再次调用setState会重新从头开始构建。
buildScope方法的最后,清空“脏”列表dirtyElements。
目光回归到WidgetsBinding的drawFrame中,当buildScope执行完毕后,会执行super.drawFrame方法,也就是我们开头提到的drawFrame,继续执行Layout布局、Paint绘制。
到这里Build的构建就查看完毕了,下面来总结一下
总结
RenderBinding中的drawFrame首先会执行WidgetsBinding中的drameFrame方法,接着执行自己的drawFrame- Build构建执行时机在
WidgetBinding的drameFrame中 performRebuild具体重建逻辑是在具体Widget中实现的
有一点需要注意,该系列文章的切入点是以setState为切入的进行分析的,所介绍的是重建的渲染机制分析。首次构建逻辑可以对照着本系列文章的思路,从runApp从头自己分析一遍。
转载自:https://juejin.cn/post/7221020223218384953