37

深入理解Flutter的编译原理与优化1530939627474767

 5 years ago
source link: http://www.techug.com/post/google-flutter.html?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
aEN3Mvf.jpg!web

导读:对于开发者而言,Flutter 工程和我们的 Android/iOS 工程有何差别?Flutter 的渲染和事件传递机制如何工作?构建缓慢或出错又如何去定位,修改和生效呢?凡此种种,都需要对 Flutter 从设计,开发构建,到最终运行有一个全局视角的观察。

本文由闲鱼技术团队出品,将以一个简单的 hello_flutter 为例,介绍下 Flutter 相关原理及定制与优化。

Flutter 简介

2i2mAr6.jpg!web

Flutter 的架构主要分成三层:Framework,Engine 和 Embedder。

Framework 使用 dart 实现,包括 Material Design 风格的 Widget,Cupertino (针对 iOS)风格的 Widgets,文本/图片/按钮等基础 Widgets、渲染、动画、手势等。此部分的核心代码是:flutter 仓库下的 flutter package,以及 sky_engine 仓库下的 io,async,ui (dart:ui 库提供了 Flutter 框架和引擎之间的接口)等 package。

Engine 使用 C++ 实现,主要包括:Skia,Dart 和 Text。Skia 是开源的二维图形库,提供了适用于多种软硬件平台的通用 API。其已作为 Google Chrome,Chrome OS,Android, Mozilla Firefox, Firefox OS 等其他众多产品的图形引擎,支持平台还包括 Windows 7+,macOS 10.10.5+,iOS8+,Android4.1+,Ubuntu14.04+ 等。

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 来渲染字体。

Embedder 是一个嵌入层,即把 Flutter 嵌入到各个平台上去,这里做的主要工作包括渲染 Surface 设置,线程设置,以及插件等。从这里可以看出,Flutter 的平台相关层很低,平台(如 iOS)只是提供一个画布,剩余的所有渲染相关的逻辑都在 Flutter 内部,这就使得它具有了很好的跨端一致性。

Flutter 工程结构

本文使用开发环境为 flutter beta v0.3.1,对应的 engine commit:09d05a389。

以 hello_flutter 工程为例,Flutter 工程结构如下所示:

RbUNNvz.png!web

其中 ios 为 iOS 部分代码,使用 CocoaPods 管理依赖,android 为 Android 部分代码,使用 Gradle 管理依赖,lib 为 dart 代码,使用 pub 管理依赖。类似 iOS 中 Cocoapods 对应的 Podfile 和 Podfile.lock,pub 下则是 pubspec.yaml 和 pubspec.lock。  

Flutter 模式

n67zuy7.jpg!web 对于 Flutter,它支持常见的 debug,release,profile 等模式,但它又有其不一样。

Debug 模式:对应了 Dart 的 JIT 模式,又称检查模式或者慢速模式。支持设备,模拟器(iOS/Android),此模式下打开了断言,包括所有的调试信息,服务扩展和 Observatory 等调试辅助。此模式为快速开发和运行做了优化,但并未对执行速度,包大小和部署做优化。Debug 模式下,编译使用 JIT 技术,支持广受欢迎的亚秒级有状态的 hot reload。

Release 模式:对应了 Dart 的 AOT 模式,此模式目标即为部署到终端用户。只支持真机,不包括模拟器。关闭了所有断言,尽可能多地去掉了调试信息,关闭了所有调试工具。为快速启动,快速执行,包大小做了优化。禁止了所有调试辅助手段,服务扩展。

Profile 模式:类似 Release 模式,只是多了对于 Profile 模式的服务扩展的支持,支持跟踪,以及最小化使用跟踪信息需要的依赖,例如,observatory 可以连接上进程。Profile 并不支持模拟器的原因在于,模拟器上的诊断并不代表真实的性能。

鉴于 Profile 同 Release 在编译原理等上无差异,本文只讨论 Debug 和 Release 模式。

事实上 flutter 下的 iOS/Android 工程本质上依然是一个标准的 iOS/Android 的工程,flutter 只是通过在 BuildPhase 中添加 shell 来生成和嵌入 App.framework 和 Flutter.framework (iOS),通过 gradle 来添加 flutter.jar 和 vm/isolate_snapshot_data/instr (Android)来将 Flutter 相关代码编译和嵌入原生 App 而已。因此本文主要讨论因 flutter 引入的构建,运行等原理。编译 target 虽然包括 arm,x64,x86,arm64,但因原理类似,本文只讨论 arm 相关(如无特殊说明,android 默认为 armv7)。

Flutter 代码的编译与运行(iOS)

Release 模式下的编译

release 模式下,flutter 下 iOS 工程中 dart 代码构建链路如下所示:

