启动

Flutter的入口在”lib/main.dart”的 main() 函数中,它是Dart应用程序的起点。在Flutter应用中,main() 函数最简单的实现如下

  1. void main() { runApp(MyApp());}

可以看main()函数只调用了一个runApp()方法,我们看看runApp()方法中都做了什么:

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

参数app是一个widget,它是Flutter应用启动后要展示的第一个Widget。而WidgetsFlutterBinding正是绑定widget 框架和Flutter engine的桥梁,定义如下:

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

可以看到 WidgetsFlutterBinding 继承自 BindingBase 并混入了很多Binding,在介绍这些Binding之前我们先介绍一下Window,下面是Window的官方解释:

The most basic interface to the host operating system’s user interface.

很明显Window正是Flutter Framework连接宿主操作系统的接口。我们看一下Window类的部分定义:

  1. class Window {
  2. // 当前设备的DPI,即一个逻辑像素显示多少物理像素,数字越大,显示效果就越精细保真。
  3. // DPI是设备屏幕的固件属性,如Nexus 6的屏幕DPI为3.5
  4. double get devicePixelRatio => _devicePixelRatio;
  5. // Flutter UI绘制区域的大小
  6. Size get physicalSize => _physicalSize;
  7. // 当前系统默认的语言Locale
  8. Locale get locale;
  9. // 当前系统字体缩放比例。
  10. double get textScaleFactor => _textScaleFactor;
  11. // 当绘制区域大小改变回调
  12. VoidCallback get onMetricsChanged => _onMetricsChanged;
  13. // Locale发生变化回调
  14. VoidCallback get onLocaleChanged => _onLocaleChanged;
  15. // 系统字体缩放变化回调
  16. VoidCallback get onTextScaleFactorChanged => _onTextScaleFactorChanged;
  17. // 绘制前回调,一般会受显示器的垂直同步信号VSync驱动,当屏幕刷新时就会被调用
  18. FrameCallback get onBeginFrame => _onBeginFrame;
  19. // 绘制回调
  20. VoidCallback get onDrawFrame => _onDrawFrame;
  21. // 点击或指针事件回调
  22. PointerDataPacketCallback get onPointerDataPacket => _onPointerDataPacket;
  23. // 调度Frame,该方法执行后,onBeginFrame和onDrawFrame将紧接着会在合适时机被调用,
  24. // 此方法会直接调用Flutter engine的Window_scheduleFrame方法
  25. void scheduleFrame() native 'Window_scheduleFrame';
  26. // 更新应用在GPU上的渲染,此方法会直接调用Flutter engine的Window_render方法
  27. void render(Scene scene) native 'Window_render';
  28. // 发送平台消息
  29. void sendPlatformMessage(String name,
  30. ByteData data,
  31. PlatformMessageResponseCallback callback) ;
  32. // 平台通道消息处理回调
  33. PlatformMessageCallback get onPlatformMessage => _onPlatformMessage;
  34. ... //其它属性及回调
  35. }

可以看到Window类包含了当前设备和系统的一些信息以及Flutter Engine的一些回调。现在我们再回来看看WidgetsFlutterBinding混入的各种Binding。通过查看这些 Binding的源码
我们可以发现这些Binding中基本都是监听并处理Window对象的一些事件,然后将这些事件按照Framework的模型包装、抽象然后分发。可以看到WidgetsFlutterBinding正是粘连Flutter engine与上层Framework的“胶水”

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

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

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

    注意,代码中的有renderView和renderViewElement两个变量

  • renderView是一个RenderObject,它是渲染树的根

  • renderViewElement是renderView对应的Element对象

可见该方法主要完成了根widget到根 RenderObject再到根Element的整个关联过程

我们看看attachToRenderTree的源码实现

  1. RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [RenderObjectToWidgetElement<T> element]) {
  2. if (element == null) {
  3. owner.lockState(() {
  4. element = createElement();
  5. assert(element != null);
  6. element.assignOwner(owner);
  7. });
  8. owner.buildScope(element, () {
  9. element.mount(null, null);
  10. });
  11. } else {
  12. element._newWidget = this;
  13. element.markNeedsBuild();
  14. }
  15. return element;
  16. }
  • 该方法负责创建根element,即RenderObjectToWidgetElement
  • 并且将element与widget 进行关联,即创建出 widget树对应的element树
  • 如果element 已经创建过了,则将根element 中关联的widget 设为新的
  • 由此可以看出element 只会创建一次,后面会进行复用
  • 那么BuildOwner是什么呢?其实他就是widget framework的管理类,它跟踪哪些widget需要重新构建!

渲染

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

  1. void scheduleWarmUpFrame() {
  2. ...
  3. Timer.run(() {
  4. handleBeginFrame(null);
  5. });
  6. Timer.run(() {
  7. handleDrawFrame();
  8. resetEpoch();
  9. });
  10. // 锁定事件
  11. lockEvents(() async {
  12. await endOfFrame;
  13. Timeline.finishSync();
  14. });
  15. ...
  16. }

可以看到该方法中主要调用了handleBeginFrame() 和 handleDrawFrame() 两个方法,在看这两个方法之前我们首先了解一下Frame 和 FrameCallback 的概念:

  • 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,这可以会导致循环刷新。

      现在请读者自行查看handleBeginFrame()和handleDrawFrame() 两个方法的源码,可以发现前者主要是执行了transientCallbacks队列,而后者执行了 persistentCallbacks 和 postFrameCallbacks 队列

绘制

渲染和绘制逻辑在RendererBinding中实现,查看其源码,发现在其initInstances()方法中有如下代码:

  1. void initInstances() {
  2. ... //省略无关代码
  3. //监听Window对象的事件
  4. ui.window
  5. ..onMetricsChanged = handleMetricsChanged
  6. ..onTextScaleFactorChanged = handleTextScaleFactorChanged
  7. ..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged
  8. ..onSemanticsAction = _handleSemanticsAction;
  9. //添加PersistentFrameCallback
  10. addPersistentFrameCallback(_handlePersistentFrameCallback);
  11. }

我们看最后一行,通过addPersistentFrameCallback 向persistentCallbacks队列添加了一个回调 _handlePersistentFrameCallback

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

该方法直接调用了RendererBinding的drawFrame()方法:

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

我们看看这些方法分别做了什么:

flushLayout()

  1. void flushLayout() {
  2. ...
  3. while (_nodesNeedingLayout.isNotEmpty) {
  4. final List<RenderObject> dirtyNodes = _nodesNeedingLayout;
  5. _nodesNeedingLayout = <RenderObject>[];
  6. for (RenderObject node in
  7. dirtyNodes..sort((RenderObject a, RenderObject b) => a.depth - b.depth)) {
  8. if (node._needsLayout && node.owner == this)
  9. node._layoutWithoutResize();
  10. }
  11. }
  12. }
  13. }

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

flushCompositingBits()

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

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

flushPaint()

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

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

compositeFrame()

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

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

最后

需要注意的是:由于RendererBinding只是一个mixin,而with它的是WidgetsBinding,所以我们需要看看WidgetsBinding中是否重写该方法,查看WidgetsBinding的drawFrame()方法源码:

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

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

总结

本节介绍了Flutter APP从启动到显示到屏幕上的主流程,读者可以结合前面章节对Widget、Element以及RenderObject的介绍来加强细节理解。