关于flutter:浅谈-Flutter-编译原理

48次阅读

共计 5724 个字符,预计需要花费 15 分钟才能阅读完成。


前言

纯熟应用 Flutter 开发 app 的人员,对各种 widget 的应用必定曾经信手拈来了,但往往对 Flutter 是如何编译、相应产物是什么却知之甚少。

本文就来理解一下 Flutter 编译的相干常识

一、Flutter 架构层

Flutter 架构次要分为三层:

1. Framework 层

基于 Dart 实现,次要蕴含

  • Material Design(Google),Cupertino(iOS)两种格调的 Widgets。
  • 文本 / 图片 / 按钮 等 Widgets。
  • 渲染 / 动画 / 绘制 / 手势 等 Widgets。
  • 外围根底类 / 办法,次要指 Flutter 仓库下的 Flutter package,以及 sky_engine 仓库下的 io、async、ui(dart:ui 库提供了 Flutter 框架和引擎之间的接口)等 package。

2. Engine 层

基于 C++ 实现,次要蕴含

  • Skia 开源的二维图形库,提供了实用于多种软硬件平台的通用 API。
  • Dart 次要蕴含 Dart Runtime,Garbage Collection(GC),如果是 Debug 模式的话,还包含 JIT(Just In Time)反对。Release 和 Profile 模式下,是 AOT(Ahead Of Time)编译成了原生的 arm 代码,并不存在 JIT 局部。
  • Text 文本渲染,其渲染档次如下:衍生自 minikin 的 libtxt 库(用于字体抉择,分隔行);HartBuzz 用于字形抉择和成型;Skia 作为渲染 / GPU 后端,在 Android 和 Fuchsia 上应用 FreeType 渲染,在 iOS 上应用 CoreGraphics 来渲染字体。

3. Embedder 层

Embedder 是一个嵌入层,即把 Flutter 嵌入到各个平台下来,这里做的次要工作包含渲染 Surface 设置, 线程设置,以及插件等。从这里能够看出,Flutter 的平台相干层很低,平台(如 iOS)只是提供一个画布,残余的所有渲染相干的逻辑都在 Flutter 外部,这就使得它具备了很好的跨端一致性。

基于下面的架构理解,要想搞懂 Flutter 到底是怎么编译的,咱们首先就要从 Engine 层下手。咱们晓得编程语言要达到可运行的目标须要进行编译,如 Android 开发中的 .java -> .class -> .dex,咱们通过编写 .java 文件,通过层层编译解释,最终变为可被机器辨认的机器语言。那咱们在日常 Flutter 开发中应用 Dart 语言进行开发的 .dart 文件一路上都经验了什么风霜雨雪,最终被机器所认可呢。

二、Flutter 编译模式

上文中提到一个概念“编译模式”,简略来讲就是:

  1. 把一段源程序翻译成机器码
  2. 让机器开始干活,运行这段机器码

不同的编译模式,次要区别在于 when、where、how 进行下面两步操作,这往往就导致了同一段代码“失效”的速度不同。那么依据需要、硬件环境抉择适合的编译模式就能进步咱们程序的效率,然而什么样的编译模式才是适合的呢,咱们先来看看常见的编译模式都有哪些。

一般来说,编译模式分为 JITAOT 两种

1. JIT

JIT 全称 Just In Time(即时编译),意思就是能够在运行时将源代码进行编译和执行。比拟典型的例子就是 v8 引擎,它能够即时编译并运行 JavaScript。一般来讲,反对 JIT 的语言都能够反对自省函数。

劣势:应用 JIT 模式进行编译,能够动静下发和执行代码,不须要思考用户所应用的机器架构,从而达到动静为用户提供丰盛内容的目标。

劣势:运行时编译必然会耗费工夫和内存,给用户带来的间接感触就是利用启动慢。

2. AOT

AOT 全称 Ahead Of Time(提前编译),意思就是提前将源代码编译成机器码,用户的机器在运行时间接执行对应的机器码。比拟典型的例子就是 C/C++,LLVM 或 GCC 通过编译并生成 C/C++ 的二进制代码,而后这些二进制代码通过用户装置并获得执行权限后,才能够通过过程进行加载执行。

劣势:提前编译好的二进制代码,加载和执行的速度都会十分快。所以编程语言运行速度排行榜靠前的都是 C、C++、Rust 这些编译类语言,这样的速度能够在密集计算场景下给用户带来十分好的体验,比方大型游戏的引擎渲染和逻辑执行。

劣势:编译须要辨别用户机器的架构,生成不同架构的二进制代码,除了架构,二进制代码自身也会让用户下载的安装包比拟大。二进制代码个别须要获得执行权限才能够执行,所以无奈在权限比拟严格的零碎中进行动静更新,如 iOS。

