Wrappres' Studio.

Flutter 原理

字数统计: 2.9k阅读时长: 12 min
2020/10/05 Share

Flutter 原理

UI 系统

无论是 Android SDK 还是 iOS 的 UIKit 的职责都是相同的,它们只是语言载体和底层的系统不同而已。Flutter 提供了一套 Dart API,然后在底层通过 OpenGL 这种跨平台的绘制库(内部会调用操作系统 API)实现了一套代码跨多端。由于 Dart API 也是调用操作系统 API,所以它的性能接近原生。

Element 与 BuildContext

组件最终的 Layout、渲染都是通过 RenderObject 来完成的,从创建到渲染的大体流程是:根据 Widget 生成 Element,然后创建相应的 RenderObject 并关联到 Element.renderObject 属性上,最后再通过 RenderObject 来完成布局排列和绘制。

Element 生命周期

  1. Framework 调用 Widget.createElement 创建一个 Element 实例,记为 element

  2. Framework 调用 element.mount(parentElement,newSlot) ,mount 方法中首先调用 element 所对应 Widget 的 createRenderObject 方法创建与 element 相关联的 RenderObject 对象,然后调用 element.attachRenderObject 方法将 element.renderObject 添加到渲染树中插槽指定的位置(这一步不是必须的,一般发生在 Element 树结构发生变化时才需要重新 attach)。插入到渲染树后的 element 就处于“active”状态,处于“active”状态后就可以显示在屏幕上了(可以隐藏)。

  3. 当有父 Widget 的配置数据改变时,同时其 State.build 返回的 Widget 结构与之前不同,此时就需要重新构建对应的 Element 树。为了进行 Element 复用,在 Element 重新构建前会先尝试是否可以复用旧树上相同位置的 element,element 节点在更新前都会调用其对应 Widget 的 canUpdate 方法,如果返回 true,则复用旧 Element,旧的 Element 会使用新 Widget 配置数据更新,反之则会创建一个新的 Element。Widget.canUpdate 主要是判断 newWidget 与 oldWidget 的 runtimeType 和 key 是否同时相等,如果同时相等就返回 true,否则就会返回 false。根据这个原理,当我们需要强制更新一个 Widget 时,可以通过指定不同的 Key 来避免复用。

  4. 当有祖先 Element 决定要移除 element 时(如 Widget 树结构发生了变化,导致 element 对应的 Widget 被移除),这时该祖先 Element 就会调用 deactivateChild 方法来移除它,移除后 element.renderObject 也会被从渲染树中移除,然后 Framework 会调用 element.deactivate 方法,这时 element 状态变为“inactive”状态。

  5. “inactive”态的 element 将不会再显示到屏幕。为了避免在一次动画执行过程中反复创建、移除某个特定 element,“inactive”态的 element 在当前动画最后一帧结束前都会保留,如果在动画执行结束后它还未能重新变成“active”状态,Framework 就会调用其 unmount 方法将其彻底移除,这时 element 的状态为 defunct,它将永远不会再被插入到树中。

  6. 如果 element 要重新插入到 Element 树的其它位置,如 element 或 element 的祖先拥有一个 GlobalKey(用于全局复用元素),那么 Framework 会先将 element 从现有位置移除,然后再调用其 activate 方法,并将其 renderObject 重新 attach 到渲染树。

BuildContext

1
Widget build(BuildContext context) {}

BuildContext 就是 widget 对应的 Element,所以我们可以通过 context 在 StatelessWidget 和 StatefulWidget 的 build 方法中直接访问 Element 对象。我们获取主题数据的代码 Theme.of(context)内部正是调用了 Element 的 dependOnInheritedWidgetOfExactType()方法。

  • 如果没有 widget 层,单靠Element层是否可以搭建起一个可用的 UI 框架?如果可以应该是什么样子?

答案当然是肯定的,因为我们之前说过 widget 树只是Element树的映射,我们完全可以直接通过 Element 来搭建一个 UI 框架。

  • Flutter UI 框架能不做成响应式吗?

