flutter 应用启动流程

2,510 阅读12分钟

Flutter 应用启动流程

在 Flutter 项目中,应用的启动一般是从 main.dart 开始的,

void main() => runApp(App());

App 就是应用的主页面,本质上是一个 Widget,换句话说,在 Flutter 中,万物皆为 Widget,它由开发者编写,runApp 则负责将这个 Widget 运行起来,展示到手机界面上,还有一系列的初始化工作,为之后的持续工作、用户交互做准备。了解 flutter 应用的启动过程,可以从这个函数开始。

runApp

void runApp(Widget app) {
  WidgetsFlutterBinding.ensureInitialized()
    ..attachRootWidget(app)
    ..scheduleWarmUpFrame();
}

从这里可以判断,runApp 大致有三个过程:

  1. 进行 WidgetsFlutterBinding 的初始化
  2. 将用户界面 app 加载到 flutter 的 widget 树中,这有点类似于 Android 中的 setContentView
  3. 执行绘制工作,将 widget 树转变为画面

WidgetsFlutterBinding

WidgetsFlutterBinding 本身没有什么功能,只不过它集成了诸多 binding 类

class WidgetsFlutterBinding extends BindingBase with GestureBinding, ServicesBinding, SchedulerBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {

  /// Returns an instance of the [WidgetsBinding], creating and
  /// initializing it if necessary. If one is created, it will be a
  /// [WidgetsFlutterBinding]. If one was previously initialized, then
  /// it will at least implement [WidgetsBinding].
  ///
  /// You only need to call this method if you need the binding to be
  /// initialized before calling [runApp].
  ///
  /// In the `flutter_test` framework, [testWidgets] initializes the
  /// binding instance to a [TestWidgetsFlutterBinding], not a
  /// [WidgetsFlutterBinding].
  static WidgetsBinding ensureInitialized() {
    if (WidgetsBinding.instance == null)
      WidgetsFlutterBinding();
    return WidgetsBinding.instance;
  }
}

依次是 BindingBase、GestureBinding、ServicesBinding、SchedulerBinding、PaintingBinding、SemanticsBinding、RendererBinding、WidgetsBinding,这里又涉及到 mixin 语法相关的知识,具体的这里不阐述,这种结果导致的结果就是 WidgetsFlutterBinding 拥有以上几个类所有的功能,而涉及到的函数重写相关的,如果按优先级来算,那就是调用函数时,先从 WidgetsBinding 中找是否有实现,如果没有,再从 RendererBinding 中找,以此类推。然后再是 super 的使用,WidgetsBinding 中调用 super 函数会先从 RenderBinding 中找,RenderBinding 调用到 super 会先从 SemanticsBinding 中找,以此类推。

然后, WidgetsFlutterBinding 没有构造函数,直接看从父类的,在 BindingBase 的构造函数中调用了 initInstances 这个函数,它在后面的每一个 binding 中都有实现,所以按照 mixin 的逻辑,应该先调用 WidgetsBinding 的实现,而在这几个 binding 中都有调用 super.initInstances ,所以它会按照从 WidgetsBinding 至 BindingBase 的顺序依次调用。

WidgetsBinding

分别向 BuildOwner 注册了 onBuildScheduled 函数,向 Window 注册了 onLocaleChanged 和 onAccessibilityFeaturesChanged 函数,向 SystemChannels 注册了 MethodCallHandler 和 MessageHandler 函数,后面两个能够接收传递过来的路由消息和系统消息,如 popRoute、pushRoute、memoryPressure 等。

RendererBinding

RendererBinding 中主要是与渲染相关的功能,初始化时创建了 PipelineOwner 实例,这个类就是负责界面渲染的,之后的 layout、paint 等过程 PipelineOwner 都会承担着很多责任,然后又在 Window 中注册了一些函数,接着初始化 RenderView ,还有就是一个比较重要的函数,_handlePersistentFrameCallback,这个函数被调用之后就会执行一系列的绘制操作,将 Widget 树绘制到屏幕中。

还有就是初始化了 RenderView,并将其加入 _nodesNeedingLayout 和 _nodesNeedingPaint,最后调用 requestVisualUpdate 请求刷新视图。

renderView 的 set 函数会将其与 PipelineOwner 联系起来,调用其 attach 函数,RenderView 的父类 RenderObject 重写了这个函数,将 RenderView 标记成需要 layout、compositingBitsUpdate、paint 和 semanticsUpdate。

SemanticsBinding

内部保存有一个 AccessibilityFeatures 实例,从 Window 中取得,Window 中又是从 C++ 中传递过来的,AccessibilityFeatures 本质上就是一个 int 值,然后通过位运算得到配置信息,如是否反转颜色、是否禁止动画、是否显示粗体字等 Android、ios 中的系统配置信息。

