天天看点

flutter: 加载与运行Dart调用封装所谓引擎文档理解DartVM与DartVMRefDart虚拟机

环境: 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

    异步调用了

    Engine

    Run

    方法
  2. Engine

    Run

    跑在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接口文档),它的初始化,运行,集成像一个巨大的黑盒。因为最终运行的还是

Isolate

Run

方法,核心还是理解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

DartVM

DartVMRef

是什么关系?按照字面及代码注释意思,是一个实例与强引用的关系,

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