Flutter渲染流程
一、视图树
Widget不稳定,一build就要重新进行构建,如果引擎直接对它进行渲染,消耗会非常高。
所以渲染引擎并不是直接渲染WidgetTree
,而是对WidgetTree
对应生成的RenderTree
进行渲染。
-
RenderTree
中放的是RederObject
对象。并不是所有Widget都会被独立渲染,只有继承了RenderObjectWidget
的才会被创建RenderObject对象
如:Column/Row/Expanded -> Flex -> MultiChildRenderObjectWidget -> RenderObjectWidget
而像StatefulWidget
、StatelessWidget
等等是继承于Widget的,而非RenderObjectWidget
,所以并不会创建RenderObject
对象。
为了理解三棵树的关系,我们先来看下它们的源码:
1. Widget
可以看成一个配置数据的结构,存放有关视图渲染的配置信息,如布局、渲染属性、事件响应信息等等。Widget比较轻便,方便在页面刷新是重绘
abstract class Widget extends DiagnosticableTree {
...
@protected
@factory
Element createElement();
...
}
无论那种类型的widget,都会隐式调用createElement
方法,element加入Element树。
1.1 RenderObjectWidget
为RenderObjectElement提供配置,遍历widget树时,会调用createElement同步widget自身配置,生成对应节点的element对象。
RenderObjectWidget
并没有对Render做任何操作
abstract class RenderObjectWidget extends Widget {
...
@override
@factory
RenderObjectElement createElement();
@protected
@factory
RenderObject createRenderObject(BuildContext context);
@protected
void updateRenderObject(BuildContext context, covariant RenderObject renderObject) { }
...
}
从上面RenderObjectWidget
的源码可以看到,它继承于Widget
类,重写了createElement
方法,与Widget
类不同的是,它额外提供了createRenderObject
和updateRenderObject
两个方法,由子类实现。
如:Row
、Column
继承于Flex
,在Flex
中重写了上面方法的实现:
class Flex extends MultiChildRenderObjectWidget {
...
@override
RenderFlex createRenderObject(BuildContext context) {
return RenderFlex(
direction: direction,
mainAxisAlignment: mainAxisAlignment,
mainAxisSize: mainAxisSize,
crossAxisAlignment: crossAxisAlignment,
textDirection: getEffectiveTextDirection(context),
verticalDirection: verticalDirection,
textBaseline: textBaseline,
clipBehavior: clipBehavior,
);
}
@override
void updateRenderObject(BuildContext context, covariant RenderFlex renderObject) {
renderObject
..direction = direction
..mainAxisAlignment = mainAxisAlignment
..mainAxisSize = mainAxisSize
..crossAxisAlignment = crossAxisAlignment
..textDirection = getEffectiveTextDirection(context)
..verticalDirection = verticalDirection
..textBaseline = textBaseline
..clipBehavior = clipBehavior;
}
...
}
1.2 MultiChildRenderObjectWidget
像Row
、Column
、Stack
这种可以添加children
组件列表的,最终都继承于MultiChildRenderObjectWidget
...
abstract class MultiChildRenderObjectWidget extends RenderObjectWidget {
final List<Widget> children;
@override
MultiChildRenderObjectElement createElement() => MultiChildRenderObjectElement(this);
}
...
这里重写了reateElement()
方法,返回一继承于RenderObjectElement
类的MultiChildRenderObjectElement
对象。
MultiChildRenderObjectElement
类的具体实现及调用时机,在后文会提及。
1.3 StatelessWidget 与 StatefulWidget
abstract class StatelessWidget extends Widget {
...
@override
StatelessElement createElement() => StatelessElement(this);
@protected
Widget build(BuildContext context);
...
}
abstract class StatefulWidget extends Widget {
...
@override
StatefulElement createElement() => StatefulElement(this);
@protected
@factory
State createState();
...
}
继承于StatelessWidget
和 StatefulWidget
的widget并不会创建对应的RenderObject
,所以像Container
、Scaffold
、MaterialApp
、Text
等这样的组件,并不直接进行渲染。
以Text
组件为例,真正返回的是继承自MultiChildRenderObjectWidget
的RichText
类。
而继承于StatefulWidget
的Scaffold
,则是在其build
方法内,去添加appBar
、child
widget等。
2. Element
-
是什么?
- 存放上下文,element其实就是BuildContext
- 持有Widget和RenderObject,是widget的一个实例化对象
- 每个Widget都会创建一个对应的Element
- 通过Element遍历视图树,支撑UI结构
-
为什么需要element?
- widget是不可变的,element对widget树的变化做了抽象,可以只将真正变化的部分同步给RenderObject进行刷新
- 提高渲染效率,而不是重新构建整个widget
- 对变化前后的数据进行比较,告诉render哪些是需要重新渲染的
2.1 Element
先来看下Element类有哪几个核心方法:
abstract class Element extends DiagnosticableTree implements BuildContext {
...
Element _parent;
@override
Widget get widget => _widget;
Widget _widget;
// 给element的child赋值
Element updateChild(Element child, Widget newWidget, dynamic newSlot) {...}
@mustCallSuper
void mount(Element parent, dynamic newSlot) {...}
@protected
Element inflateWidget(Widget newWidget, dynamic newSlot) {...}
@mustCallSuper
void unmount() {...}
void rebuild() {...}
...
}
- updateChild(...) 是一个很重要的方法,根据传入的三个参数的不同,会做不同的处理:
newWidget == null | newWidget != null | |
---|---|---|
child为null | 1. Returns null. | 2. 创建新element并返回 |
child不为null | 3. 移除传入的child,返回null | 4. child可能被更新,根据Widget.canUpdate(child.widget, newWidget) 方法判断返回更新后的child或创建新的element返回 |
@protected
Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
...
if (newWidget == null) {
if (child != null)
//情况3 移除子组件
deactivateChild(child);
//情况1
return null;
}
Element newChild;
if (child != null) {
//情况4
bool hasSameSuperclass = true;
if (hasSameSuperclass && child.widget == newWidget) {
//更新solt
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
newChild = child;
} else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) {
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
child.update(newWidget);
newChild = child;
} else {
//移除并新建child element
deactivateChild(child);
newChild = inflateWidget(newWidget, newSlot);
}
} else {
//情况2 创建element
newChild = inflateWidget(newWidget, newSlot);
}
...
}
- inflateWidget(...)
@protected
Element inflateWidget(Widget newWidget, dynamic newSlot) {
final Key key = newWidget.key;
if (key is GlobalKey) {
final Element newChild = _retakeInactiveElement(key, newWidget);
if (newChild != null) {
...
newChild._activateWithParent(this, newSlot);
final Element updatedChild = updateChild(newChild, newWidget, newSlot);
...
return updatedChild;
}
}
final Element newChild = newWidget.createElement();
...
newChild.mount(this, newSlot);
...
return newChild;
}
这里可以看到,如果当前newWidget
的key是GlobalKey,且在_retakeInactiveElement
方法中判断到widget已经在Element
的mount()
中注册到了GlobalKey
中,就直接复用,并调用上面的updateChild()
方法,此时的newWidget
和child
都不为null,所以属于情况4,调用child
的update(newWidget)
方法,更新当前Element持有的Widget。
除了这些逻辑,其它的会交由子类去实现。
- mount(...)
@mustCallSuper
void mount(Element parent, dynamic newSlot) {
...
_parent = parent;
_slot = newSlot;
_depth = _parent != null ? _parent.depth + 1 : 1;
_active = true;
if (parent != null) // Only assign ownership if the parent is non-null
_owner = parent.owner;
final Key key = widget.key;
if (key is GlobalKey) {
key._register(this);
}
_updateInheritance();
assert(() {
_debugLifecycleState = _ElementLifecycle.active;
return true;
}());
}
在Element的mount
方法中,进行了一系列初始化的操作。
如果当前element
中widget
的key是GlobalKey
,会将当前element存入由GlobalKey
维护的Map中。
最后,将生命周期设置为_ElementLifecycle.active
。
- unmount()
@mustCallSuper
void unmount() {
...
final Key key = _widget.key;
if (key is GlobalKey) {
key._unregister(this);
}
assert(() {
_debugLifecycleState = _ElementLifecycle.defunct;
return true;
}());
}
从GlobalKey中移除当前element,并更新生命周期为defunct
。
- rebuild()
void rebuild() {
if (!_active || !_dirty)
return;
...
performRebuild();
...
}
@protected
void performRebuild();
会调用performRebuild()
方法,在Element中什么也没做,交由子类去实现。
接下来来看看子类ComponentElement
的实现:
2.2 ComponentElement
abstract class ComponentElement extends Element {
Element _child;
...
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
...
_firstBuild();
...
}
void _firstBuild() {
rebuild();
}
@override
void performRebuild() {...}
@protected
Widget build();
}
在ComponentElement
的mount()
方法会调用到rebuild
,从上面Element源码中看到,rebuild
方法会最终调用performRebuild
方法:
@override
void performRebuild() {
...
Widget? built;
try {
...
built = build();
debugWidgetBuilderValue(widget, built);
} catch (e, stack) {
...
} finally {
_dirty = false;
} catch (e, stack) {
...
}
try {
_child = updateChild(_child, built, slot);
} catch (e, stack) {
built = ErrorWidget.builder(...);
_child = updateChild(null, built, slot);
}
...
}
可以看到,该方法中调用了build()
方法来创建Widget,如果出现异常,进入catch
语句创建一个ErrorWidget
,就是经常看到的那个红色报错界面。
build
后调用updateChild(...)
来给当前Element
的_child
赋值。
2.3 StatelessElement 和 StatefulElement
StatelessElement
和StatefulElement
是ComponentElement
的子类。
- StatelessElement
class StatelessElement extends ComponentElement {
@override
Widget build() => widget.build(this);
@override
void update(StatelessWidget newWidget) {
super.update(newWidget);
assert(widget == newWidget);
_dirty = true;
rebuild();
}
}
StatelessElement
类内容很少,这是重新了build
方法和update
方法,在update
内将Element
标记为dirty
,并调用rebuild()
。
- StatefulElement
class StatefulElement extends ComponentElement {
StatefulElement(StatefulWidget widget)
: _state = widget.createState(),
super(widget) {
...
_state._element = this;
...
_state._widget = widget;
...
}
@override
void _firstBuild() { ... }
@override
void update(StatefulWidget newWidget) { ... }
@override
void unmount() { ... }
}
在StatefulElement
的构造方法中,将当前Element
和widget
存放在了state
中。
先来看下这几个方法都做了什么
_firstBuild()
@override
void _firstBuild() {
...
final Object? debugCheckForReturnedFuture = state.initState() as dynamic;
...
state._debugLifecycleState = _StateLifecycle.initialized;
...
state.didChangeDependencies();
...
state._debugLifecycleState = _StateLifecycle.ready;
super._firstBuild();
}
从上面ComponentElement
源码看到,在mount(...)
方法被调用时,会调用_firstBuild()
方法,StatefulElement
中对该方法进行了重新,并进行了一系列初始化、state生命周期的控制。
并且调用 state.initState() 方法。
update(...)
@override
void update(StatefulWidget newWidget) {
super.update(newWidget);
...
final StatefulWidget oldWidget = state._widget!;
_dirty = true;
state._widget = widget as StatefulWidget;
...
final Object? debugCheckForReturnedFuture = state.didUpdateWidget(oldWidget) as dynamic;
...
rebuild();
}
这里主要调用了state.didUpdateWidget(oldWidget)
2.3 RenderObjectElement
所有继承自RenderObjectWidget
的Widget会创建在createElement()
时返回一个RenderObjectElement
类型的element。
下面看下RenderObjectElement
类的核心方法:
abstract class RenderObjectElement extends Element {
...
@override
RenderObject get renderObject => _renderObject!;
RenderObject? _renderObject;
...
void mount(Element? parent, Object? newSlot) {...}
@override
void update(covariant RenderObjectWidget newWidget) {...}
@override
void attachRenderObject(Object? newSlot) {...}
@override
void detachRenderObject() {...}
@protected
@mustCallSuper
void insertChildRenderObject(covariant RenderObject child, covariant Object? slot) {...}
@protected
void insertRenderObjectChild(covariant RenderObject child, covariant Object? slot) {...}
@protected
@mustCallSuper
void moveChildRenderObject(covariant RenderObject child, covariant Object? slot)
@protected
@mustCallSuper
void removeChildRenderObject(covariant RenderObject child)
...
}
- mount(...)
void mount(Element? parent, Object? newSlot) {
super.mount(parent, newSlot);
...
_renderObject = widget.createRenderObject(this);
...
attachRenderObject(newSlot);
_dirty = false;
}
与之前子类的mount()
方法不同的是,此处会调用widget.createRenderObject(this)
去创建对应的RenderObject
对象,并调用attachRenderObject()
方法对RenderObject
进行进一步的操作,在此之后,_dirty
就被置为了false
,下面看看attachRenderObject
中做了些什么:
- attachRenderObject(newSlot)
@override
void attachRenderObject(Object? newSlot) {
_slot = newSlot;
//查找祖先节点的`RenderObjectElement`
_ancestorRenderObjectElement = _findAncestorRenderObjectElement();
//调用祖先节点的`insertRenderObjectChild`方法,把当前`renderObject`插入
_ancestorRenderObjectElement?.insertRenderObjectChild(renderObject, newSlot);
//查找最近的ParentDataElement
final ParentDataElement<ParentData>? parentDataElement = _findAncestorParentDataElement();
if (parentDataElement != null)
//将父节点的数据通过ParentData存储在子节点中
_updateParentData(parentDataElement.widget);
}
- 查找祖先节点的
RenderObjectElement
,调用祖先节点的insertRenderObjectChild
方法,把当前renderObject
插入。-
insertRenderObjectChild
方法由子类(如:MultiChildRenderObjectElement
、SingleChildRenderObjectElement
)来实现。最终都会调用RenderObject
的adoptChild(child)
方法。
-
- 查找最近的
ParentDataElement
,将父节点的数据通过ParentData
存储在子节点中。如:Stack
和Position
3. RenderObject
- 是什么?
-
RenderObject
是AbstractNode
的子类,同时实现了HitTestTarget
接口来处理点击事件。 - 负责真正的渲染工作
-
abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget {
...
}
下面来先来看下其父类AbstractNode
:
3.1 AbstractNode
AbstractNode
是对树节点的抽象类,下面看下它的一些重要方法:
class AbstractNode {
int get depth => _depth;
int _depth = 0;
@protected
void redepthChild(AbstractNode child) {...}
@mustCallSuper
void attach(covariant Object owner) {...}
...
AbstractNode _parent;
@protected
@mustCallSuper
void adoptChild(covariant AbstractNode child) {...}
@protected
@mustCallSuper
void dropChild(covariant AbstractNode child) {...}
}
AbstractNode
定义了树的深度、父节点、是否attached。提供了一些方法可供子类重写实现,如上面RenderObjectElement
在attachRenderObject()
时会调用adoptChild(...)
方法:
adoptChild(AbstractNode child)
@protected
@mustCallSuper
void adoptChild(covariant AbstractNode child) {
...
child._parent = this;
if (attached)
child.attach(_owner!);
redepthChild(child);
}
再看下RenderObject
中的实现
3.2 RenderObject
abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget {
...
@override
void adoptChild(RenderObject child) {...}
@override
PipelineOwner? get owner => super.owner as PipelineOwner?;
@override
void attach(PipelineOwner owner) {...}
void markNeedsLayout() {...}
void layout(Constraints constraints, { bool parentUsesSize = false }) {...}
void paint(PaintingContext context, Offset offset) {...}
...
}
adoptChild(...)
@override
void adoptChild(RenderObject child) {
...
setupParentData(child);
markNeedsLayout();
markNeedsCompositingBitsUpdate();
markNeedsSemanticsUpdate();
super.adoptChild(child);
}
重写了父类的adoptChild
方法,子类在更改了其子列表时会调用,这里面有个比较重要的方法:markNeedsLayout()
markNeedsLayout()
void markNeedsLayout() {
...
if (_relayoutBoundary != this) {
markParentNeedsLayout();
} else {
_needsLayout = true;
if (owner != null) {
...
owner!._nodesNeedingLayout.add(this);
owner!.requestVisualUpdate();
}
}
}
-
_relayoutBoundary
: 重绘边界- 当重绘边界为当前
RenderObject
时,标记_needsLayout
为true;添加当前RenderObject
到PipelineOwner
的_nodesNeedingLayout
列表中。-
PipelineOwner
为该节点的所有者,所有子树拥有同一个owner。
-
- 如果边界为父节点,则调用
markParentNeedsLayout()
-> 父节点的markNeedsLayout()
- 当重绘边界为当前
PipelineOwner 渲染管线
PipelineOwner
类管理着渲染管道,并提供一些接口来驱动渲染管道。PipelineOwner
存储着需要渲染节点的RenderObject
。
要刷新管道,要依次调用:flushLayout()
、flushCompositingBits()
、flushPaint()
、flushSemantics()
。
在Element
刷新流程中,会进入WidgetsBinding
的drawFrame()
方法中,也印证了PipelineOwner
的流程:
@protected
void drawFrame() {
assert(renderView != null);
pipelineOwner.flushLayout();
pipelineOwner.flushCompositingBits();
pipelineOwner.flushPaint();
if (sendFramesToEngine) {
renderView.compositeFrame();
pipelineOwner.flushSemantics();
_firstFrameSent = true;
}
}
- flushLayout()
void flushLayout() {
...
try {
while (_nodesNeedingLayout.isNotEmpty) {
final List<RenderObject> dirtyNodes = _nodesNeedingLayout;
_nodesNeedingLayout = <RenderObject>[];
for (final RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => a.depth - b.depth)) {
if (node._needsLayout && node.owner == this)
node._layoutWithoutResize();
}
}
} finally {
_debugDoingLayout = false;
...
}
}
深度优先遍历_nodesNeedingLayout
列表,调用RenderObject
的_layoutWithoutResize
方法。
在RenderObject
的_layoutWithoutResize
方法内,必须调用layout()
方法:
void layout(Constraints constraints, { bool parentUsesSize = false }) {
...
if (!parentUsesSize || sizedByParent || constraints.isTight || parent is! RenderObject) {
relayoutBoundary = this;
} else {
relayoutBoundary = (parent as RenderObject)._relayoutBoundary;
}
...
_relayoutBoundary = relayoutBoundary;
...
if (sizedByParent) {
...
performResize();
...
}
...
performLayout();
...
_needsLayout = false;
markNeedsPaint();
}
在子类的performResize()
时,确定了布局的大小,之后再去调用markNeedsPaint()
:如果isRepaintBoundary
为false,则一直向父节点遍历,直到为true停止,并将当前render加入owner的_nodesNeedingPaint
列表中。
- flushCompositingBits()
void flushCompositingBits() {
...
_nodesNeedingCompositingBitsUpdate.sort((RenderObject a, RenderObject b) => a.depth - b.depth);
for (final RenderObject node in _nodesNeedingCompositingBitsUpdate) {
if (node._needsCompositingBitsUpdate && node.owner == this)
node._updateCompositingBits();
}
_nodesNeedingCompositingBitsUpdate.clear();
...
}
在该方法中,更新视图层合成信息。
- flushPaint()
void flushPaint() {
...
_debugDoingPaint = true;
...
final List<RenderObject> dirtyNodes = _nodesNeedingPaint;
_nodesNeedingPaint = <RenderObject>[];
// Sort the dirty nodes in reverse order (deepest first).
for (final RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => b.depth - a.depth)) {
assert(node._layer != null);
if (node._needsPaint && node.owner == this) {
if (node._layer!.attached) {
PaintingContext.repaintCompositedChild(node);
} else {
node._skippedPaintingOnLayer();
}
}
}
...
_debugDoingPaint = false;
}
同样是深度优先遍历_nodesNeedingPaint
列表,调用每个节点的paint
方法进行重绘,生成Layer。
在Layer
中保存了绘制结果,来避免不必要的重绘,只有遇到isRepaintBoundary
为true,即为重绘边界时,才会生成新的Layer。
PaintingContext
为进行绘制的对象,它会持有一个Canvas
,对node进行重绘。
- 在此之后,就能得到
Layer
树,之后就是渲染引擎做的事了。在上面drawframe()
中看到,调用renderView.compositeFrame()
来把bits数据传给引擎。
二、流程总结
从应用启动进入RunApp后的流程大致如下:
Flutter Framework:
创建RootWidget - RenderObjectToWidgetAdapter,创建对应的
_renderViewElement
;-
调用
element
的mount()
方法。在ComponentElement
中,最终会调用performRebuild()
方法 ——Stateless
执行widget
的build()
方法;StatefulElement
调用state.build
。updateChild(...)
根据传入参数不同,有四种情况,返回Element? _child
。当child element为null时,调用
nflateWidget(newWidget, newSlot)
。进入循环创建子element的过程。 若当前
element
继承于RenderObjectElement
, 如常见的SingleChildRenderObjectElement
、MultiChildRenderObjectElement
,mount()
方法处理执行上述流程外,还会创建RenderObject
,执行attachRenderObject(newSlot)
去构建render
树;markNeedsLayout()
- 不断找重回边界,找到后,将当前节点的RenderObject
添加到PipelineOwner
维护的_nodesNeedingLayout
列表中;执行
Engine
的scheduleFrame()
方法。
Engine:
- 执行
scheduleFrame()
后注册VSync
信号回调 - Engine触发
Flutter Framework
中RenderBinding
的drawFrame()
方法;
Flutter Framework:
1.drawFrame()
后依次经过Build
、Layout
、Paint
后生成LayerTree
。
Engine:
- 由
Skia
将LayerTree数据加工成GUP数据 - 通过OpenGL交给GPU进行渲染
三、iOS与Fuller渲染对比
1. iOS渲染流程
第一步,更新视图树,同步更新图层树。
第二步,CPU 计算要显示的内容、图像解码转换。当runloop 在
BeforeWaiting和
Exit时,会通知注册的监听,然后对图层打包,打完包后,将打包数据发送给一个独立负责渲染的进程
Render Server`。
第三步,Render Server
将数据反序列化,得到图层树。按照图层树中图层顺序、RGBA 值、图层 frame 过滤图层中被遮挡的部分,过滤后将图层树转成渲染树,渲染树的信息会转给 OpenGL ES/Metal。
第四步,Render Server
会调用 GPU,GPU 开始进行顶点着色器、形状装配、几何着色器、光栅化、片源着色器、测试与混合六个阶段。
第五步,将GPU渲染结果放到帧缓冲区,当下个Vsync信号时,从帧缓冲区取出放到屏幕。
2. flutter
- Flutter由Skia绘制引擎提供提供图形绘制能力,向GPU提供数据。(替代了iOS中的Core Graphics、Core Animation、Core Text)