PaintingBinding

PaintingBinding 可以认为有两个功能,一个是提供了 ImageCache 类,可以用于缓存图片,另一个是调用了 ShaderWarmUp 的 execute 函数,从意思上看是用于启动 skia(底层图形绘制库)的着色器编译,这一点可能是有预先启动以防使用到再去启动会影响等待时间的意味?

SchedulerBinding

SchedulerBinding 向 Window 注册了两个回调函数,onBeginFrame 和 onDrawFrame ,这两个函数就是在帧到来之后的回调,包括 Widgets 树的刷新和绘制、动画的处理以及其他用户投放的任务,对于 Android 平台来说,这两个函数调用的时机就是 Choreographer 回调之后,另外从功能上来说,它们也是与 Android 中基本一致的。

然后注册了生命周期的回调函数 _handleLifecycleMessage 。

ServicesBinding

向 Window 注册了 onPlatformMessage 函数,后续的开发插件时就是通过这个函数分发,然后每个插件得以接收消息。

GestureBinding

GestureBinding 向 Window 注册了 onPointerDataPacket 函数,这个函数就是原生平台上接收到触摸事件之后回调的,也是 flutter 中触摸手势的提供方,它会在接收到传来的触摸事件之后,将其分发出去。

BindingBase

BindingBase 中则没有实际的功能。

attachRootWidget

attachRootWidget 的参数是 app ,也就是在这个函数中,将用户自定义的 widget 挂载到 flutter 应用的 widget 树中,并生成 element 等,为下一步绘制做准备。

这里首先创建了 RenderObjectToWidgetAdapter 对象,将 widget 作为其 child ,renderView 为 container ,renderView 是在 RendererBinding 初始化时创建的,也是整个渲染树的根节点,然后调用 RenderObjectToWidgetAdapter 的 attachToRenderTree 函数,将会利用 widget 树生成 element 树。

在这个函数中先是创建了根 element ,然后调用 buildScope 和 mount 函数,mount 可以看作是生成 element 树,buildScope 会将所有标记为 dirty 的 element 进行 rebuild,可以刷新视图。

buildScope

buildScope 会先调用 callback ,在这里的 callback 就是 mount ,然后处理 _dirtyElements,先对其按照深度排序,再来一个 while 循环,调用每一个 dirty element 的 rebuild 函数,并且如果在处理的过程中发现 _dirtyElements 的数量发生变化或者需要重新处理的,就会将其重新排序,然后找到第一个 dirty element 继续处理,完了之后就清空 _dirtyElements。

mount

RenderObjectToWidgetElement 的 mount 函数会调用 _rebuild,然后又会调用 updateChild 更新 child ,具体的更新策略就是:

  1. 如果新的 widget 为 null,直接返回 null
  2. child 为 null,调用 inflateWidget 生成 child
  3. child 不为 null
    1. 新的 widget 与旧 widget 相同,只需要更新下 slot
    2. 否则,判断是否可以复用,调用 canUpdate 函数,可以则更新下 slot ,不可以则执行 inflateWidget 重新生成

第一次执行的时候,child 为 null,所以应该直接调用 inflateWidget 去生成。

inflateWidget

函数的参数为 widget 和 slot,首先会判断 widget 的 key 是否为 GlobalKey,如果它是一个 GlobalKey,也就意味着这个 element 是可全局唯一的,会先从 GlobalKey 中查找是否有可复用,没有现成的 element 时才会调用 createElement 生成 element,然后接着调用这个 element 的 mount 函数,与之前的 root element 不同,这里的 element 对应的类一般是 ComponentElement 的子类,而 ComponentElement 有自己实现的 mount ,在它实现的 mount 中会依次调用到 _firstBuild,rebuild 和 performRebuild,在 performRebuild 中,先是生成了一个 widget 对象,接着又调用 updateChild 生成了 widget 对应的 element,由此形成一个调用链,updateChild 中会继续调用 inflateWidget,直到整个 widget 树转化为 element 树之后结束。

scheduleWarmUpFrame

element 树生成之后,绘制的基础就有了,下一步就是调用 scheduleWarmUpFrame 函数将这样一个系统启动运行。scheduleWarmUpFrame 会调用 handleBeginFrame 和 handleDrawFrame 函数开启 flutter 应用的第一帧,后续的帧序列将会由 flutter 自行生成,在 Android 中它会辗转调用到 Android 中的 Choreographer 生成下一帧,然后再通过 C++ 传回 flutter 中,完成自给。

handleBeginFrame

在 handleBeginFrame 中主要是执行 _transientCallbacks,即调用 SchedulerBinding:scheduleFrameCallback 函数添加的回调,比如 Ticker 中的 scheduleTick 就通过这个函数延迟调用 _tick 函数。