答案当然也是肯定的,Flutter engine 提供的 dart API 是原始且独立的,这个与操作系统提供的 API 类似,上层 UI 框架设计成什么样完全取决于设计者,完全可以将 UI 框架设计成 Android 风格或 iOS 风格,但这些事 Google 不会再去做,我们也没必要再去搞这一套,这是因为响应式的思想本身是很棒的。

Flutter 运行机制-从启动到显示

启动

1
2
3
void main() {
runApp(MyApp());
}
1
2
3
4
5
void runApp(Widget app) {
WidgetsFlutterBinding.ensureInitialized()
..attachRootWidget(app)
..scheduleWarmUpFrame();
}

WidgetsFlutterBinding是绑定 widget 框架和 Flutter engine 的桥梁。

1
2
3
4
5
6
7
class WidgetsFlutterBinding extends BindingBase with GestureBinding, ServicesBinding, SchedulerBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {
static WidgetsBinding ensureInitialized() {
if (WidgetsBinding.instance == null)
WidgetsFlutterBinding();
return WidgetsBinding.instance;
}
}

Window是 Flutter Framework 连接宿主操作系统的接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class Window {

// 当前设备的DPI,即一个逻辑像素显示多少物理像素,数字越大,显示效果就越精细保真。
// DPI是设备屏幕的固件属性,如Nexus 6的屏幕DPI为3.5
double get devicePixelRatio => _devicePixelRatio;

// Flutter UI绘制区域的大小
Size get physicalSize => _physicalSize;

// 当前系统默认的语言Locale
Locale get locale;

// 当前系统字体缩放比例。
double get textScaleFactor => _textScaleFactor;

// 当绘制区域大小改变回调
VoidCallback get onMetricsChanged => _onMetricsChanged;
// Locale发生变化回调
VoidCallback get onLocaleChanged => _onLocaleChanged;
// 系统字体缩放变化回调
VoidCallback get onTextScaleFactorChanged => _onTextScaleFactorChanged;
// 绘制前回调,一般会受显示器的垂直同步信号VSync驱动,当屏幕刷新时就会被调用
FrameCallback get onBeginFrame => _onBeginFrame;
// 绘制回调
VoidCallback get onDrawFrame => _onDrawFrame;
// 点击或指针事件回调
PointerDataPacketCallback get onPointerDataPacket => _onPointerDataPacket;
// 调度Frame,该方法执行后,onBeginFrame和onDrawFrame将紧接着会在合适时机被调用,
// 此方法会直接调用Flutter engine的Window_scheduleFrame方法
void scheduleFrame() native 'Window_scheduleFrame';
// 更新应用在GPU上的渲染,此方法会直接调用Flutter engine的Window_render方法
void render(Scene scene) native 'Window_render';

// 发送平台消息
void sendPlatformMessage(String name,
ByteData data,
PlatformMessageResponseCallback callback) ;
// 平台通道消息处理回调
PlatformMessageCallback get onPlatformMessage => _onPlatformMessage;

... //其它属性及回调

}
  • GestureBinding:提供了window.onPointerDataPacket 回调,绑定 Framework 手势子系统,是 Framework 事件模型与底层事件的绑定入口。
  • ServicesBinding:提供了window.onPlatformMessage 回调, 用于绑定平台消息通道(message channel),主要处理原生和 Flutter 通信。
  • SchedulerBinding:提供了window.onBeginFramewindow.onDrawFrame回调,监听刷新事件,绑定 Framework 绘制调度子系统。
  • PaintingBinding:绑定绘制库,主要用于处理图片缓存。
  • SemanticsBinding:语义化层与 Flutter engine 的桥梁,主要是辅助功能的底层支持。
  • RendererBinding: 提供了window.onMetricsChangedwindow.onTextScaleFactorChanged 等回调。它是渲染树与 Flutter engine 的桥梁。
  • WidgetsBinding:提供了window.onLocaleChangedonBuildScheduled 等回调。它是 Flutter widget 层与 engine 的桥梁。

