乐趣区

flutter-加载与运行Dart

环境: 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 编译模式

退出移动版