flutter: 加载与运行Dart

1,922 阅读6分钟

环境: flutter sdk v1.7.8+hotfix.3@stable

对应 flutter engine: 54ad777fd29b031b87c7a68a6637fb48c0932862

在建立异步线程与消息循环之后,自然就是运行应用脚本,也就是dart文件。这一部分感觉很庞大而且千头万绪:对dart不同模式的编译,不同参数的配置,从代码看还有热加载(hot reload)的机制,从里到外都是一团乱麻;有这种感觉只是因为不熟悉,刚刚接触陌生环境产生的畏惧,只要熟悉啥都不是事。所以先不贸然进入热加载之类的细节,以目前了解的通信与异步为基础,渐次深入对象关联关系为上。

FlutterActivityDelegate.onCreate的最后容易发现一个比较重要的调用runBundle,深入的调用序列如下:

FlutterActivity.onCreate
  FlutterActivityDelegate.onCreate
    FlutterActivityDelegate.runBundle
      FlutterView.runFromBundle
        FlutterView.preRun
        FlutterNativeView.runFromBundle
          FlutterNativeView.runFromBundleInternal
            FlutterJNI.runBundleAndSnapshotFromLibrary
              FlutterJNI.nativeRunBundleAndSnapshotFromLibrary
        FlutterView.postRun

与C++层的调用序列分开:

::RunBundleAndSnapshotFromLibrary
  AndroidShellHolder::Launch

...[async:ui_thread]Engine::Run
  Engine::PrepareAndLaunchIsolate
    RuntimeController::GetRootIsolate
    IsolateConfiguration::PrepareIsolate
      IsolateConfiguration::DoPrepareIsolate => AppSnapshotIsolateConfiguration::DoPrepareIsolate
        DartIsolate::PrepareForRunningFromPrecompiledCode
    DartIsolate::Run
      DartIsolate::InvokeMainEntrypoint

这里已经有点晕了,各种名称堆砌在一起:DartIsolate, Dart_Isolate, RootDartIsolate; RunConfiguration, IsolateConfiguration AppSnapshotIsolateConfiguration; 撇开这些名称至少我们知道:

  1. AndroidShellHolder异步调用了EngineRun方法
  2. EngineRun跑在flutter的ui线程中
  3. Engine获取成员RuntimeController的一个叫RootIsolate的对象并最终调用了其DartIsolate::Run方法
  4. DartIsolate进入到了主入口方法,在这里就是lib/main.dart中的main()方法(runFromBundle(bundlePath, defaultPath, "main", false); FlutterView.java:611)。

调用封装

显然最终调的是dartSDK提供的各种方法,虽然我们大概知道flutter的Engine不会具体做dart代码的解释与执行,但比较棘手的是我们很难分清Engine与DartSDK的界限;DartSDK的接口方法散落在各处,他们的先后调用关系,对象依赖关系,内部状态的变化与检查,对于初学者都增加理解上的难度。所以最好是针对DartSDK再有一层封装或者抽象,不仅初始化与运行调用序列清晰,让sdk可替换(如果以后有其它的dart实现呢?),也让引擎真正成为引擎

所谓引擎

所以这里也可以对引擎的含义做一个梳理:引擎自然是可插拨的一种形态,只要与引擎提供的接口一致可以更换别的实现如同灯泡座与灯泡的关系,在这里显然无法更换DartSDK, 所以Flutter的引擎是针对平台的引擎,我们可以将应用移植到各种平台或者操作系统。

文档理解

这时候死看代码难有进展,我们最好先了解DartSDK本来有什么。但发现竟然很难找到一份针对DartSDK的使用教程与文档(不是Dart语言使用文档,是开发集成Dart虚拟机的C接口文档),它的初始化,运行,集成像一个巨大的黑盒。因为最终运行的还是IsolateRun方法,核心还是理解Dart的Isolate

一些资料

Engine-architecture里的UI Task Runner提到:

(root Dart isolate)runs the application's main Dart code. Bindings are set up on this isolate by the engine to schedule and submit frames.

Terminating the root isolate will also terminate all isolates spawned by that root isolate.

(root Dart isolate)also executes all responses for platform plugin messages, timers, microtasks and asynchronous I/O.

you cannot interact with the Flutter framework in any meaningful way on the secondary isolate. As non-root isolates are incapable of scheduling frames and do not have bindings that the Flutter framework depends on.

然而引用的dart isolate几乎没有用,我们想要的是isolate在flutter引擎中的表示,而不是isolate概念及使用文档。但以上描述与flutter中用ui thread来运行RootIsolate::Run是一致的。