尽管咱们会通过 JIT 和 AOT 两种编译模式来辨别语言的类型,但实际上,很多语言并不是只应用 JIT 或者 AOT 其中一种的,通常它们会混用这两种模式,来达到最大的性能优化(Java 就是这种,所以 Java 有时也会被称为半编译半解释型语言)。

因为 Flutter 应用 Dart 作为编程语言,所以如果要了解 Flutter 的编译模式,咱们先来看 Dart 的编译模式。

3. Dart 编译模式

1. Dart VM

相似 JVM 一样,Dart 也有对应 Dart VM,咱们能够了解为 Dart 虚拟机。它为 Dart 提供了执行环境,除了采纳解释执行或者 JIT 编译,还能够应用 Dart 虚拟机的 AOT 管道将 Dart 代码编译为机器代码,而后运行在 Dart VM 的精简版,称之为预编译运行时(precompiled runtime)环境,该环境不蕴含任何编译器组件,且无奈动静加载 Dart 源代码。

2. Dart 编译模式

Dart VM 有多种形式来执行代码:

  • Script: 最一般的 JIT 模式,能够间接在虚拟机中执行 Dart 源码,像解释型语言一样应用,在命令行间接调用 dart xxx.dart 执行 dart 源代码即这种模式。
  • Kernel Snapshots: 之前也叫做 Script Snapshots,采纳 JIT 模式,和 Script 模式不同的是,这种模式执行的是 Kernel AST 的二进制数据,这里不蕴含解析后的类和函数,编译后的代码,所以它们能够在不同的平台之间移植。Dart Kernel 是 Dart 程序的两头语言,是一种紧凑的二进制指标文件格式,反对独自编译和链接,是程序转换的基础设施。通过执行 dart –snapshot-kind=kernel –snapshot=xx.snapshot xx.dart 生成。
  • JIT Application Snapshots:JIT 模式,这种模式执行的是解析后的类和数据,所以须要辨别架构,然而相应的这种数据运行起来会更快。通过执行 dart –snapshot-kind=app-jit –snapshot=xx.snapshot xx.dart 生成。
  • AOT Application Snapshots:AOT 模式,在这种模式下,Dart 源码会被提前编译成特定平台的二进制文件。

总结一下 Dart 的编译模式

模式 编译模式 是否辨别架构 打包大小 动态化 启动工夫
ScriptJIT 较小 最慢
Kernel SnapshotsJIT 最小 较慢
JIT Application SnapshotsJIT 较大 较快
AOT Application SnapshotsAOT 最大 最快

3. Flutter 编译模式

Flutter 齐全采纳了 Dart,按情理来说编译模式应该统一,然而事实并非如此。起因是 Flutter 开发中要思考到 Android 和 iOS 平台的生态差别,所以在 Dart 编译模式的根底上进行了一些改变:

  • Script: 同 Dart Script 模式统一,尽管 Flutter 反对,但临时没有应用,因为影响启动速度。
  • Kernel Snapshot :Dart 的 bytecode 模式,bytecode 模式是不辨别架构的。Kernel Snapshot 在 Flutter 我的项目内也叫 Core Snapshot。bytecode 模式能够归类为 AOT 编译。
  • Core JIT :Dart 的一种二进制模式,将指令代码和 heap 数据打包成文件,而后在 vm 和 isolate 启动时载入,间接标记内存可执行,能够说这是一种 AOT 模式。Core JIT 也被叫做 AOTBlob。
  • AOT Assembly:Dart 的 AOT 模式。间接生成汇编源代码文件,由各平台自行汇编。

能够看到,Flutter 将 Dart 的编译模式复杂化了,多了不少概念,为了了解这些概念,咱们从 Flutter 利用开发的各个阶段来解读。

4. 不同阶段下的 Flutter 编译模式

Flutter 反对三种模式编译 app,别离是

Debug 模式: 对应了 Dart 中的 JIT 模式,在此模式下,反对真机和模拟器,断点开启,服务扩大开启,针对疾速开发和运行周期进行了编译优化(执行速度、包体积大小、部署并未优化),调试工具能够连贯到过程里,热重载性能。

Release 模式: 对应了 Dart 中的 AOT 模式,在此模式下只反对真机,敞开了所有断点和调试信息,禁止调试,优化了启动速度、执行速度及包体积,无奈应用热重载性能。

Profile 模式: 与 Release 模式根本类似,然而保留了一部分调试能力,用来剖析 app 的性能,然而只能在真机上应用,因为模拟器的性能剖析不具备真实性。一些服务扩大是启用的。例如,反对 performance overlay。

