初入Flutter的開發者,首先需要了解的便是如何編譯運作flutter應用。與通常Android工程項目的編譯不同,Flutter的打包編譯是通過調用flutter指令行來實作的。
在一遍遍編譯運作的過程中,你可能經常會思考:在每一條flutter指令的背後究竟做了哪些事?Flutter的編譯是如何與傳統Android gradle編譯流程串聯起來的?Dart代碼如何編譯成可執行的代碼?
我們這就來揭示其背後的奧秘。
flutter build apk
通常,對于一個标準的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" "$@"
其中,各個參數的具體含義如下:
- $DART:Dart可執行檔案,用于啟動一個Dart虛拟機。
- $FLUTTER_TOOL_ARGS:用于Google自己調試Flutter SDK用,一般情況下是空的。
- $SNAPSHOT_PATH:指定一個用于運作的snapshot檔案,這裡是
。snapshot的含義相當于java中的jar檔案。flutter/bin/cache/flutter_tools.snapshot
- $@:會透傳使用者傳入的參數,這裡就是
。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的執行環境。
flutter_tool
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);
}
這裡的
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;
}
這裡定義的一系列子指令,正是對應了我們運作
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 {
... ...
它主要調用了
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();
}
這個方法主要做了三件事:
- 首先,
會做一些驗證,主要是下載下傳pubGet
裡配置的依賴。實際上是通過Dart中的pubspec.yaml
指令來完成的,完整指令行如下:pub
flutter/bin/cache/dart-sdk/bin/pub --verbosity=warning get --no-precompile
- 接着
與ensureReadyForPlatformSpecificTooling
會依據對應的平台設定好相應的編譯環境,這裡設定好Android的gradle環境,并且加上一些額外的gradle屬性。setupApplicationPackages
- 最後,調用子類真正的
的方法。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,
);
}
這裡要求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
入口來轉換成各個平台實際所需的編譯指令。
flutter.gradle
既然已經走到了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"
這裡主要是取得
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)
}
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"
}
}
}
}
具體來說,這些産物就是
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
}
這裡傳入了
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}"
}
}
... ...
}
這裡,由于是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"
}
}
}
也就是執行了
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
回顧一下這個指令:
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');
}
}
... ...
}
這裡面其實又再次調用了兩個Dart虛拟機指令,主要做了兩件事:
- 生成kernel檔案
- 生成AOT可執行檔案
這兩個産物都是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/enginefrontend_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;
}
這裡主要執行的有三步:
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.
注釋已經說得比較清楚了。它會根據傳入的包含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;
... ...
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;
由于
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);
}
}
}
}
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);
}
可以看到最後一行執行了混淆的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);
}
}
這裡主要就是調用了
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);
}
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);
}
}
這個方法會繼續調用
_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}) {
... ...
從名字很容易看出,他們的作用,正是用來生成代碼結構的各個元素,包括指派語句、傳回語句、判斷語句等等。
_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,
}
文法樹的各個部分将被翻譯為上面的不同指令,最終,整個文法樹完整解析為二進制指令流,并存放于
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
}
... ...
至此,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>();
... ...
}
而在用
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);
}
}
這裡注意到,所有分支最終都會調用
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);
... ...
}
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();
}
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();
}
這其中寫入了很多部分,我們就不一一分析了,這裡我們主要看之前編譯完成後存到
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;
}
}
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));
}
}
代碼還是比較清晰的,就是寫入了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;
}
... ...
主要邏輯在于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);
... ...
}
一共分為三步。
- Dart_Precompile進行AOT編譯
- 把snapshot代碼轉移到buffer中
- 寫buffer到四個二進制檔案
重點在于第一步,
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
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/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的标準task
mergeXXXAssets
打入到APK中。
總結
到這裡,flutter編譯release包的完整流程就全部分析完了。我們以一張圖再來歸納一下整個編譯流程:
而如果是執行的編譯debug包的操作
flutter build apk --debug
,它的流程是這樣的:
當然,其中有些指令行的具體參數是有所不同的。總體而言,debug模式下沒有了
build aot
這一步,而編譯kernel檔案這一步,也由release版本下的
build aot
中轉移到了
build bundle
本文完整講解了Android環境下Flutter編譯apk的流程,但這其中還有很多細節沒有完全展開,包括Dart的pub機制、抽象文法樹的建構、機器碼編譯等等,如果每一點都要分析清楚也都是長篇大論。
可以說,flutter作為一門新技術,有太多值得去品味與探索的實作細節,并且在閱讀代碼的過程中我們也發現,一些代碼實作目前也沒有十分穩定,官方也在不斷優化中。我們通過對其深層原理的學習,不僅可以學以緻用,實作特定需求的改進,還可以共同改進與推進這門新技術,使得移動開發技術領域的環境更加多樣和完善。