handleDrawFrame

handleDrawFrame 与 handleBeginFrame 类似,只不过执行了 _persistentCallbacks 和 _postFrameCallbacks 两类回调,前者为固有回调,比如在 RendererBinding 初始化中添加的 _handlePersistentFrameCallback,后者为用户需要在绘制之后执行的回调。

综上,在每一帧中需要处理三种回调,除了 _persistentCallbacks 每一帧都要调用之外,其他的都是一次性的。

_handlePersistentFrameCallback

这个函数会调用 drawFrame 函数,drawFrame 在 WidgetsBinding 和 RendererBinding 中分别有实现,根据 mixin 机制,调用的应该是 WidgetsBinding 中的实现,其功能大致分为三部分:

  1. 调用 buildOwner.buildScope 刷新 element 树
  2. 调用 RendererBinding drawFrame 函数
  3. 调用 buildOwner.finalizeTree 清理无效 element

1 中之前有说到过,就是 rebuild 所有的 dirty element,3 中会以每一个 dirty element 为起点,调用 element 及其所有 child 的 unmount 函数。

drawFrame

drawFrame 这个函数完成了本地各 widget 的绘制,以及将其渲染到屏幕上的整个过程,共五个过程:

  1. flushLayout
  2. flushCompositingBits
  3. flushPaint
  4. compositeFrame
  5. flushSemantics

第四个是 RenderView 的函数,其他都是 PipelineOwner 的函数,PipelineOwner 在 RendererBinding 的初始化过程中被创建。

flushLayout

这个过程完成所有 widget 的布局计算,通过调用所有需要重新布局的结点的 _layoutWithoutResize 函数,这个函数又分为三个部分调用:

  1. performLayout
  2. markNeedsSemanticsUpdate
  3. markNeedsPaint

1 过程确定 widget 的大小和位置,2 过程将 widget _needsSemanticsUpdate 标记为 true,并从这个节点上溯到边界节点,然后将这个节点入到 PipelineOwner 的 _nodesNeedingSemantics 中,并调用 requestVisualUpdate 函数,requestVisualUpdate 中调用的 onNeedVisualUpdate 在 PipelineOwnder 初始化时传入,这个函数会在空闲状态或 postFrameCallbacks 状态请求下一帧,总之,2 过程的标记会影响 flushSemantics 过程的执行。3 过程则会从 widget 上溯到边界节点,将这个节点加入到 _nodesNeedingPaint 中,这个过程会影响 flushPaint 的执行。

flushCompositingBits

更新节点的 composition bits。

flushPaint

遍历所有需要绘制的节点,如果节点没有挂载到 widget 树中,则跳过,否则就调用 PaintingContext.repaintCompositedChild 函数,然后依次调用 _paintWithContext、paint 函数,paint 函数一般是 widget 自由实现,可以通过 PaintingContext 取得 Canvas ,并在 Canvas 上绘制任何内容,其使用与 Android 中的 draw 方法无二。

compositeFrame

将 compositing bits 发送到 GPU。

flushSemantics

将 semantics 发送到操作系统。

关于刷新帧,可以以 setState 为例,看一下 flutter 中是如何从 Android 原生中获取下一帧回调的。

setState

开发过 flutter 都知道,当我们需要改变视图的时候不能直接操作 Widget 或 Element ,而是改变数据,通过数据刷新驱动 widget 树刷新,生成新的 element 树,从而显示出新的界面,而更新数据需要放在 setState 中,否则就会只有数据的更新,而不会带动视图更新,所以从这一点可以猜测,setState 中做了某些处理,以至最终调用到 drawFrame,从而刷新视图,那么下面就分析下从调用 setState 到刷新视图的全过程。

setState 的参数是一个 callback,在 setState 中会先调用 callback ,然后接着调用 markNeedsBuild ,前者一般是开发者用于改变数据的,那么就只能从 markNeedsBuild 入手。

markNeedsBuild

这个函数会将 element 标记为 dirty,然后调用 scheduleBuildFor 函数,它会调用 onBuildScheduled 函数,然后将 element 加入 _dirtyElements,同时标记 _inDirtyList 为 true。

onBuildScheduled

onBuildScheduled 是在 WidgetsBinding 初始化时注册到 BuildOwner 中的,对应的回调函数为 _handleBuildScheduled,这个函数会调用 ensureVisualUpdate,而 ensureVisualUpdate 又会根据当前的 SchedulerPhase 值选择性操作,只有在 idle 和 postFrameCallbacks 状态时才会调用 scheduleFrame ,其他状态,比如 transientCallbacks、midFrameMicrotasks、persistentCallbacks 等,都被看作是一帧正在进行中,而目前对 element 树的改动还是会被 drawFrame 处理到的,视图会按照预期的刷新,所以没必要再去请求新的帧。