三、Flutter 编译过程

1. Flutter run

对 Flutter 的编译模式有个初步的理解后,咱们来看一下 Flutter 的编译过程。以 Android 为例,当咱们点击 Android Studio 中的运行按钮,默认状况下执行了 Flutter run 命令,当 Flutter run 命令前面不带任何参数默认采纳的是 debug 模式,咱们能够通过给 Flutter run 命令增加参数来扭转对应的编译模式,如:Flutter run -release 来运行 release 模式。

Flutter run 过程波及多个 Flutter 相干命令,其蕴含关系如下所示:

Flutter 命令的整个过程位于目录 Flutter/packages/flutter_tools/,整个 Flutter run 的过程次要蕴含以下几个外围性能:

  1. Flutter build apk:通过 gradle 来构建 APK

    • Flutter build aot:构建 AOT 编译产物

      • frontend_server:前端编译器生成 kernel 文件
      • gen_snapshot:将 dart 代码编译成 AOT 产物
    • Flutter build bundle:将相干文件放入 flutter_assets 目录
  2. 通过 adb install 装置 APK
  3. 通过 adb am start 启动利用

整个流程具体执行,如图所示:

其中,咱们较为关怀的就是这两个步骤及相干编译产物:

  • frontend_server:前端编译器生成 kernel 文件
  • gen_snapshot:将 dart 代码编译成 AOT 产物

2. frontend_server 命令

KernelCompiler.compile() 过程等价于如下命令:

可见,通过 dart 虚拟机启动 frontend_server.dart.snapshot,将 dart 代码转换成 app.dill 模式的 kernel 文件。

frontend_server 前端编译器将 dart 代码转换为 AST,并生成 app.dill 文件,其中 bytecode 生成过程默认是敞开的。

3. gen_snapshot 命令

GenSnapshot.run 具体命令依据后面的封装,针对 Android 和 iOS 平台各有不同:

针对 Android:

针对 iOS:

上述命令次要是将 dart kernel 转换为机器码,对应流程图为

此处 gen_snapshot 是一个二进制可执行文件,次要作用是将 dart 代码生成 AOT 二进制机器码。

四、Flutter 编译产物

大抵理解了 Flutter 编译的过程后,咱们就能够晓得 Flutter 在不同平台、不同模式下的编译产物是什么:

1. iOS – release 模式:

跟原生 APP 构造差异不大,Frameworks ⽂件夹次要多了 App.framework 和 Flutter.framework。App.framework ⾥ flutter_assets ⽂件夹寄存了 Flutter ⾥引⽤到的资源⽂件。


2. Android – release 模式:

跟原生 APP 构造差异不大,不同的是 assets ⾥多个 flutter_assets ⾥⾯寄存的是 Flutter 引⽤的资源⽂件,lib ⾥多了 libapp.so 和 libflutter.so。

3. iOS – debug 模式:

App.framework ⽂件夹⾥多了 isolate_snapshot_data,kernel_blob.bin,vm_snapshot_data 同时 App 这个⼆进制⽂件只有 33 KB,然而 release 模式下有 8.5MB。

4. Android – debug 模式:

flutter_assets 下多了 isolate_snapshot_data,kernel_blob.bin,vm_snapshot_data。
lib 下少了 libapp.so。

产物总结

  • Flutter APP 最终会蕴含两个库,⼀个是 dart 代码编译⽽来的 app 库,⼀个是引擎代码编译来的 Flutter 库
  • 为了实现 hot reload,debug 模式下,dart 代码会⽣成⾄少 3 个⽂件,这三个⽂件就是每次编译动静⽣成,运⾏的时候能够替换以便实现 hot reload。⽽ release 模式下这 3 个⽂件会并⼊ app 库。Flutter 库在 debug 和 release 模式下⼤⼩也是不⼀样的
  • isolate_snapshot_data:⽤于减速 isolate 启动,业务⽆关代码
  • kernel_blob.bin:业务代码产物
  • vm_snapshot_data:⽤于减速 Dart VM 启动的产物,业务⽆关代码
  • 原⽣的资源⽂件和 Flutter 的资源⽂件是隔离的

五、what can we do?

那么,在理解 Flutter 的相干编译原理后,咱们能够做哪些事件呢?

  1. 联合平台个性对 Flutter 相干产物内容进行体积优化,从而减小包体积;
  2. 从 Flutter 方向进行模块拆分、编译速度优化、缩小打包工夫;
  3. 摸索 Flutter 动态化及 Flutter for Web 等更多可能性

当然,这些进一步的摸索须要咱们更多的学习与实际。

更多精彩请关注咱们的公众号「百瓶技术」,有不定期福利呦!
正文完
 0