三棵树的渲染模型
1. 为什么采用三棵树的渲染模型
在三棵树的结构模型中,前两个树结构(Widget tree和 Eelement Tree)是极其不稳定的,在运行过程基于数据的变化和用户的交互以及动画等,随时会重新构建,而频繁的去操作、创建RenderTree是一个很耗性能的事情,同时并不是所有的Widget都会追踪执行渲染操作,非渲染的Widget最终不会生成对应的RenderObject。flutter通过算法在Widget Tree和Element Tree经常变动的情况下通过定量数据修改、复用等方式保持Render Tree的稳定性,减小性能开销,同时Element Tree肩负着中间层功能,根据不同的Widget需要,判断是否更新或创建RenderObject对象。
2. 三棵树分别肩负着什么功能使命
Widget Tree:
基于当前应用状态来描述UI信息和层级结构
Element Tree:
- 挂载Widget
- 挂载state
- 引用RenderObject
- 肩负着中间层功能,根据不同的Widget需要,判断是否更新或创建RenderObject对象
Render Tree:
主要职责是负责是负责视图渲染
3. 三棵树是什么时候创建的,创建流程是怎么样的
Widget层面我们常接触到的一共有3种Widget
- statefulWidget
- StatelessWidget
- RenderObjectWidget
我们先从StatelessWidget入手来分析一下源码: statelessWidget中有一个createElement方法,这个方法返回了一个StatelessElement对象,这个方法在每次视图需要更新/添加/移除Widget的时候就会被调用(updateChild),在调用这个方法的同时将statelessWidget作为参数传入了进入。至此,三棵树中的第二个重要元素(Element)在此被创建。接下来我们基于updateChild看一下一些主要逻辑的调用链
- 调用链:我们开发的statelessWidget内容何时被创建
Element.updateChild->Element.inflateWidget->Element.mount->子类ComponentElement.mount->ComponentElement._firstBuild->ComponentElement.rebuild->ComponentElement.performRebuild->ComponentElement.build->子类StatelessElement.build
从调用链中我们可以知道,在updateChild这个入口中最终会调用StatelessElement中的build方法,而这个方法就是我们熟悉的,在开发过程中构建UI的build方法,statelessWidget本身是不承载UI内容信息的,是他的build方法返回的内容才会承载UI的描述信息。至此statelessWidget的内容也被调用返回。
接着我们再从statefulWidget入手来分析一下: 同样在StatefulWidget中的createElement中创建了一个StatefulElement,我们看一下StatefulElement的构造方法
其中有这样几句
_state = widget.createState()
state._element = this;
state._widget = widget;
点到为止,自行领悟
StatefulElement同样继承自ComponentElement但在StatefulElement不同的是他自己实现的build方法↓
@override
Widget build() => state.build(this);
点到为止,大家都是成年人了。[手动狗头]
至此相信大家已经明白了以下问题:
- 为什么在state内部可以使用widget.XXX
接下来我们研究RenderObjectWidget RenderObjectWidget是个抽象类,它的createElement是子类实现的,我们看一下他的子类Padding->SingleChildRenderObjectWidget.createElement 返回的是一个SingleChildRenderObjectElement,接着看SingleChildRenderObjectElement->RenderObjectElement.mount中有一句
_renderObject = (widget as RenderObjectWidget).createRenderObject(this);
而基类Widget是个抽象类他的createRenderObject由子类实现,我们回到子类Padding中会发现这个方法的实现
@override
RenderPadding createRenderObject(BuildContext context) {
return RenderPadding(
padding: padding,
textDirection: Directionality.maybeOf(context),
);
}
根据继承关系RenderPadding->RenderShiftedBox->RenderBox->RenderObject可以发现这里返回的就是RenderObject,至此我们的RenderObject也被创建。 相信你已经理解了以下问题了:
- 三棵树是什么时候创建的,创建流程是怎么样
- state widget Element randerObject他们之间的关系是怎么样的
4. 为什么说RenderObject才是真正的渲染对象/渲染层
首先请看官方文档说明:
当然我们也不能仅凭官方一句话我们就认,作为社会主义接班人,我们一定要秉持唯物主义精神,一起去源码中找一些蛛丝马迹。 首先,经过我的实践,我们无法从Widget、Element中找到任何和约束、布局、尺寸计算、视图渲染等相关的代码。通过官方文档我们知道,flutter布局的逻辑是自上而下+自下而上的两次遍历确定最终渲染的尺寸的,涉及到渲染逻辑的对象一定会存在尺寸计算的逻辑,然后我们去RenderObject搜寻一番,就会发现满眼皆是和渲染相关的,特别是↓
/// Paint this render object into the given context at the given offset.
///
/// Subclasses should override this method to provide a visual appearance
/// for themselves. The render object's local coordinate system is
/// axis-aligned with the coordinate system of the context's canvas and the
/// render object's local origin (i.e, x=0 and y=0) is placed at the given
/// offset in the context's canvas.
///
/// Do not call this function directly. If you wish to paint yourself, call
/// [markNeedsPaint] instead to schedule a call to this function. If you wish
/// to paint one of your children, call [PaintingContext.paintChild] on the
/// given `context`.
///
/// When painting one of your children (via a paint child function on the
/// given context), the current canvas held by the context might change
/// because draw operations before and after painting children might need to
/// be recorded on separate compositing layers.
void paint(PaintingContext context, Offset offset) { }
仔细看看注释,就已经很明确告诉你了,这玩意就是用来处理视图渲染的。
5.为什么更新数据需要setstate
不废话,直接上源码
@protected
void setState(VoidCallback fn) {
assert(fn != null);
assert(() {
if (_debugLifecycleState == _StateLifecycle.defunct) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('setState() called after dispose(): $this'),
ErrorDescription(
'This error happens if you call setState() on a State object for a widget that '
'no longer appears in the widget tree (e.g., whose parent widget no longer '
'includes the widget in its build). This error can occur when code calls '
'setState() from a timer or an animation callback.',
),
ErrorHint(
'The preferred solution is '
'to cancel the timer or stop listening to the animation in the dispose() '
'callback. Another solution is to check the "mounted" property of this '
'object before calling setState() to ensure the object is still in the '
'tree.',
),
ErrorHint(
'This error might indicate a memory leak if setState() is being called '
'because another object is retaining a reference to this State object '
'after it has been removed from the tree. To avoid memory leaks, '
'consider breaking the reference to this object during dispose().',
),
]);
}
if (_debugLifecycleState == _StateLifecycle.created && !mounted) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('setState() called in constructor: $this'),
ErrorHint(
'This happens when you call setState() on a State object for a widget that '
"hasn't been inserted into the widget tree yet. It is not necessary to call "
'setState() in the constructor, since the state is already assumed to be dirty '
'when it is initially created.',
),
]);
}
return true;
}());
final Object? result = fn() as dynamic;
assert(() {
if (result is Future) {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('setState() callback argument returned a Future.'),
ErrorDescription(
'The setState() method on $this was called with a closure or method that '
'returned a Future. Maybe it is marked as "async".',
),
ErrorHint(
'Instead of performing asynchronous work inside a call to setState(), first '
'execute the work (without updating the widget state), and then synchronously '
'update the state inside a call to setState().',
),
]);
}
// We ignore other types of return values so that you can do things like:
// setState(() => x = 3);
return true;
}());
_element!.markNeedsBuild();
}
↑重点看:_element!.markNeedsBuild();是不是很熟悉
void markNeedsBuild() {
assert(_lifecycleState != _ElementLifecycle.defunct);
if (_lifecycleState != _ElementLifecycle.active) {
return;
}
assert(owner != null);
assert(_lifecycleState == _ElementLifecycle.active);
assert(() {
if (owner!._debugBuilding) {
assert(owner!._debugCurrentBuildTarget != null);
assert(owner!._debugStateLocked);
if (_debugIsInScope(owner!._debugCurrentBuildTarget!)) {
return true;
}
if (!_debugAllowIgnoredCallsToMarkNeedsBuild) {
final List<DiagnosticsNode> information = <DiagnosticsNode>[ ErrorSummary('setState() or markNeedsBuild() called during build.'), ErrorDescription( 'This ${widget.runtimeType} widget cannot be marked as needing to build because the framework ' 'is already in the process of building widgets. A widget can be marked as ' 'needing to be built during the build phase only if one of its ancestors ' 'is currently building. This exception is allowed because the framework ' 'builds parent widgets before children, which means a dirty descendant ' 'will always be built. Otherwise, the framework might not visit this ' 'widget during this build phase.', ), describeElement('The widget on which setState() or markNeedsBuild() was called was'), ];
if (owner!._debugCurrentBuildTarget != null) {
information.add(owner!._debugCurrentBuildTarget!.describeWidget('The widget which was currently being built when the offending call was made was'));
}
throw FlutterError.fromParts(information);
}
assert(dirty); // can only get here if we're not in scope, but ignored calls are allowed, and our call would somehow be ignored (since we're already dirty)
} else if (owner!._debugStateLocked) {
assert(!_debugAllowIgnoredCallsToMarkNeedsBuild);
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('setState() or markNeedsBuild() called when widget tree was locked.'),
ErrorDescription(
'This ${widget.runtimeType} widget cannot be marked as needing to build '
'because the framework is locked.',
),
describeElement('The widget on which setState() or markNeedsBuild() was called was'),
]);
}
return true;
}());
if (dirty) {
return;
}
_dirty = true;
owner!.scheduleBuildFor(this);
}
↑重点看
if (dirty) { return; }
_dirty = true;
owner!.scheduleBuildFor(this); 在这里就重新标记了这个Element需要渲染。
打完收工,愿人间美好,愿世界和平,愿天下再无互卷的程序员。
本文讨论范围:
- 为什么采用三棵树的渲染模型
- 三棵树分别肩负着什么功能使命
- 三棵树是什么时候创建的,创建流程是怎么样的
- 为什么在state内部可以使用widget.XXX
- 为什么说RenderObject才是真正的渲染对象/渲染层
- 为什么更新数据需要setstate
- state widget Element randerObject他们之间的关系是怎么样的