抛开 Diff 和 Render 我们本文不讲解,因为这两部分稍稍繁琐一些,我们来关注下剩下的四个环节。
绘制流程
注:此流程图出自 复杂业务如何保证Flutter的高性能高流畅度?| 闲鱼技术,可以较为清晰的表达 Flutter 核心的绘制流程了。
1.4.1 Build
执行 build 方法时,根据组件的类型,存在两种不同的逻辑。
我们知道,Flutter 内的 Widget 可以分为 StatelessWidget 与 StatefulWidget,即无状态组件与有状态组件。
所谓 StatelessWidget,就是它 build 的信息完全由配置参数(入参)组成,换句话说,它们一旦创建成功就不再关心、也不响应任何数据变化进行重绘。
StatelessWidget
所谓 StatefulWidget,除了父组件初始化时传入的静态配置之外,还要处理用户的交互与内部数据变化(如网络数据回包)并体现在 UI 上,这类组件就需要以 State 类打来 Widget 构建的设计方式来实现。它由 State 的 build 方法构建 UI, 最终调用 buildScope
方法。其会遍历 _dirtyElements
,对其调用 rebuild/build。
StatefulWidget
注:以上两图出自 《Flutter 核心技术与实战 | 陈航》
1.4.2 Layout
只有布局类 Widget 会触发 layout(如 Container、Padding、Align 等)。
每个 RenderObject 节点需要做两件事:
-
调用自己的 performLayout 来计算 layout
-
调用 child 的 layout,把 parent 的限制传入
/// 实际计算 layout 的实现void performLayout() { _size = configuration.size; if (child != null) { child.layout(BoxConstraints.tight(_size)); }}void layout(Constraints constraints, { bool parentUsesSize = false }) { /// …省略无关逻辑 RenderObject relayoutBoundary; if (!parentUsesSize || sizedByParent || constraints.isTight || parent is! RenderObject) { relayoutBoundary = this; } else { relayoutBoundary = (parent as RenderObject)._relayoutBoundary; } if (!_needsLayout && constraints == _constraints && relayoutBoundary == _relayoutBoundary) { return; } _constraints = constraints; _relayoutBoundary = relayoutBoundary; if (sizedByParent) { performResize(); } RenderObject debugPreviousActiveLayout; performLayout(); markNeedsSemanticsUpdate(); _needsLayout = false; markNeedsPaint();}
如此递归一轮,每个节点都受到父节点的约束并计算出自己的 size,然后父节点就可以按照自己的逻辑决定各个子节点的位置,从而完成整个 Layout 环节。
layout
1.4.3 Paint
渲染管道中首先找出需要重绘的 RenderObject,如果有实现了 CustomPainter 则调用 CustomPainter paint 方法 再调用 child 的 paint 方法;如果未实现 CustomPainter,则直接调用 child 的 paint。
在调用 paint 的时候,经过一串的转换后,layer->PaintingContext->Canvas
,最终 paint 就是描绘在 Canvas 上。
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); }}// 在父类的 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); // 在调用 paint 的时候,经过一串的转换后,layer->PaintingContext->Canvas,最终 paint 就是描绘在 Canvas 上 painter.paint(canvas, size); /// … canvas.restore();}
1.4.4 Composite
合成主要做三件事情:
-
把所有 Layer 组合成 Scene
-
通过
ui.window.render
方法,把 Scene 提交给 Engine。 -
Engine 把计算所有的 Layer 最终的显示效果,渲染到屏幕上。
final ui.Window _window;void compositeFrame() { // 省略计时逻辑 final ui.SceneBuilder builder = ui.SceneBuilder(); final ui.Scene scene = layer.buildScene(builder); if (automaticSystemUiAdjustment) _updateSystemChrome(); _window.render(scene); scene.dispose();}void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) { addChildrenToScene(builder);}void addChildrenToScene(ui.SceneBuilder builder, [ Offset childOffset = Offset.zero ]) { Layer child = firstChild; while (child != null) { if (childOffset == Offset.zero) { child._addToSceneWithRetainedRendering(builder); } else { child.addToScene(builder, childOffset); } child = child.nextSibling; }}
2. 跨端方案对比
跨端开发是必然趋势,从本质上来说,它增加业务代码的复用率,减少因为适配不