Flutter渲染流程简析
作者:腾讯NOW直播 - levinyang(杨亦伟)
前言
Flutter是谷歌的移动UI框架,可以快速在iOS和Android上构建高质量的原生用户界面。 Flutter可以与现有的代码一起工作。在全世界,Flutter正在被越来越多的开发者和组织使用,并且Flutter是完全免费、开源的。本文主要讲述Flutter整个渲染流程,重点关注在渲染过程中Framework层,从setState到向Engine提交Layer整个过程是怎么样实现的,让读者更加深入理解Flutter的渲染过程。
渲染框架
Flutter的框架分为Framework和Engine两层,应用是基于Framework层开发的,Framework负责渲染中的Build,Layout,Paint,生成Layer等环节。Engine层是C++实现的渲染引擎,负责把Framework生成的Layer组合,生成纹理,然后通过Open GL接口向GPU提交渲染数据。

渲染过程
当需要更新UI的时候,Framework通知Engine,Engine会等到下个Vsync信号到达的时候,会通知Framework,然后Framework会进行animations, build,layout,compositing,paint,最后生成layer提交给Engine。Engine会把layer进行组合,生成纹理,最后通过Open Gl接口提交数据给GPU, GPU经过处理后在显示器上面显示。整个流程如下图:


渲染触发
接下来我们先分析一下当有UI需要更新的时候,是怎么样触发渲染,从应用到Framework,再到Engine这个过程是怎么样的。在Flutter开发应用的时候,当需要更新的UI的时候,需要调用一下setState方法,然后就可以实现了UI的更新,我们接下来分析一下该方法做哪些事情。
void setState(VoidCallback fn) {
...
_element.markNeedsBuild(); //通过相应的element来实现更新,关于element,widget,renderOjbect这里不展开讨论
}
void markNeedsBuild() {
...
if (dirty)
return;
_dirty = true;
owner.scheduleBuildFor(this);
}
void scheduleBuildFor(Element element) {
...
if (!_scheduledFlushDirtyElements && onBuildScheduled != null) {
_scheduledFlushDirtyElements = true;
onBuildScheduled(); //这是一个callback,调用的方法是下面的_handleBuildScheduled
}
_dirtyElements.add(element); //把当前element添加到_dirtyElements数组里面,后面重新build会遍历这个数组
element._inDirtyList = true;
}
void _handleBuildScheduled() {
...
ensureVisualUpdate();
}
void ensureVisualUpdate() {
switch (schedulerPhase) {
case SchedulerPhase.idle:
case SchedulerPhase.postFrameCallbacks:
scheduleFrame();
return;
case SchedulerPhase.transientCallbacks:
case SchedulerPhase.midFrameMicrotasks:
case SchedulerPhase.persistentCallbacks:
return;
}
}
void scheduleFrame() {
if (_hasScheduledFrame || !_framesEnabled)
return;
...
ui.window.scheduleFrame();
_hasScheduledFrame = true;
}
void scheduleFrame() native 'Window_scheduleFrame';//这个方法是Engine实现的,把接口暴露给Framework,调用这个方法通知引擎,需要更新UI,引擎会在下一个vSync的到达的时候通知Framework
渲染过程
当应用调用setState后,经过Framework一连串的调用后,最终调用scheduleFrame来通知Engine需要更新UI,Engine就会在下个vSync到达的时候通过调用_drawFrame来通知Framework,然后Framework就会通过BuildOwner进行Build和PipelineOwner进行Layout,Paint,最后把生成Layer,组合成Scene提交给Engine。接下来我们从代码中分析一下,这些环节具体是怎么样实现的。首先从Engine回调Framework的入口开始。
void _drawFrame() { //Engine回调Framework入口
_invoke(window.onDrawFrame, window._onDrawFrameZone);
}
//初始化的时候把onDrawFrame设置为_handleDrawFrame
void initInstances() {
super.initInstances();
_instance = this;
ui.window.onBeginFrame = _handleBeginFrame;
ui.window.onDrawFrame = _handleDrawFrame;
SystemChannels.lifecycle.setMessageHandler(_handleLifecycleMessage);
}
void _handleDrawFrame() {
if (_ignoreNextEngineDrawFrame) {
_ignoreNextEngineDrawFrame = false;
return;
}
handleDrawFrame();
}
void handleDrawFrame() {
_schedulerPhase = SchedulerPhase.persistentCallbacks;//记录当前更新UI的状态
for (FrameCallback callback in _persistentCallbacks)
_invokeFrameCallback(callback, _currentFrameTimeStamp);
}
}
void initInstances() {
....
addPersistentFrameCallback(_handlePersistentFrameCallback);
}
void _handlePersistentFrameCallback(Duration timeStamp) {
drawFrame();
}
void drawFrame() {
...
if (renderViewElement != null)
buildOwner.buildScope(renderViewElement); //先重新build widget
super.drawFrame();
buildOwner.finalizeTree();
}
void drawFrame() { //这个方法完成Layout,CompositingBits,Paint,生成Layer和提交给Engine的工作
assert(renderView != null);
pipelineOwner.flushLayout();
pipelineOwner.flushCompositingBits();
pipelineOwner.flushPaint();
renderView.compositeFrame(); //生成Layer并提交给Engine
pipelineOwner.flushSemantics();
}
从上面代码分析得知,从Engine回调,Framework会build,Layout,Paint,生成Layer等环节。接下来具体分析一下,这些环节是怎么实现的。
Build
在Flutter应用开发中,无状态的widget是通过StatelessWidget的build方法构建UI,有状态的widget是通过State的build方法构建UI。现在具体分析一下从setState调用后到调用自定义State的build的流程是怎样的(现在只分析有状态的widget渲染过程)。
//这是官方的demo
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
//这里就是构建UI,当调用setState后就会调用到这里,重新生成新的widget
@override
Widget build(BuildContext context) {
return new Scaffold(
...
);
}
}
//从上面代码的分析到,在调用了setState后,最终会调用到buildScope来build
void buildScope(Element context, [VoidCallback callback]) {
...
_dirtyElements.sort(Element._sort);
_dirtyElementsNeedsResorting = false;
int dirtyCount = _dirtyElements.length;
int index = 0;
while (index < dirtyCount) {
...
_dirtyElements[index].rebuild();
index += 1;
}
for (Element element in _dirtyElements) {
assert(element._inDirtyList);
element._inDirtyList = false;
}
_dirtyElements.clear();
}
void rebuild() {
...
if (!_active || !_dirty)
return;
performRebuild();
}
void performRebuild() {
...
built = build();
...
}
Widget build() => widget.build(this);
从上面可以看出,buildScope会遍历_dirtyElements,对每个在数组里面的每个element调用rebuild,最终就是调用到相应的widget的build方法。 其实当setState的时候会把相应的element添加到_dirtyElements数组里,并且element标识dirty状态。
Layout
在Flutter中应用中,是使用支持layout的widget来实现布局的,支持layout的wiget有Container,Padding,Align等等,强大又简易。在渲染流程中,在widget build后会进入layout环节,下面具体分析一下layout的实现,layout入口是flushLayout。
void flushLayout() {
...
while (_nodesNeedingLayout.isNotEmpty) {
final List<RenderObject> dirtyNodes = _nodesNeedingLayout;
_nodesNeedingLayout = <RenderObject>[];
for (RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => a.depth - b.depth)) {//这里是按照在node tree中的深度顺序遍历_nodesNeedingLayout,RenderObject的markNeedsLayout方法会把自己添加到_nodesNeedingLayout
if (node._needsLayout && node.owner == this)//对于需要layout的RenderObject进行layout
node._layoutWithoutResize();
}
}
...
}
void _layoutWithoutResize() {
...
performLayout(); //这个方法是计算layout的实现,不同layout widget有不同的实现
markNeedsSemanticsUpdate();
...
_needsLayout = false;
markNeedsPaint();
}
//这里就是列出来RenderView的计算布局的实现方式,这个比较简单,就是读取配置里面的大小,然后调用child的layout,其他widget layout的计算布局的方式是非常繁琐复杂的,可以自行分析代码
void performLayout() {
assert(_rootTransform != null);
_size = configuration.size;
assert(_size.isFinite);
if (child != null)
child.layout(new BoxConstraints.tight(_size));//调用child的layout
}
//这个方法parent调用child的layout的入口,parent会把限制传给child,child根据限制来layout
void layout(Constraints constraints, { bool parentUsesSize: false }) {
...
RenderObject relayoutBoundary;
if (!parentUsesSize || sizedByParent || constraints.isTight || parent is! RenderObject) {
relayoutBoundary = this;
} else {
final RenderObject parent = this.parent;
relayoutBoundary = parent._relayoutBoundary;
}
if (!_needsLayout && constraints == _constraints && relayoutBoundary == _relayoutBoundary) {
return;
}
_constraints = constraints;
_relayoutBoundary = relayoutBoundary;
if (sizedByParent) {
performResize();
}
RenderObject debugPreviousActiveLayout;
performLayout();//实际计算layout的实现
markNeedsSemanticsUpdate();
_needsLayout = false;
markNeedsPaint();
}
void performResize() {
...
size = constraints.biggest;
switch (axis) {
case Axis.vertical:
offset.applyViewportDimension(size.height);
break;
case Axis.horizontal:
offset.applyViewportDimension(size.width);
break;
}
}
//这是标记为layout为dirty,把自己添加到渲染管道(PipelineOwner)里面
void markNeedsLayout() {
if (_relayoutBoundary != this) {
markParentNeedsLayout();
} else {
_needsLayout = true;
if (owner != null) {
return true;
}());
owner._nodesNeedingLayout.add(this);
owner.requestVisualUpdate();
}
}
}
从上面分析出来,layout的整个过程,首先是当RenderOjbect需要重新layout的时候,把自己添加到渲染管道里面,然后再触发渲染到了layout环节,先从渲染管道里面遍历找出需要渲染的RenderObject,然后调用performLayout进行计算layout,而且不同的对象实现不同的performLayout方法,计算layout的方式也不一样,然后再调用child 的layout入口,同时把parent的限制也传给child,child调用自己的performLayout。
Paint
当需要描绘自定义的图像的时候,可以通过继承CustomPainter,实现paint方法,然后在paint方法里面使用Flutter提供接口可以实现复杂的图像。 下面具体分析一下paint流程是怎么实现的。
//这是官方的paint demo
class Sky extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
var rect = Offset.zero & size;
var gradient = new RadialGradient(
center: const Alignment(0.7, -0.6),
radius: 0.2,
colors: [const Color(0xFFFFFF00), const Color(0xFF0099FF)],
stops: [0.4, 1.0],
);
canvas.drawRect(
rect,
new Paint()..shader = gradient.createShader(rect),
);
}
@override
bool shouldRepaint(Sky oldDelegate) => false;
}
//这是在渲染管道中paint的入口
void flushPaint() {
final List<RenderObject> dirtyNodes = _nodesNeedingPaint;
_nodesNeedingPaint = <RenderObject>[];
// Sort the dirty nodes in reverse order (deepest first).
for (RenderObject node in dirtyNodes..sort((RenderObject a, RenderObject b) => b.depth - a.depth)) { //这是实现的方式和layout过程基本类似,不过排序是反序的
assert(node._layer != null);
if (node._needsPaint && node.owner == this) {
if (node._layer.attached) {
PaintingContext.repaintCompositedChild(node);
} else {
node._skippedPaintingOnLayer();
}
}
}
}
static void repaintCompositedChild(RenderObject child, { bool debugAlsoPaintedParent: false }) {
...
if (child._layer == null) {
child._layer = new OffsetLayer();
} else {
child._layer.removeAllChildren();
}
final PaintingContext childContext = new PaintingContext._(child._layer, child.paintBounds); //通过layer生成 painting context
child._paintWithContext(childContext, Offset.zero);
childContext._stopRecordingIfNeeded();
}
void _paintWithContext(PaintingContext context, Offset offset) {
...
paint(context, offset);
...
}
void paint(PaintingContext context, Offset offset) {
if (_painter != null) { //只有持有CustomPainter情况下,才继续往下调用自定义的CustomPainter的paint方法,把canvas传过去
_paintWithPainter(context.canvas, offset, _painter);
_setRasterCacheHints(context);
}
super.paint(context, offset); //调用父类的paint的方法
if (_foregroundPainter != null) {
_paintWithPainter(context.canvas, offset, _foregroundPainter);
_setRasterCacheHints(context);
}
}
//super paint 在父类的paint里面继续调用child的paint,实现父子遍历
void paint(PaintingContext context, Offset offset) {
if (child != null){
context.paintChild(child, offset);
}
void _paintWithPainter(Canvas canvas, Offset offset, CustomPainter painter) {
int debugPreviousCanvasSaveCount;
canvas.save();
if (offset != Offset.zero)
canvas.translate(offset.dx, offset.dy);
painter.paint(canvas, size);//,在调用paint的时候,经过一串的转换后,layer->PaintingContext->Canvas,最终paint就是描绘在Canvas上
...
canvas.restore();
}
总结来说,paint过程中,渲染管道中首先找出需要重绘的RenderObject,然后如果有实现了CustomPainter,就是调用CustomPainter paint方法,再去调用child的paint方法。
Composited Layer
void compositeFrame() {
Timeline.startSync('Compositing', arguments: timelineWhitelistArguments);
try {
final ui.SceneBuilder builder = new ui.SceneBuilder();
layer.addToScene(builder, Offset.zero);
final ui.Scene scene = builder.build();
ui.window.render(scene);
scene.dispose();
assert(() {
if (debugRepaintRainbowEnabled || debugRepaintTextRainbowEnabled)
debugCurrentRepaintColor = debugCurrentRepaintColor.withHue(debugCurrentRepaintColor.hue + 2.0);
return true;
}());
} finally {
Timeline.finishSync();
}
}
void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
addChildrenToScene(builder, offset + layerOffset);
}
void addChildrenToScene(ui.SceneBuilder builder, Offset childOffset) {
Layer child = firstChild;
while (child != null) {
child.addToScene(builder, childOffset);
child = child.nextSibling;
}
}
Composited Layer就是把所有layer组合成Scene,然后通过ui.window.render方法,把scene提交给Engine,到这一步,Framework向Engine提交数据基本完成了。Engine会把所有的layer根据大小,层级,透明度计算出最终的显示效果,通过Openg Gl接口渲染到屏幕上。
总结
本文结合Flutter的官方描绘的框架和渲染流程,简要介绍了渲染的过程实现方式,让读者对Flutter在渲染方面有基本的理解,便于以后的开发和探索。Now直播终端团队致力于为Flutter生态作出一点自己的贡献,期待Flutter越来越好!
转载自:https://juejin.cn/post/6844903661248708615