uQ7rUfz.jpg!web

其中 gen_snapshot 是 dart 编译器,采用了 tree shaking (类似依赖树逻辑,可生成最小包,也因而在 Flutter 中禁止了 dart 支持的反射特性)等技术,用于生成汇编形式的机器代码,再通过 xcrun 等编译工具链生成最终的 App.framework。换句话说,所有的 dart 代码,包括业务代码,三方 package 代码,它们所依赖的 flutter 框架代码,最终将会变成 App.framework。

tree shaking 功能位于 gen_snapshot 中,对应逻辑参见:

engine/src/third_party/dart/runtime/vm/compiler/aot/precompiler.cc

dart 代码最终对应到 App.framework 中的符号如下所示:

YjYB733.jpg!web

事实上,类似 Android Release 下的产物(见下文),App.framework 也包含了 kDartVmSnapshotData,kDartVmSnapshotInstructions,kDartIsolateSnapshotData,kDartIsolateSnapshotInstructions 四个部分。为什么 iOS 使用 App.framework 这种方式,而不是 Android 的四个文件的方式呢?原因在于在 iOS 下,因为系统的限制,Flutter 引擎不能够在运行时将某内存页标记为可执行,而 Android 是可以的。

Flutter.framework 对应了 Flutter 架构中的 engine 部分,以及 Embedder。实际中 Flutter.framework 位于 flutter 仓库的/bin/cache/artifacts/engine/ios*下,默认从 google 仓库拉取。当需要自定义修改的时候,可通过下载 engine 源码,利用 Ninja 构建系统来生成。

Flutter 相关代码的最终产物是:App.framework (dart 代码生成)和 Flutter.framework (引擎)。从 Xcode 工程的视角看,Generated.xcconfig 描述了 Flutter 相关环境的配置信息,然后 Runner 工程设置中的 Build Phases 新增的 xcode_backend.sh 实现了 Flutter.framework 的拷贝(从 Flutter 仓库的引擎到 Runner 工程根目录下的 Flutter 目录)与嵌入和 App.framework 的编译与嵌入。最终生成的 Runner.app 中 Flutter 相关内容如下所示:

yyQn6nQ.jpg!web

其中 flutter_assets 是相关的资源,代码则是位于 Frameworks 下的 App.framework 和 Flutter.framework。

Release 模式下的运行

Flutter 相关的渲染,事件,通信处理逻辑如下所示:

M73mYny.jpg!web

其中 dart 中的 main 函数调用栈如下:

B7VVjmq.jpg!web

Debug 模式下的编译

Debug 模式下 flutter 的编译,结构类似 Release 模式,差异主要表现为两点:

1. Flutter.framework

因为是 Debug,此模式下 Framework 中是有 JIT 支持的,而在 Release 模式下并没有 JIT 部分。

2. App.framework

不同于 AOT 模式下的 App.framework 是 Dart 代码对应的本地机器代码,JIT 模式下,App.framework 只有几个简单的 API,其 Dart 代码存在于 snapshot_blob.bin 文件里。这部分的 snapshot 是脚本快照,里面是简单的标记化的源代码。所有的注释,空白字符都被移除,常量也被规范化,也没有机器码,tree shaking 或者是混淆。

App.framework 中的符号表如下所示:

2ERBJfb.jpg!web

对 Runner.app/flutter_assets/snapshot_blob.bin 执行 strings 命令可以看到如下内容:

BnUrieQ.jpg!web

Debug 模式下 main 入口的调用堆栈如下:

fQja6rr.jpg!web

Flutter 代码的编译与运行(Android)

鉴于 Android 和 iOS 除了部分平台相关的特性外,其他逻辑如 Release 对应 AOT,Debug 对应 JIT 等均类似,此处只涉及两者不同。

Release 模式下的编译

release 模式下,flutter 下 Android 工程中 dart 代码整个构建链路如下所示:

EN3QzmE.jpg!web

其中 vm/isolate_snapshot_data/instr 内容均为 arm 指令,将会在运行时被 engine 载入,并标记 vm/isolate_snapshot_instr 为可执行。vm_中涉及 runtime 等服务(如 gc),用于初始化 DartVM,调用入口见 Dart_Initialize (dart_api.h)。isolate__则是对应了我们的 App 代码,用于创建一个新 isolate,调用入口见 Dart_CreateIsolate (dart_api.h)。

flutter.jar 类似 iOS 的 Flutter.framework,包括了 engine 部分的代码(Flutter.jar 中的 libflutter.so),以及一套将 Flutter 嵌入 Android 的类和接口(FlutterMain,FlutterView,FlutterNativeView 等)。实际中 flutter.jar 位于 flutter 仓库的/bin/cache/artifacts/engine/android*下,默认从 google 仓库拉取。当需要自定义修改的时候,可通过下载 engine 源码,利用 Ninja 构建系统来生成 flutter.jar。

