赞
踩
初入Flutter的开发者,首先需要了解的便是如何编译运行flutter应用。与通常Android工程项目的编译不同,Flutter的打包编译是通过调用flutter命令行来实现的。
在一遍遍编译运行的过程中,你可能经常会思考:在每一条flutter命令的背后究竟做了哪些事?Flutter的编译是如何与传统Android gradle编译流程串联起来的?Dart代码如何编译成可执行的代码?
我们这就来揭示其背后的奥秘。
通常,对于一个标准的Flutter工程,只要执行以下命令就可以完成打包,
flutter build apk
这里默认的属性是--release
,因此会默认打出release包。当然,如果你需要打debug包,可以这么操作:
flutter build apk --debug
首先,我们来看下flutter
命令具体是什么东西。
flutter
本体,在Flutter SDK目录的bin下面,也就是/path-to-flutter-sdk/flutter/bin/flutter
,它是一个命令行脚本,里面的核心在于这一行:
"$DART" $FLUTTER_TOOL_ARGS "$SNAPSHOT_PATH" "$@"
其中,各个参数的具体含义如下:
flutter/bin/cache/flutter_tools.snapshot
。snapshot的含义相当于java中的jar文件。build apk
。因此,这里实际上执行的就是如下命令:
flutter/bin/cache/dart-sdk/bin/dart FLUTTER_TOOL_ARGS= SNAPSHOT_PATH=flutter/bin/cache/flutter_tools.snapshot build apk
可以看出,这个命令实际上和java执行jar文件的方式如出一辙,只不过,这里编译打包的逻辑,都是用Dart语言实现在flutter_tools.snapshot
中的,因此采用了Dart的执行环境。
Dart的snapshot相当于Java的jar,因此,一个snapshot在执行的时候,必然有它的执行入口,也就是类似main
函数的东西。
这里的入口,正是flutter/packages/flutter_tools/bin/flutter_tools.dart
可以看到它的main
函数如下:
- void main(List<String> args) {
- executable.main(args);
- }
它调用了flutter/packages/flutter_tools/lib/executable.dart
的main
函数,我们继续往下看:
- /// Main entry point for commands.
- ///
- /// This function is intended to be used from the `flutter` command line tool.
- Future<void> main(List<String> args) async {
- final bool verbose = args.contains('-v') || args.contains('--verbose');
-
- final bool doctor = (args.isNotEmpty && args.first == 'doctor') ||
- (args.length == 2 && verbose && args.last == 'doctor');
- final bool help = args.contains('-h') || args.contains('--help') ||
- (args.isNotEmpty && args.first == 'help') || (args.length == 1 && verbose);
- final bool muteCommandLogging = help || doctor;
- final bool verboseHelp = help && verbose;
-
- await runner.run(args, <FlutterCommand>[
- AnalyzeCommand(verboseHelp: verboseHelp),
- AttachCommand(verboseHelp: verboseHelp),
- BuildCommand(verboseHelp: verboseHelp), // 对应flutter build apk
- ChannelCommand(verboseHelp: verboseHelp),
- CleanCommand(),
- ConfigCommand(verboseHelp: verboseHelp),
- CreateCommand(),
- DaemonCommand(hidden: !verboseHelp),
- DevicesCommand(),
- DoctorCommand(verbose: verbose),
- DriveCommand(),
- EmulatorsCommand(),
- FormatCommand(),
- IdeConfigCommand(hidden: !verboseHelp),
- InjectPluginsCommand(hidden: !verboseHelp),
- InstallCommand(),
- LogsCommand(),
- MakeHostAppEditableCommand(),
- PackagesCommand(),
- PrecacheCommand(),
- RunCommand(verboseHelp: verboseHelp),
- ScreenshotCommand(),
- ShellCompletionCommand(),
- StopCommand(),
- TestCommand(verboseHelp: verboseHelp),
- TraceCommand(),
- UpdatePackagesCommand(hidden: !verboseHelp),
- UpgradeCommand(),
- ], verbose: verbose,
- muteCommandLogging: muteCommandLogging,
- verboseHelp: verboseHelp);
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
这里的runner
是flutter
的一个命令行运行解析的通用类,在执行runner.run
的时候传入了一系列的XXXCommand
方法,我们只需要知道flutter build
对应的命令都会匹配到BuildCommand()
方法就可以了。
再来看BuildCommand的实现:
- class BuildCommand extends FlutterCommand {
- BuildCommand({bool verboseHelp = false}) {
- addSubcommand(BuildApkCommand(verboseHelp: verboseHelp));
- addSubcommand(BuildAotCommand());
- addSubcommand(BuildIOSCommand());
- addSubcommand(BuildFlxCommand());
- addSubcommand(BuildBundleCommand(verboseHelp: verboseHelp));
- }
-
- @override
- final String name = 'build'; // flutter build
-
- @override
- final String description = 'Flutter build commands.';
-
- @override
- Future<FlutterCommandResult> runCommand() async => null;
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
这里定义的一系列子命令,正是对应了我们运行flutter build -h
所看到的内容:
- Flutter build commands.
-
- Usage: flutter build <subcommand> [arguments]
- -h, --help Print this usage information.
-
- Available subcommands:
- aot Build an ahead-of-time compiled snapshot of your app's Dart code.
- apk Build an Android APK file from your app.
- bundle Build the Flutter assets directory from your app.
- flx Deprecated
- ios Build an iOS application bundle (Mac OS X host only).
BuildCommand
继承了FlutterCommand
,而外部调用BuildCommand
的时候,执行的是这个父类FlutterCommand
的run
方法:
- /// Runs this command.
- ///
- /// Rather than overriding this method, subclasses should override
- /// [verifyThenRunCommand] to perform any verification
- /// and [runCommand] to execute the command
- /// so that this method can record and report the overall time to analytics.
- @override
- Future<void> run() {
- final DateTime startTime = systemClock.now();
-
- return context.run<void>(
- name: 'command',
- overrides: <Type, Generator>{FlutterCommand: () => this},
- body: () async {
- ... ...
- try {
- commandResult = await verifyThenRunCommand();
- } on ToolExit {
- ... ...
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
它主要调用了verifyThenRunCommand
方法。
- /// Perform validation then call [runCommand] to execute the command.
- /// Return a [Future] that completes with an exit code
- /// indicating whether execution was successful.
- ///
- /// Subclasses should override this method to perform verification
- /// then call this method to execute the command
- /// rather than calling [runCommand] directly.
- @mustCallSuper
- Future<FlutterCommandResult> verifyThenRunCommand() async {
- await validateCommand();
-
- // Populate the cache. We call this before pub get below so that the sky_engine
- // package is available in the flutter cache for pub to find.
- if (shouldUpdateCache)
- await cache.updateAll();
-
- if (shouldRunPub) {
- await pubGet(context: PubContext.getVerifyContext(name));
- final FlutterProject project = await FlutterProject.current();
- await project.ensureReadyForPlatformSpecificTooling();
- }
-
- setupApplicationPackages();
-
- final String commandPath = await usagePath;
-
- if (commandPath != null) {
- final Map<String, String> additionalUsageValues = await usageValues;
- flutterUsage.sendCommand(commandPath, parameters: additionalUsageValues);
- }
-
- return await runCommand();
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
这个方法主要做了三件事:
首先,pubGet
会做一些验证,主要是下载pubspec.yaml
里配置的依赖。实际上是通过Dart中的pub
指令来完成的,完整命令行如下:flutter/bin/cache/dart-sdk/bin/pub --verbosity=warning get --no-precompile
接着ensureReadyForPlatformSpecificTooling
与setupApplicationPackages
会依据对应的平台设定好相应的编译环境,这里设定好Android的gradle环境,并且加上一些额外的gradle属性。
最后,调用子类真正的runCommand
的方法。
由于这里执行的是build apk
,所以子类是BuildApkCommand
,因此继续看BuildApkCommand
的runCommand
:
- class BuildApkCommand extends BuildSubCommand {
- ... ...
-
- @override
- Future<FlutterCommandResult> runCommand() async {
- await super.runCommand();
- await buildApk(
- project: await FlutterProject.current(),
- target: targetFile,
- buildInfo: getBuildInfo(),
- );
- return null;
- }
- }
核心也就是这里的buildApk
:
- Future<void> buildApk({
- @required FlutterProject project,
- @required String target,
- BuildInfo buildInfo = BuildInfo.debug
- }) async {
- if (!project.android.isUsingGradle) {
- throwToolExit(
- 'The build process for Android has changed, and the current project configuration\n'
- 'is no longer valid. Please consult\n\n'
- ' https://github.com/flutter/flutter/wiki/Upgrading-Flutter-projects-to-build-with-gradle\n\n'
- 'for details on how to upgrade the project.'
- );
- }
-
- // 检测ANDROID_HOME
- // Validate that we can find an android sdk.
- if (androidSdk == null)
- throwToolExit('No Android SDK found. Try setting the ANDROID_HOME environment variable.');
-
- final List<String> validationResult = androidSdk.validateSdkWellFormed();
- if (validationResult.isNotEmpty) {
- for (String message in validationResult) {
- printError(message, wrap: false);
- }
- throwToolExit('Try re-installing or updating your Android SDK.');
- }
-
- return buildGradleProject(
- project: project,
- buildInfo: buildInfo,
- target: target,
- );
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
这里要求Android工程必须使用gradle,否则直接退出。然后,检测ANDROID_HOME
是否存在,并调用buildGradleProject
。
buildGradleProject
主要做的是加入一些必要的参数,执行gradle命令,其最终执行的完整命令行如下:
flutter_hello/android/gradlew -q -Ptarget=lib/main.dart -Ptrack-widget-creation=false -Ptarget-platform=android-arm assembleRelease
由此,又回到了我们熟悉的Android世界,它只是在我们熟悉的gradlew assembleRelease
中增加了一些额外参数。flutter_hello
是我们的flutter示例工程。
这里为什么需要如此大费周折地绕个圈子来执行gradle命令呢?自然是因为Flutter本身的定位,它是一个跨平台方案,因此对于各个平台都有其特定的实现,因此才需要以一个统一的flutter build
入口来转换成各个平台实际所需的编译命令。
既然已经走到了gradlew,理所当然地,我们直接看他的build.gradle
就可以了。他的内容是由Flutter SDK生成的,相比于普通的Android工程,是有些许不同。不过我们只需要根据gradle的知识体系来理解就能贯通始终。
其与标准Android gradle工程的不同之处仅仅在于文件开头的部分:
- def localProperties = new Properties()
- def localPropertiesFile = rootProject.file('local.properties')
- if (localPropertiesFile.exists()) {
- localPropertiesFile.withReader('UTF-8') { reader ->
- localProperties.load(reader)
- }
- }
-
- def flutterRoot = localProperties.getProperty('flutter.sdk')
- if (flutterRoot == null) {
- throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
- }
-
- def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
- if (flutterVersionCode == null) {
- flutterVersionCode = '1'
- }
-
- def flutterVersionName = localProperties.getProperty('flutter.versionName')
- if (flutterVersionName == null) {
- flutterVersionName = '1.0'
- }
-
- apply plugin: 'com.android.application'
- apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
-
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
这里主要是取得local.properties
中的一些Flutter相关属性,分别是flutter.sdk
、flutter.versionCode
和flutter.versionName
,他们指示了flutter sdk的路径,以及一些版本号信息。
接下来,便进入了flutter/packages/flutter_tools/gradle/flutter.gradle
中。
flutter.gradle
里面主要是实现了一个FlutterPlugin
,它是一个标准的gradle plugin,因此,它必然会定义一些task以及设定必要的依赖,addFlutterTask
方法中设置了这些依赖关系:
- // in addFlutterTask
- // We know that the flutter app is a subproject in another Android app when these tasks exist.
- Task packageAssets = project.tasks.findByPath(":flutter:package${variant.name.capitalize()}Assets")
- Task cleanPackageAssets = project.tasks.findByPath(":flutter:cleanPackage${variant.name.capitalize()}Assets")
- Task copyFlutterAssetsTask = project.tasks.create(name: "copyFlutterAssets${variant.name.capitalize()}", type: Copy) {
- dependsOn flutterTask
- dependsOn packageAssets ? packageAssets : variant.mergeAssets
- dependsOn cleanPackageAssets ? cleanPackageAssets : "clean${variant.mergeAssets.name.capitalize()}"
- into packageAssets ? packageAssets.outputDir : variant.mergeAssets.outputDir
- with flutterTask.assets
- }
- if (packageAssets) {
- // Only include configurations that exist in parent project.
- Task mergeAssets = project.tasks.findByPath(":app:merge${variant.name.capitalize()}Assets")
- if (mergeAssets) {
- mergeAssets.dependsOn(copyFlutterAssetsTask)
- }
- } else {
- variant.outputs[0].processResources.dependsOn(copyFlutterAssetsTask)
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
processXXXResources
这个Task会依赖于copyFlutterAssetsTask
,这就会使得快执行到processXXXResources
的时候必须先执行完copyFlutterAssetsTask
才能继续。就这样,flutter的相关处理就嵌入到gradle的编译流程中了。
另外,copyFlutterAssetsTask
依赖了flutterTask
和mergeXXXAssets
。也就是说,当flutterTask
使得flutter编译完成,并且mergeXXXAssets
执行完毕,也就是正常Android的assets处理完成后,flutter相应的产物就会被copyFlutterAssetsTask
复制到build/app/intermediates/merged_assets/debug/mergeXXXAssets/out
目录下。这里的XXX
指代各种build variant,也就是Debug
或者Release
。
flutter的编译产物,具体是由flutterTask
的getAssets
方法指定的:
- CopySpec getAssets() {
- return project.copySpec {
- from "${intermediateDir}"
-
- include "flutter_assets/**" // the working dir and its files
-
- if (buildMode == 'release' || buildMode == 'profile') {
- if (buildSharedLibrary) {
- include "app.so"
- } else {
- include "vm_snapshot_data"
- include "vm_snapshot_instr"
- include "isolate_snapshot_data"
- include "isolate_snapshot_instr"
- }
- }
- }
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
具体来说,这些产物就是build/app/intermediates/flutter/XXX
下面的flutter_assets/
目录中的所有内容。如果是release或者profile版本的话,还包含Dart的二进制产物app.so
或者***_snapshot_***
。可以看到,除了默认情况的***_snapshot_***
,我们还可以指定Dart产物为常规的so
库形式。
显然,flutter的编译过程就包含在flutterTask中,他的定义是这样的:
- FlutterTask flutterTask = project.tasks.create(name: "${flutterBuildPrefix}${variant.name.capitalize()}", type: FlutterTask) {
- flutterRoot this.flutterRoot
- flutterExecutable this.flutterExecutable
- buildMode flutterBuildMode
- localEngine this.localEngine
- localEngineSrcPath this.localEngineSrcPath
- targetPath target
- verbose verboseValue
- fileSystemRoots fileSystemRootsValue
- fileSystemScheme fileSystemSchemeValue
- trackWidgetCreation trackWidgetCreationValue
- compilationTraceFilePath compilationTraceFilePathValue
- buildHotUpdate buildHotUpdateValue
- buildSharedLibrary buildSharedLibraryValue
- targetPlatform targetPlatformValue
- sourceDir project.file(project.flutter.source)
- intermediateDir project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter/${variant.name}")
- extraFrontEndOptions extraFrontEndOptionsValue
- extraGenSnapshotOptions extraGenSnapshotOptionsValue
- }
-
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
这里传入了FlutterTask
所需的各种参数,而具体的FlutterTask
核心只有一句话
- class FlutterTask extends BaseFlutterTask {
- ... ...
- @TaskAction
- void build() {
- buildBundle()
- }
- }
也就是调用了buildBundle
方法,我们先看它的前半部分:
-
- void buildBundle() {
- if (!sourceDir.isDirectory()) {
- throw new GradleException("Invalid Flutter source directory: ${sourceDir}")
- }
-
- intermediateDir.mkdirs()
-
- if (buildMode == "profile" || buildMode == "release") {
- project.exec {
- executable flutterExecutable.absolutePath
- workingDir sourceDir
- if (localEngine != null) {
- args "--local-engine", localEngine
- args "--local-engine-src-path", localEngineSrcPath
- }
- args "build", "aot"
- args "--suppress-analytics"
- args "--quiet"
- args "--target", targetPath
- args "--target-platform", "android-arm"
- args "--output-dir", "${intermediateDir}"
- if (trackWidgetCreation) {
- args "--track-widget-creation"
- }
- if (extraFrontEndOptions != null) {
- args "--extra-front-end-options", "${extraFrontEndOptions}"
- }
- if (extraGenSnapshotOptions != null) {
- args "--extra-gen-snapshot-options", "${extraGenSnapshotOptions}"
- }
- if (buildSharedLibrary) {
- args "--build-shared-library"
- }
- if (targetPlatform != null) {
- args "--target-platform", "${targetPlatform}"
- }
- args "--${buildMode}"
- }
- }
- ... ...
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
这里,由于是release版本,因此会先编译aot的二进制Dart产物,也就是***_snapshot_***
产物,实际是执行以下命令:
flutter build aot --suppress-analytics --quiet --target lib/main.dart --target-platform android-arm --output-dir /path-to-project/build/app/intermediates/flutter/release --target-platform android-arm --release
接着,buildBundle方法的后半部分还会调用一次flutter命令:
- void buildBundle() {
- ... ....
- project.exec {
- executable flutterExecutable.absolutePath
- workingDir sourceDir
- if (localEngine != null) {
- args "--local-engine", localEngine
- args "--local-engine-src-path", localEngineSrcPath
- }
- args "build", "bundle"
- args "--suppress-analytics"
- args "--target", targetPath
- if (verbose) {
- args "--verbose"
- }
- if (fileSystemRoots != null) {
- for (root in fileSystemRoots) {
- args "--filesystem-root", root
- }
- }
- if (fileSystemScheme != null) {
- args "--filesystem-scheme", fileSystemScheme
- }
- if (trackWidgetCreation) {
- args "--track-widget-creation"
- }
- if (compilationTraceFilePath != null) {
- args "--precompile", compilationTraceFilePath
- }
- if (buildHotUpdate) {
- args "--hotupdate"
- }
- if (extraFrontEndOptions != null) {
- args "--extra-front-end-options", "${extraFrontEndOptions}"
- }
- if (extraGenSnapshotOptions != null) {
- args "--extra-gen-snapshot-options", "${extraGenSnapshotOptions}"
- }
- if (targetPlatform != null) {
- args "--target-platform", "${targetPlatform}"
- }
- if (buildMode == "release" || buildMode == "profile") {
- args "--precompiled"
- } else {
- args "--depfile", "${intermediateDir}/snapshot_blob.bin.d"
- }
- args "--asset-dir", "${intermediateDir}/flutter_assets"
- if (buildMode == "debug") {
- args "--debug"
- }
- if (buildMode == "profile" || buildMode == "dynamicProfile") {
- args "--profile"
- }
- if (buildMode == "release" || buildMode == "dynamicRelease") {
- args "--release"
- }
- if (buildMode == "dynamicProfile" || buildMode == "dynamicRelease") {
- args "--dynamic"
- }
- }
- }
-
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
也就是执行了
flutter build bundle --suppress-analytics --target lib/main.dart --target-platform android-arm --precompiled --asset-dir /Users/xl/WorkSpace/FlutterProjects/flutter_hello/build/app/intermediates/flutter/release/flutter_assets --release]
我们马上就来看下flutter build aot
和flutter build bundle
这两个命令的具体实现。
回顾一下这个命令:
flutter build aot --suppress-analytics --quiet --target lib/main.dart --target-platform android-arm --output-dir /path-to-project/build/app/intermediates/flutter/release --target-platform android-arm --release
之前在分析flutter build apk的时候有提到,flutter build会通过flutter命令行脚本转化为启动一个Dart虚拟机并执行flutter_tool.snapshot,因此上述命令转化为:
flutter/bin/cache/dart-sdk/bin/dart FLUTTER_TOOL_ARGS= SNAPSHOT_PATH=flutter/bin/cache/flutter_tools.snapshot build aot --suppress-analytics --quiet --target lib/main.dart --target-platform android-arm --output-dir /path-to-project/flutter_hello/build/app/intermediates/flutter/release --target-platform android-arm --release
回顾一下之前讲解flutter_tools.snapshot提到的,BuildCommand里定义了一系列子命令:
- class BuildCommand extends FlutterCommand {
- BuildCommand({bool verboseHelp = false}) {
- addSubcommand(BuildApkCommand(verboseHelp: verboseHelp));
- addSubcommand(BuildAotCommand());
- addSubcommand(BuildIOSCommand());
- addSubcommand(BuildFlxCommand());
- addSubcommand(BuildBundleCommand(verboseHelp: verboseHelp));
- }
- ... ...
这里对应的自然是BuildAotCommand
,直接来看它的runCommand
:
- class BuildAotCommand extends BuildSubCommand {
- ... ...
-
- Future<FlutterCommandResult> runCommand() async {
- ... ...
-
- String mainPath = findMainDartFile(targetFile);
- final AOTSnapshotter snapshotter = AOTSnapshotter();
-
- // Compile to kernel.
- mainPath = await snapshotter.compileKernel(
- platform: platform,
- buildMode: buildMode,
- mainPath: mainPath,
- packagesPath: PackageMap.globalPackagesPath,
- trackWidgetCreation: false,
- outputPath: outputPath,
- extraFrontEndOptions: argResults[FlutterOptions.kExtraFrontEndOptions],
- );
-
- ... ...
-
- // Build AOT snapshot.
- if (platform == TargetPlatform.ios) {
- ... ...
- } else {
- // Android AOT snapshot.
- final int snapshotExitCode = await snapshotter.build(
- platform: platform,
- buildMode: buildMode,
- mainPath: mainPath,
- packagesPath: PackageMap.globalPackagesPath,
- outputPath: outputPath,
- buildSharedLibrary: argResults['build-shared-library'],
- extraGenSnapshotOptions: argResults[FlutterOptions.kExtraGenSnapshotOptions],
- );
- if (snapshotExitCode != 0) {
- status?.cancel();
- throwToolExit('Snapshotting exited with non-zero exit code: $snapshotExitCode');
- }
- }
- ... ...
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
这里面其实又再次调用了两个Dart虚拟机命令,主要做了两件事:
这两个产物都是Dart代码生成的程序文件。
kernel文件格式是由Dart定义的一种特殊数据格式,Dart虚拟机,会以解释方式来执行它。我们之前提到的flutter_tool.snapshot
等snapshot文件,实际上都是kernel文件。
而AOT可执行文件,需要等kernel文件生成完毕后,在编译期间根据kernel来生成的。它是二进制的,机器平台(arm、arm64、x86等)可执行的代码,也因此它被称为AOT(Ahead of time compiling)。在运行的时候它的指令码已经是平台的机器码了,因此不再需要Dart虚拟机解析,执行速度更快。release模式下,打包进APK的Dart源码都是以AOT文件的形式存在的。
我们先来分析第一步,也就是Compile to kernel
这一步。它会根据Dart代码文件生成kernel文件,具体是执行了以下命令:
flutter/bin/cache/dart-sdk/bin/dart /path-to-flutter-sdk/flutter/bin/cache/artifacts/engine/darwin-x64/frontend_server.dart.snapshot --sdk-root flutter/bin/cache/artifacts/engine/common/flutter_patched_sdk/ --strong --target=flutter --aot --tfa -Ddart.vm.product=true --packages .packages --output-dill /path-to-project/flutter_hello/build/app/intermediates/flutter/release/app.dill --depfile /path-to-project/flutter_hello/build/app/intermediates/flutter/release/kernel_compile.d package:flutter_hello/main.dart
可以看到,这里是通过Dart虚拟机启动了frontend_server.dart.snapshot
来把Dart代码文件编译成名为app.dill
的kernel文件。
而这个frontend_server.dart.snapshot
不同于之前的flutter_tool.snapshot
,他是存在于engine的Dart代码中的,而不是Flutter SDK中。
engine的代码需要单独下载:https://github.com/flutter/engine
frontend_server.dart.snapshot
的入口就存在于engine/frontend_server/bin/starter.dart
。经过一系列跳转后,最终执行的是compileToKernel
方法:
- Future<Component> compileToKernel(Uri source, CompilerOptions options,
- {bool aot: false,
- bool useGlobalTypeFlowAnalysis: false,
- Map<String, String> environmentDefines,
- bool genBytecode: false,
- bool dropAST: false,
- bool useFutureBytecodeFormat: false,
- bool enableAsserts: false,
- bool enableConstantEvaluation: true}) async {
-
- ... ...
-
- final component = await kernelForProgram(source, options);
-
- ... ...
-
- // Run global transformations only if component is correct.
- if (aot && component != null) {
- await _runGlobalTransformations(
- source,
- options,
- component,
- useGlobalTypeFlowAnalysis,
- environmentDefines,
- enableAsserts,
- enableConstantEvaluation,
- errorDetector);
- }
-
- if (genBytecode && !errorDetector.hasCompilationErrors && component != null) {
- await runWithFrontEndCompilerContext(source, options, component, () {
- generateBytecode(component,
- dropAST: dropAST,
- useFutureBytecodeFormat: useFutureBytecodeFormat,
- environmentDefines: environmentDefines);
- });
- }
-
- // Restore error handler (in case 'options' are reused).
- options.onDiagnostic = errorDetector.previousErrorHandler;
-
- return component;
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
这里主要执行的有三步:kernelForProgram
、_runGlobalTransformations
和runWithFrontEndCompilerContext
。
kernelForProgram
- Future<Component> kernelForProgram(Uri source, CompilerOptions options) async {
- var pOptions = new ProcessedOptions(options: options, inputs: [source]);
- return await CompilerContext.runWithOptions(pOptions, (context) async {
- var component = (await generateKernelInternal())?.component;
- if (component == null) return null;
-
- if (component.mainMethod == null) {
- context.options.report(
- messageMissingMain.withLocation(source, -1, noLength),
- Severity.error);
- return null;
- }
- return component;
- });
- }
我们可以先看下这个方法的注释:
- /// Generates a kernel representation of the program whose main library is in
- /// the given [source].
- ///
- /// Intended for whole-program (non-modular) compilation.
- ///
- /// Given the Uri of a file containing a program's `main` method, this function
- /// follows `import`, `export`, and `part` declarations to discover the whole
- /// program, and converts the result to Dart Kernel format.
- ///
- /// If `compileSdk` in [options] is true, the generated component will include
- /// code for the SDK.
- ///
- /// If summaries are provided in [options], the compiler will use them instead
- /// of compiling the libraries contained in those summaries. This is useful, for
- /// example, when compiling for platforms that already embed those sources (like
- /// the sdk in the standalone VM).
- ///
- /// The input [source] is expected to be a script with a main method, otherwise
- /// an error is reported.
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
注释已经说得比较清楚了。它会根据传入的包含main函数的Dart代码文件路径,并根据其中的import
, export
, 和part
来找到完整的程序所包含的所有Dart代码,最终把他们转换成kernel格式的文件。这一步最主要是生成一个Component对象,它会持有整个Dart程序的所有信息。
Component的定义如下
- /// A way to bundle up libraries in a component.
- class Component extends TreeNode {
- final CanonicalName root;
-
- final List<Library> libraries;
-
- /// Map from a source file URI to a line-starts table and source code.
- /// Given a source file URI and a offset in that file one can translate
- /// it to a line:column position in that file.
- final Map<Uri, Source> uriToSource;
-
- /// Mapping between string tags and [MetadataRepository] corresponding to
- /// those tags.
- final Map<String, MetadataRepository<dynamic>> metadata =
- <String, MetadataRepository<dynamic>>{};
- /// Reference to the main method in one of the libraries.
- Reference mainMethodName;
- ... ...
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
Component
其实只是一个包装类,其主要功能就是组织程序中所有Library,后续所有对kernel的处理都是围绕Componet
展开的。而Library
指的是app源文件、所有的package或者dart库以及第三方库,它们每个Library都各自包含了自己包下面的所有Class
、Field
、Procedure
等组成部分。
- class Library extends NamedNode implements Comparable<Library>, FileUriNode {
- /// An import path to this library.
- ///
- /// The [Uri] should have the `dart`, `package`, `app`, or `file` scheme.
- ///
- /// If the URI has the `app` scheme, it is relative to the application root.
- Uri importUri;
-
- /// The URI of the source file this library was loaded from.
- Uri fileUri;
-
- /// If true, the library is part of another build unit and its contents
- /// are only partially loaded.
- ///
- /// Classes of an external library are loaded at one of the [ClassLevel]s
- /// other than [ClassLevel.Body]. Members in an external library have no
- /// body, but have their typed interface present.
- ///
- /// If the library is non-external, then its classes are at [ClassLevel.Body]
- /// and all members are loaded.
- bool isExternal;
-
- String name;
-
- @nocoq
- final List<Expression> annotations;
-
- final List<LibraryDependency> dependencies;
-
- /// References to nodes exported by `export` declarations that:
- /// - aren't ambiguous, or
- /// - aren't hidden by local declarations.
- @nocoq
- final List<Reference> additionalExports = <Reference>[];
-
- @informative
- final List<LibraryPart> parts;
-
- final List<Typedef> typedefs;
- final List<Class> classes;
- final List<Procedure> procedures;
- final List<Field> fields;
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
由于Componet
只是一个包装类,因此主要通过拿到它,来处理其中的各个Library
对象。
generateKernelInternal
方法经过一连串调用会走到buildBody
方法,这个方法会对单个Library
进行处理。而外层会依次把Componet
中的各个Library
传入到这个方法。
- Future<Null> buildBody(LibraryBuilder library) async {
- if (library is SourceLibraryBuilder) {
- // We tokenize source files twice to keep memory usage low. This is the
- // second time, and the first time was in [buildOutline] above. So this
- // time we suppress lexical errors.
- Token tokens = await tokenize(library, suppressLexicalErrors: true);
- if (tokens == null) return;
- DietListener listener = createDietListener(library);
- DietParser parser = new DietParser(listener);
- parser.parseUnit(tokens);
- for (SourceLibraryBuilder part in library.parts) {
- if (part.partOfLibrary != library) {
- // Part was included in multiple libraries. Skip it here.
- continue;
- }
- Token tokens = await tokenize(part);
- if (tokens != null) {
- listener.uri = part.fileUri;
- listener.partDirectiveIndex = 0;
- parser.parseUnit(tokens);
- }
- }
- }
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
buildBody
方法会对该Library
以及Library
中的part
部分分别做词法分析(tokenize)和语法分析(parse)。
tokenize
做的就是对Library
中的Dart源码,根据词法规则解析成一个个的词法单元tokens。
而parseUnit
就是把前面tokenize
得到的tokens根据Dart语法规则进一步解析成为抽象语法树。
由此,Dart源码已经被转化成了一颗抽象语法树并存储于Component
中了。
_runGlobalTransformations
而第二步的_runGlobalTransformations
主要是执行了一系列transformations操作:
- Future _runGlobalTransformations(
- Uri source,
- CompilerOptions compilerOptions,
- Component component,
- bool useGlobalTypeFlowAnalysis,
- Map<String, String> environmentDefines,
- bool enableAsserts,
- bool enableConstantEvaluation,
- ErrorDetector errorDetector) async {
- if (errorDetector.hasCompilationErrors) return;
-
- final coreTypes = new CoreTypes(component);
- _patchVmConstants(coreTypes);
-
- // TODO(alexmarkov, dmitryas): Consider doing canonicalization of identical
- // mixin applications when creating mixin applications in frontend,
- // so all backends (and all transformation passes from the very beginning)
- // can benefit from mixin de-duplication.
- // At least, in addition to VM/AOT case we should run this transformation
- // when building a platform dill file for VM/JIT case.
- mixin_deduplication.transformComponent(component);
-
- if (enableConstantEvaluation) {
- await _performConstantEvaluation(source, compilerOptions, component,
- coreTypes, environmentDefines, enableAsserts);
-
- if (errorDetector.hasCompilationErrors) return;
- }
-
- if (useGlobalTypeFlowAnalysis) {
- globalTypeFlow.transformComponent(
- compilerOptions.target, coreTypes, component);
- } else {
- devirtualization.transformComponent(coreTypes, component);
- no_dynamic_invocations_annotator.transformComponent(component);
- }
-
- // We don't know yet whether gen_snapshot will want to do obfuscation, but if
- // it does it will need the obfuscation prohibitions.
- obfuscationProhibitions.transformComponent(component, coreTypes);
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
可以看到最后一行执行了混淆的transform。这里的混淆主要是做一些mapping,其实就类似于proguard做的一些事,不过目前看来,这方面的支持还比较初级,一些自定义规则之类的功能应该还不如proguard那么完善。
runWithFrontEndCompilerContext
runWithFrontEndCompilerContext
主要传入一个回调方法:
- await runWithFrontEndCompilerContext(source, options, component, () {
- generateBytecode(component,
- dropAST: dropAST,
- useFutureBytecodeFormat: useFutureBytecodeFormat,
- environmentDefines: environmentDefines);
- });
也就是这里的generateBytecode
,它需要拿到之前kernelForProgram
所生成的Component
对象,并根据其中的抽象语法树来生成具体的kernel字节码。
- void generateBytecode(Component component,
- {bool dropAST: false,
- bool omitSourcePositions: false,
- bool useFutureBytecodeFormat: false,
- Map<String, String> environmentDefines,
- ErrorReporter errorReporter}) {
- final coreTypes = new CoreTypes(component);
- void ignoreAmbiguousSupertypes(Class cls, Supertype a, Supertype b) {}
- final hierarchy = new ClassHierarchy(component,
- onAmbiguousSupertypes: ignoreAmbiguousSupertypes);
- final typeEnvironment =
- new TypeEnvironment(coreTypes, hierarchy, strongMode: true);
- final constantsBackend =
- new VmConstantsBackend(environmentDefines, coreTypes);
- final errorReporter = new ForwardConstantEvaluationErrors(typeEnvironment);
- new BytecodeGenerator(
- component,
- coreTypes,
- hierarchy,
- typeEnvironment,
- constantsBackend,
- omitSourcePositions,
- useFutureBytecodeFormat,
- errorReporter)
- .visitComponent(component);
- if (dropAST) {
- new DropAST().visitComponent(component);
- }
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
这里主要就是调用了BytecodeGenerator.visitComponent
,visit这个Component
对象的时候,就会顺势访问到其中所有的Library
。BytecodeGenerator
这个类比较繁琐,他对于语法树中各个语法类型的情况分别有不同的处理,我们来大概浏览一下:
- class BytecodeGenerator extends RecursiveVisitor<Null> {
- ... ...
-
- @override
- visitComponent(Component node) => node.visitChildren(this);
-
- @override
- visitLibrary(Library node) {
- if (node.isExternal) {
- return;
- }
- visitList(node.classes, this);
- visitList(node.procedures, this);
- visitList(node.fields, this);
- }
-
- @override
- visitClass(Class node) {
- visitList(node.constructors, this);
- visitList(node.procedures, this);
- visitList(node.fields, this);
- }
-
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
这里的visitXXXX
系列方法会递归地访问其成员节点,由此完整遍历并处理好整颗语法树。
一个Library
中,包含了Constructor
、Procedure
、Field
等几个基本的组成单元,在以visitXXXX
访问它们的时候,最终都会调用defaultMember
方法。
- @override
- defaultMember(Member node) {
- if (node.isAbstract) {
- return;
- }
- try {
- if (node is Field) {
- if (node.isStatic && !_hasTrivialInitializer(node)) {
- start(node);
- if (node.isConst) {
- _genPushConstExpr(node.initializer);
- } else {
- node.initializer.accept(this);
- }
- _genReturnTOS();
- end(node);
- }
- } else if ((node is Procedure && !node.isRedirectingFactoryConstructor) ||
- (node is Constructor)) {
- start(node);
- if (node is Constructor) {
- _genConstructorInitializers(node);
- }
- if (node.isExternal) {
- final String nativeName = getExternalName(node);
- if (nativeName == null) {
- return;
- }
- _genNativeCall(nativeName);
- } else {
- node.function?.body?.accept(this);
- // BytecodeAssembler eliminates this bytecode if it is unreachable.
- asm.emitPushNull();
- }
- _genReturnTOS();
- end(node);
- }
- } on BytecodeLimitExceededException {
- // Do not generate bytecode and fall back to using kernel AST.
- hasErrors = true;
- end(node);
- }
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
这个方法会继续调用_genXXXX
系列方法,并且_genXXXX
方法之间也会互相调用。我们先来大致看下它们都是哪些方法:
- 183: void _genNativeCall(String nativeName) {
- 288: void _genConstructorInitializers(Constructor node) {
- 314: void _genFieldInitializer(Field field, Expression initializer) {
- 330: void _genArguments(Expression receiver, Arguments arguments) {
- 339: void _genPushBool(bool value) {
- 347: void _genPushInt(int value) {
- 370: void _genPushConstExpr(Expression expr) {
- 383: void _genReturnTOS() {
- 387: void _genStaticCall(Member target, ConstantArgDesc argDesc, int totalArgCount,
- 401: void _genStaticCallWithArgs(Member target, Arguments args,
- 424: void _genTypeArguments(List<DartType> typeArgs, {Class instantiatingClass}) {
- 453: void _genPushInstantiatorAndFunctionTypeArguments(List<DartType> types) {
- 469: void _genPushInstantiatorTypeArguments() {
- 556: void _genPushFunctionTypeArguments() {
- 564: void _genPushContextForVariable(VariableDeclaration variable,
- 578: void _genPushContextIfCaptured(VariableDeclaration variable) {
- 584: void _genLoadVar(VariableDeclaration v, {int currentContextLevel}) {
- ... ...
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
从名字很容易看出,他们的作用,正是用来生成代码结构的各个元素,包括赋值语句、返回语句、判断语句等等。
而_genXXXX
方法还不是最终执行者,真正的幕后英雄,是asm.emitXXXX
系列函数。
就以bool
赋值语句_genPushBool
为例:
- void _genPushBool(bool value) {
- if (value) {
- asm.emitPushTrue();
- } else {
- asm.emitPushFalse();
- }
- }
它分别根据value的不同,调用了asm.emitPushTrue和asm.emitPushFalse。就以设置true值为例:
- void emitPushTrue() {
- emitWord(_encode0(Opcode.kPushTrue));
- }
-
- int _encode0(Opcode opcode) => _uint8(opcode.index);
-
- void emitWord(int word) {
- if (isUnreachable) {
- return;
- }
- _encodeBufferIn[0] = word;
- bytecode.addAll(_encodeBufferOut);
- }
Opcode.kPushTrue
表示一个push true的字节码值,emitPushTrue
会将其转为一个int大小的字节码,并写入到bytecode
之中
Dart定义了一系列的字节码指令集,
- enum Opcode {
- kTrap,
-
- // Prologue and stack management.
- kEntry,
- kEntryFixed,
- kEntryOptional,
- kLoadConstant,
- kFrame,
- kCheckFunctionTypeArgs,
- kCheckStack,
-
- // Object allocation.
- kAllocate,
- kAllocateT,
- kCreateArrayTOS,
-
- // Context allocation and access.
- kAllocateContext,
- kCloneContext,
- kLoadContextParent,
- kStoreContextParent,
- kLoadContextVar,
- kStoreContextVar,
-
- // Constants.
- kPushConstant,
- kPushNull,
- kPushTrue,
- kPushFalse,
- kPushInt,
-
- // Locals and expression stack.
- kDrop1,
- kPush,
- kPopLocal,
- kStoreLocal,
-
- ... ...
-
- // Int operations.
- kNegateInt,
- kAddInt,
- kSubInt,
- kMulInt,
- kTruncDivInt,
- kModInt,
- kBitAndInt,
- kBitOrInt,
- kBitXorInt,
- kShlInt,
- kShrInt,
- kCompareIntEq,
- kCompareIntGt,
- kCompareIntLt,
- kCompareIntGe,
- kCompareIntLe,
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
语法树的各个部分将被翻译为上面的不同指令,最终,整个语法树完整解析为二进制指令流,并存放于BytecodeAssembler
的bytecode
成员中。
- class BytecodeAssembler {
- ... ...
-
- final List<int> bytecode = new List<int>();
- ... ...
-
- void emitWord(int word) {
- if (isUnreachable) {
- return;
- }
- _encodeBufferIn[0] = word;
- bytecode.addAll(_encodeBufferOut); // 都被add进bytecode
- }
-
- ... ...
-
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
至此,Dart代码已经被解析为kernel格式的指令流,接下来,我们来看下它是如何被写进文件的。
写kernel文件
我们先来回顾一下之前的generateBytecode
,它是一个全局函数,在它其中new
了一个BytecodeGenerator
,并通过它的visitComponent
方法来解析语法树,并且生成二进制指令流到BytecodeAssembler
的bytecode
成员中,这个BytecodeAssembler
对应的是BytecodeGenerator
的成员字段asm
,主要代码如下:
- void generateBytecode(Component component,
- {bool dropAST: false,
- bool omitSourcePositions: false,
- bool useFutureBytecodeFormat: false,
- Map<String, String> environmentDefines,
- ErrorReporter errorReporter}) {
- ... ...
- new BytecodeGenerator(
- component,
- coreTypes,
- hierarchy,
- typeEnvironment,
- constantsBackend,
- omitSourcePositions,
- useFutureBytecodeFormat,
- errorReporter)
- .visitComponent(component);
- ... ...
- }
-
- // BytecodeGenerator的成员字段asm是BytecodeAssembler
- class BytecodeGenerator extends RecursiveVisitor<Null> {
- ... ...
- BytecodeAssembler asm;
- ... ...
- }
-
-
- // BytecodeAssembler的bytecode中存放所有二进制指令流
- class BytecodeAssembler {
- ... ...
- final List<int> bytecode = new List<int>();
- ... ...
- }
-
-
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
而在用visitComponent
遍历语法树的时候,记得我们调用的一些列visitXXXX
都会走到defaultMember
,我们再来看一下defaultMember
。
- @override
- defaultMember(Member node) {
- if (node.isAbstract) {
- return;
- }
- try {
- if (node is Field) {
- if (node.isStatic && !_hasTrivialInitializer(node)) {
- start(node);
- if (node.isConst) {
- _genPushConstExpr(node.initializer);
- } else {
- node.initializer.accept(this);
- }
- _genReturnTOS();
- end(node);
- }
- } else if ((node is Procedure && !node.isRedirectingFactoryConstructor) ||
- (node is Constructor)) {
- start(node);
- if (node is Constructor) {
- _genConstructorInitializers(node);
- }
- if (node.isExternal) {
- final String nativeName = getExternalName(node);
- if (nativeName == null) {
- return;
- }
- _genNativeCall(nativeName);
- } else {
- node.function?.body?.accept(this);
- // BytecodeAssembler eliminates this bytecode if it is unreachable.
- asm.emitPushNull();
- }
- _genReturnTOS();
- end(node);
- }
- } on BytecodeLimitExceededException {
- // Do not generate bytecode and fall back to using kernel AST.
- hasErrors = true;
- end(node);
- }
- }
-
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
这里注意到,所有分支最终都会调用end(node)
,
- void end(Member node) {
- if (!hasErrors) {
- final formatVersion = useFutureBytecodeFormat
- ? futureBytecodeFormatVersion
- : stableBytecodeFormatVersion;
- metadata.mapping[node] = new BytecodeMetadata(formatVersion, cp,
- asm.bytecode, asm.exceptionsTable, nullableFields, closures);
- }
-
- ... ...
- }
可以看到,在end
函数中,asm.bytecode
被转移到了metadata
,而这个metadata
在构造方法的时候就已经被加进了component
中:
- BytecodeGenerator(
- this.component,
- this.coreTypes,
- this.hierarchy,
- this.typeEnvironment,
- this.constantsBackend,
- this.omitSourcePositions,
- this.useFutureBytecodeFormat,
- this.errorReporter)
- : recognizedMethods = new RecognizedMethods(typeEnvironment) {
- component.addMetadataRepository(metadata);
- }
再回到最开始,compileToKernel
是由compile
调用的,它被传入了一个component
对象,并且最后通过writeDillFile
方法来写到文件中。
- @override
- Future<bool> compile(
- String filename,
- ArgResults options, {
- IncrementalCompiler generator,
- }) async {
-
- ... ...
-
- Component component;
-
- ... ...
-
- component = await _runWithPrintRedirection(() => compileToKernel(
- _mainSource, compilerOptions,
- aot: options['aot'],
- useGlobalTypeFlowAnalysis: options['tfa'],
- environmentDefines: environmentDefines));
-
- ... ...
-
- await writeDillFile(component, _kernelBinaryFilename,
- filterExternal: importDill != null);
-
- ... ...
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
writeDillFile的实现如下:
- writeDillFile(Component component, String filename,
- {bool filterExternal: false}) async {
- final IOSink sink = new File(filename).openWrite();
- final BinaryPrinter printer = filterExternal
- ? new LimitedBinaryPrinter(
- sink, (lib) => !lib.isExternal, true /* excludeUriToSource */)
- : printerFactory.newBinaryPrinter(sink);
-
- component.libraries.sort((Library l1, Library l2) {
- return "${l1.fileUri}".compareTo("${l2.fileUri}");
- });
-
- component.computeCanonicalNames();
- for (Library library in component.libraries) {
- library.additionalExports.sort((Reference r1, Reference r2) {
- return "${r1.canonicalName}".compareTo("${r2.canonicalName}");
- });
- }
- if (unsafePackageSerialization == true) {
- writePackagesToSinkAndTrimComponent(component, sink);
- }
-
- printer.writeComponentFile(component);
- await sink.close();
- }
-
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
filename
就是前面通过命令行参数传入的/path-to-project/flutter_hello/build/app/intermediates/flutter/release/app.dill
。这里我们主要关注writeComponentFile
。
- void writeComponentFile(Component component) {
- computeCanonicalNames(component);
- final componentOffset = getBufferOffset();
- writeUInt32(Tag.ComponentFile);
- writeUInt32(Tag.BinaryFormatVersion);
- indexLinkTable(component);
- indexUris(component);
- _collectMetadata(component);
- if (_metadataSubsections != null) {
- _writeNodeMetadataImpl(component, componentOffset);
- }
- libraryOffsets = <int>[];
- CanonicalName main = getCanonicalNameOfMember(component.mainMethod);
- if (main != null) {
- checkCanonicalName(main);
- }
- writeLibraries(component);
- writeUriToSource(component.uriToSource);
- writeLinkTable(component);
- _writeMetadataSection(component);
- writeStringTable(stringIndexer);
- writeConstantTable(_constantIndexer);
- writeComponentIndex(component, component.libraries);
-
- _flush();
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
这其中写入了很多部分,我们就不一一分析了,这里我们主要看之前编译完成后存到BytecodeMetadata
里的asm.bytecode
的数据是如何在这里被写入到文件的。这个逻辑,主要在_writeNodeMetadataImpl
中。
- void _writeNodeMetadataImpl(Node node, int nodeOffset) {
- for (var subsection in _metadataSubsections) {
- final repository = subsection.repository;
- final value = repository.mapping[node];
- if (value == null) {
- continue;
- }
-
- if (!MetadataRepository.isSupported(node)) {
- throw "Nodes of type ${node.runtimeType} can't have metadata.";
- }
-
- if (!identical(_sink, _mainSink)) {
- throw "Node written into metadata can't have metadata "
- "(metadata: ${repository.tag}, node: ${node.runtimeType} $node)";
- }
-
- _sink = _metadataSink;
- subsection.metadataMapping.add(nodeOffset);
- subsection.metadataMapping.add(getBufferOffset());
- repository.writeToBinary(value, node, this);
- _sink = _mainSink;
- }
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
repository.writeToBinary
对应的是BytecodeMetadataRepository.writeToBinary
- void writeToBinary(BytecodeMetadata metadata, Node node, BinarySink sink) {
- sink.writeUInt30(metadata.version);
- sink.writeUInt30(metadata.flags);
- metadata.constantPool.writeToBinary(node, sink);
- sink.writeByteList(metadata.bytecodes); // 这里bytecodes被写进了文件中
- if (metadata.hasExceptionsTable) {
- metadata.exceptionsTable.writeToBinary(sink);
- }
- if (metadata.hasNullableFields) {
- sink.writeUInt30(metadata.nullableFields.length);
- metadata.nullableFields.forEach((ref) => sink
- .writeCanonicalNameReference(getCanonicalNameOfMember(ref.asField)));
- }
- if (metadata.hasClosures) {
- sink.writeUInt30(metadata.closures.length);
- metadata.closures.forEach((c) => c.writeToBinary(sink));
- }
- }
-
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
代码还是比较清晰的,就是写入了metadata的各个数据,sink.writeByteList(metadata.bytecodes);
完成了bytecodes
的写入,此外包括version
、flags
等等信息也一同写入了文件。sink
在这里指的是kernel二进制文件写入者,是一个BinaryPrinter
类,绑定一个具体文件,也就是app.dill
。
至此,kernel文件的编译和生成流程就走完了。
现在,就可以根据上一步得到的app.dill
来生成AOT可执行文件了。回顾一下gradle脚本中调用命令行生成AOT的代码:
- final int snapshotExitCode = await snapshotter.build(
- platform: platform,
- buildMode: buildMode,
- mainPath: mainPath,
- packagesPath: PackageMap.globalPackagesPath,
- outputPath: outputPath,
- buildSharedLibrary: argResults['build-shared-library'],
- extraGenSnapshotOptions: argResults[FlutterOptions.kExtraGenSnapshotOptions],
- );
实际对应的命令是
- flutter/bin/cache/artifacts/engine/android-arm-release/darwin-x64/gen_snapshot
- --causal_async_stacks
- --packages=.packages
- --deterministic
- --snapshot_kind=app-aot-blobs
- --vm_snapshot_data=/path-to-project/flutter_hello/build/app/intermediates/flutter/release/vm_snapshot_data
- --isolate_snapshot_data=/path-to-project/flutter_hello/build/app/intermediates/flutter/release/isolate_snapshot_data
- --vm_snapshot_instructions=/path-to-project/flutter_hello/build/app/intermediates/flutter/release/vm_snapshot_instr
- --isolate_snapshot_instructions=/path-to-project/flutter_hello/build/app/intermediates/flutter/release/isolate_snapshot_instr
- --no-sim-use-hardfp
- --no-use-integer-division /path-to-project/flutter_hello/build/app/intermediates/flutter/release/app.dill
到这里,gen_snapshot不再是之前常见的Dart命令,而是一个货真价实的Native二进制可执行文件。其对应的是Dart虚拟机中的C++代码:dart/runtime/bin/gen_snapshot.cc
,入口在main函数中:
- int main(int argc, char** argv) {
- ... ...
-
- error = Dart_Initialize(&init_params);
-
- ... ...
-
- return GenerateSnapshotFromKernel(kernel_buffer, kernel_buffer_size);
-
- ... ...
- }
这里主要做了两件事。首先,根据传入的参数,初始化出一个Dart运行环境,主要是加载kernel文件,把所有Dart类都加载到运行环境中。接着,会根据已有的运行环境,直接编译生成二进制可执行文件snapshot。
我们重点来看后面GenerateSnapshotFromKernel
这步。
gen_snapshot中定义了许多snapshot的种类:
- static const char* kSnapshotKindNames[] = {
- "core",
- "core-jit",
- "core-jit-all",
- "app-jit",
- "app-aot-blobs",
- "app-aot-assembly",
- "vm-aot-assembly", NULL,
- };
对应枚举类型分别为:
- // Global state that indicates whether a snapshot is to be created and
- // if so which file to write the snapshot into. The ordering of this list must
- // match kSnapshotKindNames below.
- enum SnapshotKind {
- kCore,
- kCoreJIT,
- kCoreJITAll,
- kAppJIT,
- kAppAOTBlobs,
- kAppAOTAssembly,
- kVMAOTAssembly,
- };
而我们的命令行参数传入的是--snapshot_kind=app-aot-blobs
,因此这里只需要看kAppAOTBlobs
类型的就可以了。
- static int GenerateSnapshotFromKernel(const uint8_t* kernel_buffer,
- intptr_t kernel_buffer_size) {
- switch (snapshot_kind) {
- ... ...
- case kAppAOTBlobs:
- case kAppAOTAssembly: {
- if (Dart_IsNull(Dart_RootLibrary())) {
- Log::PrintErr(
- "Unable to load root library from the input dill file.\n");
- return kErrorExitCode;
- }
-
- CreateAndWritePrecompiledSnapshot();
-
- CreateAndWriteDependenciesFile();
-
- break;
- }
- ... ...
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
主要逻辑在于CreateAndWritePrecompiledSnapshot中
- static void CreateAndWritePrecompiledSnapshot() {
- ... ...
-
- result = Dart_Precompile();
-
- ... ...
-
- result = Dart_CreateAppAOTSnapshotAsBlobs(
- &vm_snapshot_data_buffer, &vm_snapshot_data_size,
- &vm_snapshot_instructions_buffer, &vm_snapshot_instructions_size,
- &isolate_snapshot_data_buffer, &isolate_snapshot_data_size,
- &isolate_snapshot_instructions_buffer,
- &isolate_snapshot_instructions_size, shared_data, shared_instructions);
-
- ... ...
-
- WriteFile(vm_snapshot_data_filename, vm_snapshot_data_buffer,
- vm_snapshot_data_size);
- WriteFile(vm_snapshot_instructions_filename,
- vm_snapshot_instructions_buffer, vm_snapshot_instructions_size);
- WriteFile(isolate_snapshot_data_filename, isolate_snapshot_data_buffer,
- isolate_snapshot_data_size);
- WriteFile(isolate_snapshot_instructions_filename,
- isolate_snapshot_instructions_buffer,
- isolate_snapshot_instructions_size);
- ... ...
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
一共分为三步。
重点在于第一步,Dart_Precompile
调用的是Precompiler::CompileAll()
来实现编译的,具体细节比较复杂,大概说来,它会先根据前面Dart_Initialize
得到的Dart运行环境的数据生成FlowGraph
对象,再进行各种执行流图的优化,最后把优化后的FlowGraph
对象翻译为具体架构(arm/arm64/x86等)的二进制指令。
而后面两步就是把内存中的二进制数据最终落地到文件中,也就是isolate_snapshot_data、isolate_snapshot_instr、vm_snapshot_data、vm_snapshot_instr这四个文件。
至此,flutter build aot
执行完毕,Dart代码完全编译成了二进制可执行文件。
回顾一下这个命令:
flutter build bundle --suppress-analytics --target lib/main.dart --target-platform android-arm --precompiled --asset-dir /path-to-project/flutter_hello/build/app/intermediates/flutter/release/flutter_assets --release
之前在分析flutter build apk的时候有提到,flutter build会通过flutter命令行脚本转化为启动一个Dart虚拟机并执行flutter_tool.snapshot,因此上述命令转化为:
- flutter/bin/cache/dart-sdk/bin/dart
- FLUTTER_TOOL_ARGS=
- SNAPSHOT_PATH=flutter/bin/cache/flutter_tools.snapshot
- build bundle
- --suppress-analytics
- --target lib/main.dart
- --target-platform android-arm
- --precompiled
- --asset-dir /path-to-project/flutter_hello/build/app/intermediates/flutter/release/flutter_assets
- --release
build bundle
对应BuildBundleCommand
,由于是relase模式,参数中会带上--precompiled
,因此不会在这里编译kernel文件了。其最终执行的是以下代码:
- Future<void> writeBundle(
- Directory bundleDir, Map<String, DevFSContent> assetEntries) async {
- if (bundleDir.existsSync())
- bundleDir.deleteSync(recursive: true);
- bundleDir.createSync(recursive: true);
-
- await Future.wait<void>(
- assetEntries.entries.map<Future<void>>((MapEntry<String, DevFSContent> entry) async {
- final File file = fs.file(fs.path.join(bundleDir.path, entry.key));
- file.parent.createSync(recursive: true);
- await file.writeAsBytes(await entry.value.contentsAsBytes());
- }));
- }
实际上只是把一些文件放进了build/app/intermediates/flutter/release/flutter_assets
目录下,这些文件分别是:
- packages/cupertino_icons/assets/CupertinoIcons.ttf
- fonts/MaterialIcons-Regular.ttf
- AssetManifest.json
- FontManifest.json
- LICENSE
因此,当build bundle
执行完毕后,所有flutter所需要的文件都已经放入flutter_assets
中了。
我们前面在讲flutter.gradle
的时候提到。build/app/intermediates/flutter/release/flutter_assets
里的东西会被全部复制到build/app/intermediates/merged_assets/debug/mergeXXXAssets/out
下。这样,这些flutter文件会在最后,一起跟着Android的标准taskmergeXXXAssets
打入到APK中。
到这里,flutter编译release包的完整流程就全部分析完了。我们以一张图再来归纳一下整个编译流程:
而如果是执行的编译debug包的操作flutter build apk --debug
,它的流程是这样的:
当然,其中有些命令行的具体参数是有所不同的。总体而言,debug模式下没有了build aot
这一步,而编译kernel文件这一步,也由release版本下的build aot
中转移到了build bundle
中。
本文完整讲解了Android环境下Flutter编译apk的流程,但这其中还有很多细节没有完全展开,包括Dart的pub机制、抽象语法树的构建、机器码编译等等,如果每一点都要分析清楚也都是长篇大论。
可以说,flutter作为一门新技术,有太多值得去品味与探索的实现细节,并且在阅读代码的过程中我们也发现,一些代码实现目前也没有十分稳定,官方也在不断优化中。我们通过对其深层原理的学习,不仅可以学以致用,实现特定需求的改进,还可以共同改进与推进这门新技术,使得移动开发技术领域的环境更加多样和完善。
作者:千山0xA2DB01D
链接:https://www.jianshu.com/p/4e8ccb02e92d
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。