WidgetsFlutterBinding.ensureInitialized()负责初始化一个WidgetsBinding的全局单例,紧接着会调用WidgetsBindingattachRootWidget方法,该方法负责将根 Widget 添加到RenderView上,代码如下:

1
2
3
4
5
6
7
void attachRootWidget(Widget rootWidget) {
_renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
container: renderView,
debugShortDescription: '[root]',
child: rootWidget
).attachToRenderTree(buildOwner, renderViewElement);
}

代码中的有renderViewrenderViewElement两个变量,renderView是一个RenderObject,它是渲染树的根,而renderViewElementrenderView对应的Element对象,可见该方法主要完成了根 widget 到根 RenderObject再到根Element的整个关联过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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);
});
} else {
element._newWidget = this;
element.markNeedsBuild();
}
return element;
}

该方法负责创建根 element,即RenderObjectToWidgetElement,并且将 element 与 widget 进行关联,即创建出 widget 树对应的 element 树。如果 element 已经创建过了,则将根 element 中关联的 widget 设为新的,由此可以看出 element 只会创建一次,后面会进行复用。那么BuildOwner是 widget framework 的管理类,它跟踪哪些 widget 需要重新构建。

渲染

回到runApp的实现中,当调用完attachRootWidget后,最后一行会调用 WidgetsFlutterBinding 实例的 scheduleWarmUpFrame() 方法,该方法的实现在SchedulerBinding 中,它被调用后会立即进行一次绘制(而不是等待”vsync” 信号),在此次绘制结束前,该方法会锁定事件分发,也就是说在本次绘制结束完成之前 Flutter 将不会响应各种事件,这可以保证在绘制过程中不会再触发新的重绘。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void scheduleWarmUpFrame() {
...
Timer.run(() {
handleBeginFrame(null);
});
Timer.run(() {
handleDrawFrame();
resetEpoch();
});
// 锁定事件
lockEvents(() async {
await endOfFrame;
Timeline.finishSync();
});
...
}
  • Frame: 一次绘制过程,我们称其为一帧。Flutter engine 受显示器垂直同步信号”VSync”的驱使不断的触发绘制。我们之前说的 Flutter 可以实现 60fps(Frame Per-Second),就是指一秒钟可以触发 60 次重绘,FPS 值越大,界面就越流畅。
  • FrameCallback:SchedulerBinding 类中有三个 FrameCallback 回调队列, 在一次绘制过程中,这三个回调队列会放在不同时机被执行:
    1. transientCallbacks:用于存放一些临时回调,一般存放动画回调。可以通过SchedulerBinding.instance.scheduleFrameCallback 添加回调。
    2. persistentCallbacks:用于存放一些持久的回调,不能在此类回调中再请求新的绘制帧,持久回调一经注册则不能移除。SchedulerBinding.instance.addPersitentFrameCallback(),这个回调中处理了布局与绘制工作。
    3. postFrameCallbacks:在 Frame 结束时只会被调用一次,调用后会被系统移除,可由 SchedulerBinding.instance.addPostFrameCallback() 注册,注意,不要在此类回调中再触发新的 Frame,这可以会导致循环刷新。

绘制

1
2
3
4
5
6
7
8
9
10
11
12
13
void initInstances() {
... //省略无关代码

//监听Window对象的事件
ui.window
..onMetricsChanged = handleMetricsChanged
..onTextScaleFactorChanged = handleTextScaleFactorChanged
..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged
..onSemanticsAction = _handleSemanticsAction;

//添加PersistentFrameCallback
addPersistentFrameCallback(_handlePersistentFrameCallback);
}

通过addPersistentFrameCallbackpersistentCallbacks队列添加了一个回调 _handlePersistentFrameCallback:

1
2
3
void _handlePersistentFrameCallback(Duration timeStamp) {
drawFrame();
}

该方法直接调用了RendererBindingdrawFrame()方法:

1
2
3
4
5
6
7
8
void drawFrame() {
assert(renderView != null);
pipelineOwner.flushLayout(); //布局
pipelineOwner.flushCompositingBits(); //重绘之前的预处理操作,检查RenderObject是否需要重绘
pipelineOwner.flushPaint(); // 重绘
renderView.compositeFrame(); // 将需要绘制的比特数据发给GPU
pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
}

flushLayout()

1
2
3
4
5
6
7
8
9
10
11
12
13
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)) {
if (node._needsLayout && node.owner == this)
node._layoutWithoutResize();
}
}
}
}

该方法主要任务是更新了所有被标记为“dirty”的RenderObject的布局信息。主要的动作发生在node._layoutWithoutResize()方法中,该方法中会调用performLayout()进行重新布局。

flushCompositingBits()

1
2
3
4
5
6
7
8
9
10
void flushCompositingBits() {
_nodesNeedingCompositingBitsUpdate.sort(
(RenderObject a, RenderObject b) => a.depth - b.depth
);
for (RenderObject node in _nodesNeedingCompositingBitsUpdate) {
if (node._needsCompositingBitsUpdate && node.owner == this)
node._updateCompositingBits(); //更新RenderObject.needsCompositing属性值
}
_nodesNeedingCompositingBitsUpdate.clear();
}

检查RenderObject是否需要重绘,然后更新RenderObject.needsCompositing属性,如果该属性值被标记为true则需要重绘。

flushPaint()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void flushPaint() {
...
try {
final List<RenderObject> dirtyNodes = _nodesNeedingPaint;
_nodesNeedingPaint = <RenderObject>[];
// 反向遍历需要重绘的RenderObject
for (RenderObject node in
dirtyNodes..sort((RenderObject a, RenderObject b) => b.depth - a.depth)) {
if (node._needsPaint && node.owner == this) {
if (node._layer.attached) {
// 真正的绘制逻辑
PaintingContext.repaintCompositedChild(node);
} else {
node._skippedPaintingOnLayer();
}
}
}
}
}

该方法进行了最终的绘制,可以看出它不是重绘了所有 RenderObject,而是只重绘了需要重绘的 RenderObject。真正的绘制是通过 PaintingContext.repaintCompositedChild()来绘制的,该方法最终会调用 Flutter engine 提供的 Canvas API 来完成绘制。

compositeFrame()

1
2
3
4
5
6
7
8
9
10
11
12
13
void compositeFrame() {
...
try {
final ui.SceneBuilder builder = ui.SceneBuilder();
final ui.Scene scene = layer.buildScene(builder);
if (automaticSystemUiAdjustment)
_updateSystemChrome();
ui.window.render(scene); //调用Flutter engine的渲染API
scene.dispose();
} finally {
Timeline.finishSync();
}
}

这个方法中有一个Scene对象,Scene 对象是一个数据结构,保存最终渲染后的像素信息。这个方法将 Canvas 画好的Scene传给window.render()方法,该方法会直接将 scene 信息发送给 Flutter engine,最终由 engine 将图像画在设备屏幕上。

1
2
3
4
5
6
7
8
9
10
@override
void drawFrame() {
...//省略无关代码
try {
if (renderViewElement != null)
buildOwner.buildScope(renderViewElement);
super.drawFrame(); //调用RendererBinding的drawFrame()方法
buildOwner.finalizeTree();
}
}

调用RendererBinding.drawFrame()方法前会调用 buildOwner.buildScope() (非首次绘制),该方法会将被标记为“dirty” 的 element 进行 rebuild()

CATALOG
  1. 1. Flutter 原理
    1. 1.1. UI 系统
    2. 1.2. Element 与 BuildContext
      1. 1.2.1. Element 生命周期
      2. 1.2.2. BuildContext
  2. 2. Flutter 运行机制-从启动到显示
    1. 2.1. 启动
    2. 2.2. 渲染
    3. 2.3. 绘制
      1. 2.3.1. flushLayout()
      2. 2.3.2. flushCompositingBits()
      3. 2.3.3. flushPaint()
      4. 2.3.4. compositeFrame()