以 isolate_snapshot_data/instr 为例,执行 disarm 命令结果如下:

aiYJ3iF.jpg!web
biqI3m6.jpg!web

其 Apk 结构如下所示:

Zv2Qbaa.jpg!web

APK 新安装之后,会根据一个 ts 的判断(packageinfo 中的 versionCode 结合 lastUpdateTime)来决定是否拷贝 APK 中的 assets,拷贝后内容如下所示:

EFjEbu7.jpg!web

isolate/vm_snapshot_data/instr 均最后位于 app 的本地 data 目录下,而这部分又属于可写内容,因此可以通过下载并替换的方式,完成 App 的整个替换和更新。

Release 模式下的运行

bqyEnaY.jpg!web

Debug 模式下的编译

类似 iOS 的 Debug/Release 的差别,Android 的 Debug 与 Release 的差异主要包括以下两部分:

1. flutter.jar

区别同 iOS

2. App 代码部分

位于 flutter_assets 下的 snapshot_blob.bin,同 iOS。

在介绍了 iOS/Android 下的 Flutter 编译原理后,下面着重描述下如何定制 flutter/engine 以完成定制和优化。鉴于 Flutter 处于敏捷的迭代中,现在的问题后续不一定是问题,因而此部分并不是要去解决多少问题,而是选取不同类别的问题来说明解决思路。

Flutter 构建相关的定制与优化

Flutter 是一个很复杂的系统,除了上述提到的三层架构中的内容外,还包括 Flutter Android Studio (Intellij)插件,pub 仓库管理等。但我们的定制和优化往往是在 flutter 的工具链相关,具体代码位于 flutter 仓库的 flutter_tools 包。接下来举例说明下如何对这部分做定制。

Android 部分

相关内容包括 flutter.jar,libflutter.so (位于 flutter.jar 下),gen_snapshot,flutter.gradle,flutter (flutter_tools)。

1. 限定 Android 中 target 为 armeabi

此部分属于构建相关,逻辑位于 flutter.gradle 下。当 App 是通过 armeabi 支持 armv7/arm64 的时候,需要修改 flutter 的默认逻辑。如下所示:

u6riiu7.jpg!web

因为 gradle 本身的特点,此部分修改后直接构建即可生效。

2. 设定 Android 启动时默认使用第一个 launchable-activity,此部分属于 flutter_tools 相关,修改如下:

Yr2Yjia.jpg!web

这里的重点不是如何去修改,而是如何去让修改生效。原理上来说,flutter run/build/analyze/test/upgrade 等命令实际上执行的都是 flutter (flutter_repo_dir/bin/flutter)这一脚本,再通过脚本通过 dart 执行 flutter_tools.snapshot (通过 packages/flutter_tools 生成)。其逻辑如下:

viaumi6.jpg!web

不难看出要重新构建 flutter_tools,可以删除 flutter_repo_dir/bin/cache/flutter_tools.stamp (这样重新生成一次),或者屏蔽掉 if/fi 判断(每一次都会重新生成)。

3. 如何在 Android 工程 Debug 模式下使用 release 模式的 flutter

当开发者在研发中发现 flutter 有些卡顿时,猜测可能是逻辑的原因,也可能是因为是 Debug 下的 flutter。此时可以构建 release 下的 apk,也可以将 flutter 强制修改为 release 模式如下:

MZBziiM.jpg!web

iOS 部分

相关内容包括:Flutter.framework,gen_snapshot,xcode_backend.sh,flutter (flutter_tools)。

yMzuayV.jpg!web

1. 优化构建过程中反复替换 Flutter.framework 导致的重新编译

此部分逻辑属于构建相关,位于 xcode_backend.sh 中,Flutter 为了保证每次获取到正确的 Flutter.framework,每次都会基于配置(见 Generated.xcconfig 配置)查找和替换 Flutter.framework,但这也导致了工程中对此 Framework 有依赖部分代码的重新编译,修改如下:

ru6vMby.jpg!web

2. 如何在 iOS 工程 Debug 模式下使用 release 模式的 flutter

只需要将 Generated.xcconfig 中的 FLUTTER_BUILD_MODE 修改为 release,FLUTTER_FRAMEWORK_DIR 修改为 release 对应的路径即可。

3. armv7 的支持

原始文章请参见:

https://github.com/flutter/engine/wiki/iOS-Builds-Supporting-ARMv7

事实上 flutter 本身是支持 iOS 下的 armv7 的,但目前并未提供官方支持,需要自行修改相关逻辑,具体如下:

a.默认的逻辑可以生成