scheduleFrame

scheduleFrame 会调用 Window 的 scheduleFrame 函数,这是一个 native 函数,会进入到 C++ 中执行,此举就是为了通过 C++ 继而转到 Android 代码中执行,这个函数会在 C++ 中依次调用 window、runtime_controller、engine 和 animator 中对应的函数,最终调用 Animator::AwaitVSyn 。

Animator::AwaitVSyn

void Animator::AwaitVSync() {
  waiter_->AsyncWaitForVsync(
      [self = weak_factory_.GetWeakPtr()](fml::TimePoint frame_start_time,
                                          fml::TimePoint frame_target_time) {
        if (self) {
          if (self->CanReuseLastLayerTree()) {
            self->DrawLastLayerTree();
          } else {
            self->BeginFrame(frame_start_time, frame_target_time);
          }
        }
      });

  delegate_.OnAnimatorNotifyIdle(dart_frame_deadline_);
}

在这个函数中调用了 VsyncWaiter::AsyncWaitForVsync 函数,并传递了一个 callback 函数,里面会调用 BeginFrame,由此可知,这个 callback 就是在新的一帧到来时的回调函数,接着再看 VsyncWaiter 中的,它先将 callback 保存在成员变量中,然后调用了 AwaitVSync,以 Android 平台为例,这个函数将会被它的子类 VsyncWaiterAndroid 实现,这个实现利用 jni 调用 java 中的方法 FlutterJNI.asyncWaitForVsync。

FlutterJNI.asyncWaitForVsync

在 java 中这个方法会调用 AsyncWaitForVsyncDelegate.asyncWaitForVsync 方法,这是一个接口,其实现如下:

private final FlutterJNI.AsyncWaitForVsyncDelegate asyncWaitForVsyncDelegate = new FlutterJNI.AsyncWaitForVsyncDelegate() {
    @Override
    public void asyncWaitForVsync(long cookie) {
        Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
            @Override
            public void doFrame(long frameTimeNanos) {
                float fps = windowManager.getDefaultDisplay().getRefreshRate();
                long refreshPeriodNanos = (long) (1000000000.0 / fps);
                FlutterJNI.nativeOnVsync(frameTimeNanos, frameTimeNanos + refreshPeriodNanos, cookie);
            }
        });
    }
};

由此就可以找到 Choreographer 与 flutter 之间的联系,flutter 调用的 requestFrame 最终会向 Choreographer 注册一个回调,然后等待 Choreographer 下一帧的到来,再去执行 FlutterJNI.nativeOnVsync,可想而之,这个过程是一个从 java 调用到 flutter 的过程,与上面的相反。

FlutterJNI.nativeOnVsync

这是一个 native 函数,会映射到调用 VsyncWaiterAndroid::OnNativeVsync 函数,VsyncWaiterAndroid 本质上看就是一个 jni 的接口类,它负责从 C++ 中调用 java 方法,也负责被 java 方法调用,再传递给 Animator。在它的 OnNativeVsync 中会依次调用 ConsumePendingCallback、FireCallback,在 FireCallback 中会先拿到之前传进来的 callback ,然后就是在 UI 线程中调用它,也就是 Animator::AwaitVSync 中声明的 callback 函数,所以,下一步就是调用 BeginFrame。

Animator::BeginFrame

这个函数的主要实现就是调用 OnAnimatorBeginFrame,附加的还有如判断当前管道中是有可用的 producer_continuation,以及在最后调用 OnAnimatorNotifyIdle。

Animator 的 delegate 是 Shell,Shell 会转而调用 Engine 的 BeginFrame,Engine 再调用 RuntimeController 的 BeginFrame,然后是 Window 的 BeginFrame,Window 就是 C++ 中用于与 dart 交互的中间类,它负责从 C++ 中调用 dart 函数和 dart 对 C++ 函数的调用,在这里,它会通过 DartInvokeField 先后调用 dart 中的 _beginFrame 和 _drawFrame。

hooks.dart

在 dart 中 hooks.dart 负责接收 C++ 层的调用,比如 _beginFrame 函数:

@pragma('vm:entry-point')
void _beginFrame(int microseconds) {
  _invoke1<Duration>(window.onBeginFrame, window._onBeginFrameZone, Duration(microseconds: microseconds));
}

它再去调用 window 中的函数,而 window 中的 onBeginFrame、onDrawFrame 等函数都是在之前初始化 SchedulerBinding 时就注册过的,就是在之前 scheduleWarmUpFrame 也会调用的 handleBeginFrame 和 handleDrawFrame。

由此,每当 flutter 中需要刷新视图时,就通过请求帧、回调、处理帧这样一个过程完成。