所以Isolate可以理解为(至少在flutter的表示中)一种特殊的线程,这个线程有自己的堆和栈(和普通线程一样),但不能共享状态,也就是不能加锁来进行同步!RootIsolate又是一个特殊的Isolate,它的一个重要功能是调度和准备渲染帧,而具体的渲染工作由RootIsolate交给GPU线程(应该存在另一个isolate实例)来做。 这个理解与在Engine::PrepareAndLaunchIsolate中调用了DartIsolate::Run是一致的,于是看RootIsolate创建流程:

RuntimeController::RuntimeController
  DartIsolate::CreateRootIsolate
    DartIsolate::DartIsolate
      phase_ = Phase::Uninitialized;
    DartIsolate::CreateDartVMAndEmbedderObjectPair
      Dart_CreateIsolate
      DartIsolate::Initialize
        phase_ = Phase::Initialized;
      DartIsolate::LoadLibraries
        phase_ = Phase::LibrariesSetup;

在这里标注了一下DartIsolate::phase_的变化,以便能更好追踪DartIsolate的状态,同样,结合之前的DartIsolate::Run调用序列:

::RunBundleAndSnapshotFromLibrary
  ::CreateIsolateConfiguration
    IsolateConfiguration::CreateForAppSnapshot
  AndroidShellHolder::Launch
...[async:ui_thread]Engine::Run
  Engine::PrepareAndLaunchIsolate
    IsolateConfiguration::PrepareIsolate
      IsolateConfiguration::DoPrepareIsolate => AppSnapshotIsolateConfiguration::DoPrepareIsolate
        DartIsolate::PrepareForRunningFromPrecompiledCode
          Dart_RootLibrary
          MarkIsolateRunnable
          phase_ = Phase::Ready;
    DartIsolate::Run
      Dart_RootLibrary(), "main"
      DartIsolate::InvokeMainEntrypoint
        "dart:isolate._getStartMainIsolateFunction"
        "dart:ui._runMainZoned"
      phase_ = Phase::Running;

可见对phase_的检查是符合预期的(phase_被置成Phase::Ready之前必须是Phase::LibrariesSetup) 以Dart_开头的方法都是DartSDK的方法,分布在各种对象的各种方法中,但大体上我们知道了flutter中的DartIsolate是SDK中Dart_Isolate的封装。 在调用入口方法之前(InvokeMainEntrypoint),先获取了入口方法本身(user_entrypoint_function),从哪里获取的?Dart_RootLibrary()。我们应该能猜出来这个RootLibrary应该就是我们编写的Dart应用(main.dart所在的lib/目录下那一坨),所以另外追踪一下如何设置RootLibrary的,Dart_SetRootLibrary流程:

Shell::Create
  DartVMRef::Create
    DartVM::Create
      DartVM::DartVM
        DartUI::InitForGlobal
        Dart_Initialize
          DartIsolate::DartIsolateCreateCallback
            DartIsolate::DartCreateAndStartServiceIsolate
              DartServiceIsolate::Startup
                Dart_SetRootLibrary

原来是在创建Shell前先创建了DartVM,在DartVM的构造函数时设置的,而且终于涉及到了那个一直被雪藏的类DartVM

DartVM与DartVMRef

DartVMDartVMRef是什么关系?按照字面及代码注释意思,是一个实例与强引用的关系,DartVM只能通过DartVMRef::Create来获取实例

A reference to the VM may only be obtained via the |Create| method.

那么可以总结如下:

  1. DartVMRef屏蔽了DartVM的创建
  2. DartVMRef保证进程全局只有一个DartVM实例及数据(DartVMData)
  3. DartVMRef线程安全的获取DartVM实例

我觉得倒不如叫DartVMManager来的简单明白,DartVMRef除了引用还干了这么多事...那个通过DartVMRef::Create方法来获取实例的操作看着也比较别扭。

Dart虚拟机

目前的阶段没法深入虚拟机实现原理,加载机制,只能通观概览的了解一下它的特性。目测DartVM所做的工作其实并不多,主要是调用了DartSDK的各种API。

虚拟机分类

虚拟机先分为系统虚拟机(system vm)和应用虚拟机(process vm), 应用虚拟机又可分为字节码虚拟机(bytecode vm)和源码虚拟机(language vm),与JVM不同,DartVM是后面这种。

非字节码

我们知道Dart虚拟机可以JIT(解释执行)也可以AOT(编译运行),这是被选作flutter开发语言的原因之一。可以肯定的是Dart虚拟机没有基于字节码,因为一旦用了字节码指令,相关的复杂度其实是膨胀的,解释可以参看这篇非常棒的为什么不用字节码 ,这种源码虚拟机(language vm)其实和JS引擎有点类似。

非条件竞争

语言本身在创建之初的考虑就是避免这种锁竞争的(Isolate机制)。

Adding support for sharing memory across threads in our VM would be pointless since the one language we know our VM will run doesn’t use it.

总之不采用字节码是一种折衷(tradeoff),归根结底还是为了保持简单! 另外还可通过这篇文章了解DartVM,不过有点艰深。

另:flutter编译模式