Flutter.framework (arm64)

b.修改 flutter 以使得 flutter_tools 可以每次重新构建,修改 build_aot.dart 和 mac.dart,将相关针对 iOS 的 arm64 修改为 armv7,修改 gen_snapshot 为 i386 架构。

其中 i386 架构下的 gen_snapshot 可通过以下命令生成:

Qby2Yfv.jpg!web

这里有一个隐含逻辑:

构建 gen_snapshot 的 CPU 相关预定义宏(x86_64/__i386 等),目标 gen_snapshot 的 arch,最终的 App.framework 的架构整体上要保持一致。即 x86_64->x86_64->arm64 或者 i386->i386->armv7。

c.在 iPhone4S 上,会发生因 gen_snapshot 生成不被支持的 SDIV 指令而造成 EXC_BAD_INSTRUCTION (EXC_ARM_UNDEFINED)错误,可通过给 gen_snapshot 添加参数–no-use-integer-division 实现(位于 build_aot.dart)。其背后的逻辑如下图所示:

MnMrqqF.jpg!web

d.基于a和b生成的 Flutter.framework,将其 lipo create 生成同时支持 armv7 和 arm64 的 Flutter.framework。

e.修改 Flutter.framework 下的 Info.plist,移除

mYjqIjZ.jpg!web

同理,对于 App.framework 也要作此操作,以免上架后会受到 App Thining 的影响。

flutter_tools 的调试

例如我们想了解 flutter 在构建 debug 模式下的 apk 的时候,具体执行的逻辑如何,可以按照下面的思路走:

a.了解 flutter_tools 的命令行参数

QjAJNvV.jpg!web

b.以 dart 工程形式打开 packages/flutter_tools,基于获得的参数修改 flutter_tools.dart,设置命令行 dart app 即可开始调试。

UZb6Bzv.jpg!web

定制 engine 与调试

假设我们在 flutter beta v0.3.1 的基础上进行定制与业务开发,为了保证稳定,一定周期内并不升级 SDK,而此时,flutter 在 master 上修改了某个 v0.3.1 上就有的 bug,记为 fix_bug_commit。如何才能跟踪和管理这种情形呢?

1. flutter beta v0.3.1 指定了其对应的 engine commit 为:09d05a389,见

flutter/bin/internal/engine.version。

2. 获取 engine 代码

3. 因为 2 中拿到的是 master 代码,而我们需要的是特定 commit (09d05a389) 对应的代码库,因而从此 commit 拉出新分支:custom_beta_v0.3.1。

4. 基于 custom_beta_v0.3.1(commit:09d05a389),执行 gclient sync,即可拿到对应 flutter beta v0.3.1 的所有 engine 代码。

5. 使用 git cherry-pick fix_bug_commit 将 master 的修改同步到 custom_beta_v0.3.1,如果修改有很多对最新修改的依赖,可能会导致编译失败。

6. 对于 iOS 相关的修改执行以下代码:

JBzYBn6.jpg!web

如果需要调试 Flutter.framework 源代码,构建的时候命令如下:

Qv6N7rf.jpg!web

用生成产物替换掉 flutter 中的 Flutter.framework 和 gen_snapshot,即可调试 engine 源代码。

7. 对于 Android 相关的修改执行以下代码:

bmuE7jr.jpg!web

即可生成针对 Android 的 arm&debug/release/profile

的产物。可用构建产物替换

flutter/bin/cache/artifacts/engine/android*下的 gen_snapshot 和 flutter.jar。

如果对文本的内容有疑问或指正,欢迎告知我们。

另闲鱼技术团队诚聘各路英才,flutter,C++,iOS/Android,Java 都要,欢迎简历来砸。联系邮箱:[email protected]。

参考文档:

1. Flutter’s modes

https://github.com/flutter/flutter/wiki/Flutter%27s-modes

2. iOS Builds Supporting ARMv7

https://github.com/flutter/engine/wiki/iOS-Builds-Supporting-ARMv7

3. Contributing to the Flutter engine

https://github.com/flutter/engine/blob/master/CONTRIBUTING.md

4. Flutter System Architecture

  • https://docs.google.com/presentation/d/1cw7A4HbvM_Abv320rVgPVGiUP2msVs7tfGbkgdrTy0I/edit

5. The magic of flutter

  • https://docs.google.com/presentation/d/1B3p0kP6NV_XMOimRV09Ms75ymIjU5gr6GGIX74Om_DE/edit

6. Symbolicating production crash stacks

https://github.com/flutter/engine/wiki/Symbolicating-production-crash-stacks

7. flutter.io

https://flutter.io/docs

8. 获取本文使用的源代码

https://github.com/FlutterRepo/hello_flutter.git


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK