在Flutter源码阅读分析:Framework层的启动中,我们分析了Framework层的启动流程,其中讲到了在runApp
方法中,调用到了attchRootWidget
方法:
// ./packages/flutter/lib/src/widgets/binding.dart
void attachRootWidget(Widget rootWidget) {
_readyToProduceFrames = true;
_renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
container: renderView,
debugShortDescription: '[root]',
child: rootWidget,
).attachToRenderTree(buildOwner, renderViewElement as RenderObjectToWidgetElement<RenderBox>);
}
这个方法获取一个Widget
并将其附到renderViewElement
上,在必要的时候创建这个renderViewElement
。
其中涉及到了Widget
、Element
和Render
,都属于Flutter渲染机制。本文将对Flutter渲染机制进行分析。
首先看一下RenderObjectToWidgetAdapter
这个类和其构造方法:
class RenderObjectToWidgetAdapter<T extends RenderObject> extends RenderObjectWidget {
RenderObjectToWidgetAdapter({
this.child,
this.container,
this.debugShortDescription,
}) : super(key: GlobalObjectKey(container));
...
}
这个类的作用是桥接RenderObject
和Element
树,其中container
就是RenderObject
,而Element
树则插入在其中。类型参数T
是一种RenderObject
,是container
期望其孩子的类型。
再看一下RenderObjectToWidgetAdapter
类的attachToRenderTree
方法:
RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T> element ]) {
if (element == null) {
owner.lockState(() {
element = createElement();
assert(element != null);
element.assignOwner(owner);
});
owner.buildScope(element, () {
element.mount(null, null);
});
// This is most likely the first time the framework is ready to produce
// a frame. Ensure that we are asked for one.
SchedulerBinding.instance.ensureVisualUpdate();
} else {
element._newWidget = this;
element.markNeedsBuild();
}
return element;
}
这个方法填充了widget
,并且将结果RenderObject
设置为container
的孩子。如果element
为空,那么则会创建一个新的element
,否则的话,给定的element
会调度一个更新,使得与当前widget
关联。
从上面可以看出,对于Flutter框架来说,主要关注的就是Widget
、Element
、RenderObject
。下面我们来分析一下这三者的特点和关系。
在上文中的例子中,rootWidget
是用户开发的Flutter应用的根节点,是一个Widget
类。
在Widget
类的注释中,官方给出的定位是用于描述Element
的配置。Widget
是Flutter框架的中心类结构。一个Widget
是UI中一个固定不变的部分。可以被填充成Element
,而Element
又管理底层的渲染树。
Widget
本身是没有可变状态的,所有的成员变量都是final
的。如果需要和一个Widget
关联的可变状态,可以使用StatefulWidget
,这个类会创建一个StatefulWidget
,而它又会在填充成element
和合并到树中的时候创建一个State
对象。
一个给定的Widget
可以被包含到树中0次或更多次。特别是Widget
可以被多次放置到树中。每次Widget
被放置到树中,都会填充成一个Element
。看一下Widget
基类的方法声明:
@immutable
abstract class Widget extends DiagnosticableTree {
const Widget({ this.key });
final Key key;
@protected
Element createElement();
...
static bool canUpdate(Widget oldWidget, Widget newWidget) {
...
}
static int _debugConcreteSubtype(Widget widget) {
...
}
}
分别介绍一下这几个方法和成员变量。
首先是key
这个成员变量,它用于控制在树中一个Widget
如何替换另一个。主要有以下几种方式:更新Element
、替换Element
以及换位置。通常情况下,如果一个Widget
是另一个的唯一孩子,那么不需要明确的key
。
createElement
方法用于将配置填充为一个具体的实例。
canUpdate
方法用于判断newWidget
能否用于更新当前以oldWidget
为配置的Element
。
_debugConcreteSubtype
方法返回一个编码值,用于指示Widget
的实际子类型,1表示StatefulWidget
,2表示StatelessWidget
。
StatefullWidget
和StatelessWidget
都是Widget
的抽象子类,下面看一下这两个子类的具体情况。
StatelessWidget
用于不需要可变状态的情况。一个无状态Widget
通过建立一些列其他更完整描述UI的Widget
的方式,来描述部分UI。这个构建过程是一个递归的过程,直到这个描述已经被完全的实现。
当部分UI依赖的只有其自身配置信息和BuildContext
时,StatelessWidget
就非常有用了。
abstract class StatelessWidget extends Widget {
...
@protected
Widget build(BuildContext context);
}
build
方法会在当前Widget
被插入到给定BuildContext
内的树中时被调用。框架会用这个方法返回的Widget
更新当前Widget
的子树,可能是更新现有子树,也可能是移除子树。然后根据返回的Widget
填充一个新的子树。
通常情况下,这个方法的实现,会返回一个新建的Widget
系列,构建信息是根据从当前Widget
构造函数和给定BuildContext
中传递进来的信息来配置。
StatefulWidget
拥有一个可变的状态。这个状态State
在Widget
建立时可以同步地被读取,而在Widget
的整个声明周期中可能会改变。
StatefulWidget
可用于可动态变化的用户节目口描述。比如说,依赖于一些系统状态或者时钟驱动的情况。
abstract class StatefulWidget extends Widget {
...
@protected
State createState();
}
StatefulWIdget
实例本身时不可变的,但是其动态信息会保存在一切辅助类对象里,比如通过createState
方法创建的State
对象,或者是State
订阅的对象。
框架会在填充一个StatefulWidget
时调用createState
方法。这意味着,当一个StatefulWidget
在树中不同位置插入时,可能会有多个State
对象与这个StatefulWidget
关联。类似的,如果一个StatefulWidget
先从树中移除,之后又重新插入到树中,那么框架会再次调用createState
去创建一个新的State
对象,便于简化State
对象的声明周期。
可以看出,对于StatefulWidget
来说,State
类是关键辅助类。下面再看一下State
类的详情。
State
类用于表示StatefulWidget
的逻辑和内部状态。
State
对象有以下的声明周期:
StatefulWidget.createState
方法创建State
对象;State
对象与一个BuildContext
关联。这个关联是不变的,也就是说,State
对象不会改变它的BuildContext
。不过,BuildContext
本身是可以在沿着子树移动。这种情况下,State
对象可以认为是mounted
。initState
方法。State
的子类都需要重载initState
方法,来实现一次性初始化。这个初始化依赖于BuildContext
或Widget
,即分别对应于context
和widget
属性。didChangeDependencies
方法。State
的子类需要重写该方法,来实现包括InderitedWidget
在内的初始化。如果调用了BuildContext.dependOnInheritedWIdgetOfExactType
方法,那么在后续InheritedWidget
改变或当前Widget
在树中移动时,didChangeDependencies
方法会再次被调用。State
对象已经完全初始化,框架可能会调用任意次数的build
方法来获取一个子树UI的描述。State
对象会自发的通过调用setState
方法来请求重建其子树。这个方法以为着其部分内部状态发生了改变,这可能会影响到子树中的UI。Widget
可能会重建和请求当前位置显式一个新的Widget
。当这些发生时,框架会将widget
属性更新为新的Widget
,并且调用didUpdateWidget
方法,将之前的Widget
作为一个参数传入。State
对象应该重载didUpdateWidget
方法来应对其关联的Widget
的变化。框架也会在didUpdateWidget
方法之后调用build
方法,这意味着在didUpdateWidget
方法内调用setState
方法是多余的。reassemble
方法。这会使得在iniState
方法中准备好的数据重新初始化。State
的子树从树中移除,框架会调用deactivate
方法。子类需要重载这个方法,来清理当前对象和树中其他element
的连接。build
方法使得State
对象适配新位置。以上操作会在子树移动所在的动画帧结束前完成,这意味着State
对象可以延迟释放大部分资源,直到框架调用他们的dispose
方法。dispose
方法,表示这个State
对象不会再创建。子类需要重载这个方法来释放这个对象持有的资源。dispose
之后,State
对象可以认为是未安装状态,其mounted
属性置为false
。此时不能调用setState
方法。生命周期终止:在State
对象被处理后,将不再有机会重新挂载。@optionalTypeArgs
abstract class State<T extends StatefulWidget> with Diagnosticable {
...
T _widget;
...
StatefulElement _element;
bool get mounted => _element != null;
@protected
@mustCallSuper
void initState() {
assert(_debugLifecycleState == _StateLifecycle.created);
}
@mustCallSuper
@protected
void didUpdateWidget(covariant T oldWidget) { }
@protected
@mustCallSuper
void reassemble() { }
@protected
void setState(VoidCallback fn) {
...
}
@protected
@mustCallSuper
void deactivate() { }
@protected
@mustCallSuper
void dispose() {
...
}
@protected
Widget build(BuildContext context);
@protected
@mustCallSuper
void didChangeDependencies() { }
...
}
InheritedWIdget
可用于向下传播信息的Widget
的基类。为了从BuildContext
中获取最近的特定类型InheritedWidget
实例,需要使用BuildContext.dependOnInheritedWidgetOfExactType
。如果使用这种方式引用了InheritedWidget
,那么在其状态发生改变时,会引发消费者重建。
abstract class InheritedWidget extends ProxyWidget {
...
@override
InheritedElement createElement() => InheritedElement(this);
@protected
bool updateShouldNotify(covariant InheritedWidget oldWidget);
}
InheritedWIdget
继承自ProxyWidget
。ProxyWidget
会有子Widget
提供给它,而不需要新创建一个。
RenderObjectWidget
为RenderObjectElement
提供配置。RenderObjectElement
用于包装RenderObject
。而RenderObject
则是提供了应用实际渲染。
abstract class RenderObjectWidget extends Widget {
...
@override
RenderObjectElement createElement();
@protected
RenderObject createRenderObject(BuildContext context);
@protected
void updateRenderObject(BuildContext context, covariant RenderObject renderObject) { }
@protected
void didUnmountRenderObject(covariant RenderObject renderObject) { }
}
这个类有三个重要子类,分别是LeafRenderObjectWidget
、SingleChildRenderObjectWidget
、MultiChildRenderObjectWidget
,分别用于无子节点、有单个子节点和有多个子节点的RenderObjectWidget
。
Widget
构成了Flutter UI的最上层,直接面对开发者。开发者在开发Flutter应用时,都是通过Widget
来实现应用的UI。
Widget
类继承关系如图所示:
上文讲过了Widget
的作用是为Element
的配置提供描述的。反过来讲,那就是Element
其实可以说是Widget
在树中特定位置的实例。
Widget
用来描述如何配置一个子树,而且同一个Widget
可以用来同时配置多个子树,因为Widget
是不可变的。经过一段时间后,与给定Element
相关联的Widget
可能会发生改变。例如,当父节点Widget
重建后,为当前位置创建了一个新Widget
。
Element
会形成一棵树。大部分的Element
拥有单独的一个子节点,不过部分Widget
(如RenderObjectElement
的子类)会拥有多个子节点。
Element
有如下的生命周期:
Widget.createElement
方法创建一个Element
。这个Widget
被用来当作Element
的初始化配置。mount
方法来将一个新创建的Element
添加到树中给定父节点的指定槽中。mount
方法负责填充所有的子Widget
,以及在必要的时候调用attachRenderObject
将关联的RenderObject
附着到render树上。Element
是“active”,可以出现在屏幕上了。Element
使用的配置Widget
。在这种情况下,框架会调用update
方法来更新Widget
。新的Widget
通常会有与老的Widget
相同的runtimeType
和key
。如果父节点希望改变runtimeType
或key
,可以通过卸载该Element
并填充新的Widget
来实现。deactivateChild
方法将当前Element
从树中移除。停用间接祖先会导致将那个Element
的RenderObject
从渲染树中移除,并且将当前Element
添加到owner
的非活动Element
列表中,最终引起框架对Element
调用deactivate
方法。Element
是“inactive”的,且不再在屏幕上出现。一个Element
可以在当前动画帧结束前保持在“inactive”状态。在动画帧结束时,所有仍然保持在“inactive”状态的Element
会被卸载(unmount)。Element
重新合并入树中(如该Element
或其祖先节点有一个global key,且重用时),框架会将这个Element
从owner
的非活跃Element
列表中移除,然后将该Element
的RenderObject
重新附着到渲染树中。此时,这个Element
重新被视为“active”,且可以出现在屏幕上。Element
在当前动画帧结束时没有重新合并到树中,框架就会对该Element
调用unmount
方法。Element
可以认为是“defunct”状态,且不再会重新合并入树中。abstract class Element extends DiagnosticableTree implements BuildContext {
...
@mustCallSuper
@protected
void reassemble() {
...
}
...
void visitChildren(ElementVisitor visitor) { }
...
@protected
Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
...
}
@mustCallSuper
void mount(Element parent, dynamic newSlot) {
...
}
@mustCallSuper
void update(covariant Widget newWidget) {
...
}
...
void detachRenderObject() {
...
}
void attachRenderObject(dynamic newSlot) {
...
}
...
@protected
Element inflateWidget(Widget newWidget, dynamic newSlot) {
...
}
...
@protected
void deactivateChild(Element child) {
...
}
...
@mustCallSuper
void activate() {
...
}
@mustCallSuper
void deactivate() {
...
}
...
@mustCallSuper
void unmount() {
...
}
...
void markNeedsBuild() {
...
}
void rebuild() {
...
}
@protected
void performRebuild();
}
可以看到,Element
类继承于BuildContext
类,也就是说Element
就是在Widget
小节里经常提到的BuildContext
。
在Element
类的方法中,updateChild
方法是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]. |
Element
是一个抽象类,其子类主要分为两种,ComponentElement
和RenderObjectElement
。下面分别看一下各自的情况。
ComponentElement
是组合其他Element
的Element
。其本身不直接创造RenderObject
,但是会通过创造其他Element
的方式间接地创建RenderObject
。
ComponentElement
的子类StatelessElement
和StatefulElement
分别是对应于StatelessWidget
和StatefullWidget
的Element
。同样,InheritedElement
也是ComponentElement
的一种,对应于InheritedWidget
。
RenderObjectElement
对象有一个关联的渲染树中的RenderObject
。RenderObject
实际执行布局、绘制、碰撞检测等操作。
RenderObject
有三种子节点模型:
RenderObject
,无子节点:LeafRenderObjectElement
类处理这种情况SingleChildRenderObjectElement
类处理这种情况MultiChildRenderObjectElement
类处理这种情况RenderObject
的子节点模型会更复杂。可续会有一个二维数组的子节点,可能仅在需要的时候创建子节点,也可能形成多列表的形式。在这些情况发生时,就需要相应的新的RenderObjectElement
子类。这样的子类需要能够管理子节点,特别时这个对象的Element
子节点,以及其对应RenderObject
的子节点。RenderObjectElement
还有一个特殊的子类RootRenderObjectElement
,用于表示树的根节点。只有根节点Element
可以显式的设置BuildOwner
,其他的Element
都只能继承父节点的BuildOwner
。
RenderObject
是渲染库的核心。每个RenderObject
都有一个父节点,且有一个叫parentData
的槽位用于供父RenderObject
保存子节点相关数据,例如子节点位置等。RenderObject
类还实现了基本的布局和绘制协议。
RenderObject
类没有定义子节点模型(如一个节点有零个、一个还是多个子节点),也没有定义坐标系(如是在直角坐标系还是极坐标系中),同样也没有定义特定的布局协议。
RenderBox
子类采用了直角坐标系布局系统。通常情况下,直接继承RenderObject
类有点过度了,继承RenderBox
类可能会更好一些。当然,如果实现的子类不想使用直角坐标系的话,那就得继承RenderObject
类了。
abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget {
...
void reassemble() {
...
}
// LAYOUT
...
@override
void adoptChild(RenderObject child) {
...
}
@override
void dropChild(RenderObject child) {
...
}
void visitChildren(RenderObjectVisitor visitor) { }
...
@override
void attach(PipelineOwner owner) {
...
}
...
void markNeedsLayout() {
...
}
...
void scheduleInitialLayout() {
...
}
...
void layout(Constraints constraints, { bool parentUsesSize = false }) {
...
}
...
// PAINTING
...
void markNeedsCompositingBitsUpdate() {
...
}
...
void markNeedsPaint() {
...
}
...
void scheduleInitialPaint(ContainerLayer rootLayer) {
...
}
...
void paint(PaintingContext context, Offset offset) { }
void applyPaintTransform(covariant RenderObject child, Matrix4 transform) {
...
}
...
// SEMANTICS
void scheduleInitialSemantics() {
...
}
...
void markNeedsSemanticsUpdate() {
...
}
...
void assembleSemanticsNode(
SemanticsNode node,
SemanticsConfiguration config,
Iterable<SemanticsNode> children,
) {
...
}
// EVENTS
@override
void handleEvent(PointerEvent event, covariant HitTestEntry entry) { }
...
}
下面来看一下Flutter提供的实现子类RenderBox
。
RenderBox
是在二维直角坐标系内的RenderObject
。
对于RenderBox
来说,size
表达为宽和高。每个RenderBox
都有自己的坐标系,这个坐标系左上角坐标为(0, 0)
,右下角坐标为(width, height)
。RenderBox
中的点包含了左上角,但是不包含右下角。
盒布局通过向下传递BoxConstraints
对象来实现布局。BoxContraints
为子节点的宽高提供了最大值和最小值约束。子节点在确定自身尺寸时,必须遵守父节点给定的约束。
以上协议足够表达一系列普通盒布局数据流。例如,为了实现width-in-height-out数据流,在调用子节点layout
方法时,传递有紧凑的宽度数值的盒约束。在子节点确定了其高度后,使用子节点的高度来确定自身的尺寸。
RenderView
是渲染树的根节点,其直接继承于RenderObject
。
RenderVIew
表示的是渲染树的整体输出surface。它也处理整个渲染管线的启动工作。RenderView
只有一个单独的子节点,这个子节点是RenderBox
类,它负责填满整个输出surface。
我们仍然以Framework层的启动中的启动的app为例。
const Center( // [2]
child:
Text('Hello, world!',
key: Key('title'),
textDirection: TextDirection.ltr
)
)
首先看一下Center
类。Center
是将子节点置于其中心的Widget
。
class Center extends Align {
...
}
Center
类继承自Align
类:
class Align extends SingleChildRenderObjectWidget {
...
@override
RenderPositionedBox createRenderObject(BuildContext context) {
return RenderPositionedBox(
alignment: alignment,
widthFactor: widthFactor,
heightFactor: heightFactor,
textDirection: Directionality.of(context),
);
}
...
}
Align
类继承自SingleChildRenderObjectWidget
,对应的Element
则为SingleChildRenderObjectElement
。Align
重载了createRenderObject
方法,创建的RenderObject
为RenderPositionedBox
。
接着看一下RenderPositionedBox
类:
class RenderPositionedBox extends RenderAligningShiftedBox {
...
}
abstract class RenderAligningShiftedBox extends RenderShiftedBox {
...
}
abstract class RenderShiftedBox extends RenderBox with RenderObjectWithChildMixin<RenderBox> {
...
}
RenderPositionedBox
类最终继承自RenderBox
。通过使用一个AlignmentGeometry
来对子节点进行定位。
再来看一下Text
类。该类用于表示一连串相同样式的文字。
class Text extends StatelessWidget {
...
@override
Widget build(BuildContext context) {
...
Widget result = RichText(
...
text: TextSpan(
...
),
);
...
return result;
}
...
}
Text
类继承于StatelessWidget
,对应StatelessElement
。Text
实现了build
方法,创建了一个RichText
。RichText
也是一个Widget
,继承关系如下:
class RichText extends MultiChildRenderObjectWidget {
...
@override
RenderParagraph createRenderObject(BuildContext context) {
...
}
...
}
RichText
继承于MultiChildRenderObjectWidget
,用于表示一个富文本段落。RichText
可能有多个SizedBox
类子节点,但这种类型的子节点是通过Text.rich
方法创建的,该例子内不涉及,也就是说children
属性是一个长度为0的列表。
RichText
重载了createRenderObject
,创建一个RenderParagraph
。
class RenderParagraph extends RenderBox
with ContainerRenderObjectMixin<RenderBox, TextParentData>,
RenderBoxContainerDefaultsMixin<RenderBox, TextParentData>,
RelayoutWhenSystemFontsChangeMixin {
...
}
RenderParagraph
继承自RenderBox
,是用于展示文字段落的RenderObject
。
对于Text
来说,三者关系如下图:
本文对Flutter框架中的Widget
、Element
和RenderObject
做了简要讲解。这三个类系统是Flutter框架的核心。Widget
负责UI部分,与开发者直接交互;Element
负责在指定位置实例化Widget
,并维护树结构;RenderObject
则是渲染的核心,负责包括布局、测量、绘制等工作。
三棵树之间有一定的对应关系。一个Widget
可能会对应多个Element
,而一个Element
则仅对应一个Widget
;只有继承于RenderObjectElement
的Element
会维护RenderObject
;而RenderObject
的创建入口则是在RenderObjectWidget
中。
后续文章中,会基于这三者的概念之上,详细分析Flutter的渲染管线。