初入Flutter的開發(fā)者,首先需要了解的便是如何編譯運行flutter應用。與通常Android工程項目的編譯不同,F(xiàn)lutter的打包編譯是通過調用flutter命令行來實現(xiàn)的。
在一遍遍編譯運行的過程中,你可能經常會思考:在每一條flutter命令的背后究竟做了哪些事?Flutter的編譯是如何與傳統(tǒng)Android gradle編譯流程串聯(lián)起來的?Dart代碼如何編譯成可執(zhí)行的代碼?
我們這就來揭示其背后的奧秘。
flutter build apk
通常,對于一個標準的Flutter工程,只要執(zhí)行以下命令就可以完成打包,
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" "$@"
其中,各個參數(shù)的具體含義如下:
- $DART:Dart可執(zhí)行文件,用于啟動一個Dart虛擬機。
- $FLUTTER_TOOL_ARGS:用于Google自己調試Flutter SDK用,一般情況下是空的。
-
$SNAPSHOT_PATH:指定一個用于運行的snapshot文件,這里是
flutter/bin/cache/flutter_tools.snapshot。snapshot的含義相當于java中的jar文件。 -
$@:會透傳用戶傳入的參數(shù),這里就是
build apk。
因此,這里實際上執(zhí)行的就是如下命令:
flutter/bin/cache/dart-sdk/bin/dart FLUTTER_TOOL_ARGS= SNAPSHOT_PATH=flutter/bin/cache/flutter_tools.snapshot build apk
可以看出,這個命令實際上和java執(zhí)行jar文件的方式如出一轍,只不過,這里編譯打包的邏輯,都是用Dart語言實現(xiàn)在flutter_tools.snapshot中的,因此采用了Dart的執(zhí)行環(huán)境。
flutter_tool
Dart的snapshot相當于Java的jar,因此,一個snapshot在執(zhí)行的時候,必然有它的執(zhí)行入口,也就是類似main函數(shù)的東西。
這里的入口,正是flutter/packages/flutter_tools/bin/flutter_tools.dart
可以看到它的main函數(shù)如下:
void main(List<String> args) {
executable.main(args);
}
它調用了flutter/packages/flutter_tools/lib/executable.dart的main函數(shù),我們繼續(xù)往下看:
/// 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的一個命令行運行解析的通用類,在執(zhí)行runner.run的時候傳入了一系列的XXXCommand方法,我們只需要知道flutter build對應的命令都會匹配到BuildCommand()方法就可以了。
再來看BuildCommand的實現(xiàn):
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的時候,執(zhí)行的是這個父類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會做一些驗證,主要是下載pubspec.yaml里配置的依賴。實際上是通過Dart中的pub指令來完成的,完整命令行如下:flutter/bin/cache/dart-sdk/bin/pub --verbosity=warning get --no-precompile接著
ensureReadyForPlatformSpecificTooling與setupApplicationPackages會依據(jù)對應的平臺設定好相應的編譯環(huán)境,這里設定好Android的gradle環(huán)境,并且加上一些額外的gradle屬性。最后,調用子類真正的
runCommand的方法。
由于這里執(zhí)行的是build apk,所以子類是BuildApkCommand,因此繼續(xù)看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主要做的是加入一些必要的參數(shù),執(zhí)行gradle命令,其最終執(zhí)行的完整命令行如下:
flutter_hello/android/gradlew -q -Ptarget=lib/main.dart -Ptrack-widget-creation=false -Ptarget-platform=android-arm assembleRelease
由此,又回到了我們熟悉的Android世界,它只是在我們熟悉的gradlew assembleRelease中增加了一些額外參數(shù)。flutter_hello是我們的flutter示例工程。
這里為什么需要如此大費周折地繞個圈子來執(zhí)行gradle命令呢?自然是因為Flutter本身的定位,它是一個跨平臺方案,因此對于各個平臺都有其特定的實現(xiàn),因此才需要以一個統(tǒng)一的flutter build入口來轉換成各個平臺實際所需的編譯命令。
flutter.gradle
既然已經走到了gradlew,理所當然地,我們直接看他的build.gradle就可以了。他的內容是由Flutter SDK生成的,相比于普通的Android工程,是有些許不同。不過我們只需要根據(jù)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里面主要是實現(xiàn)了一個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,這就會使得快執(zhí)行到processXXXResources的時候必須先執(zhí)行完copyFlutterAssetsTask才能繼續(xù)。就這樣,flutter的相關處理就嵌入到gradle的編譯流程中了。
另外,copyFlutterAssetsTask依賴了flutterTask和mergeXXXAssets。也就是說,當flutterTask使得flutter編譯完成,并且mergeXXXAssets執(zhí)行完畢,也就是正常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產物為常規(guī)的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所需的各種參數(shù),而具體的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_***產物,實際是執(zhí)行以下命令:
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"
}
}
}
也就是執(zhí)行了
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這兩個命令的具體實現(xiàn)。
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虛擬機并執(zhí)行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可執(zhí)行文件
這兩個產物都是Dart代碼生成的程序文件。
kernel文件格式是由Dart定義的一種特殊數(shù)據(jù)格式,Dart虛擬機,會以解釋方式來執(zhí)行它。我們之前提到的flutter_tool.snapshot等snapshot文件,實際上都是kernel文件。
而AOT可執(zhí)行文件,需要等kernel文件生成完畢后,在編譯期間根據(jù)kernel來生成的。它是二進制的,機器平臺(arm、arm64、x86等)可執(zhí)行的代碼,也因此它被稱為AOT(Ahead of time compiling)。在運行的時候它的指令碼已經是平臺的機器碼了,因此不再需要Dart虛擬機解析,執(zhí)行速度更快。release模式下,打包進APK的Dart源碼都是以AOT文件的形式存在的。
生成kernel文件
我們先來分析第一步,也就是Compile to kernel這一步。它會根據(jù)Dart代碼文件生成kernel文件,具體是執(zhí)行了以下命令:
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。經過一系列跳轉后,最終執(zhí)行的是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;
}
這里主要執(zhí)行的有三步: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.
注釋已經說得比較清楚了。它會根據(jù)傳入的包含main函數(shù)的Dart代碼文件路徑,并根據(jù)其中的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,后續(xù)所有對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源碼,根據(jù)詞法規(guī)則解析成一個個的詞法單元tokens。
而parseUnit就是把前面tokenize得到的tokens根據(jù)Dart語法規(guī)則進一步解析成為抽象語法樹。
由此,Dart源碼已經被轉化成了一顆抽象語法樹并存儲于Component中了。
_runGlobalTransformations
而第二步的_runGlobalTransformations主要是執(zhí)行了一系列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);
}
可以看到最后一行執(zhí)行了混淆的transform。這里的混淆主要是做一些mapping,其實就類似于proguard做的一些事,不過目前看來,這方面的支持還比較初級,一些自定義規(guī)則之類的功能應該還不如proguard那么完善。
runWithFrontEndCompilerContext
runWithFrontEndCompilerContext主要傳入一個回調方法:
await runWithFrontEndCompilerContext(source, options, component, () {
generateBytecode(component,
dropAST: dropAST,
useFutureBytecodeFormat: useFutureBytecodeFormat,
environmentDefines: environmentDefines);
});
也就是這里的generateBytecode,它需要拿到之前kernelForProgram所生成的Component對象,并根據(jù)其中的抽象語法樹來生成具體的kernel字節(jié)碼。
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系列方法會遞歸地訪問其成員節(jié)點,由此完整遍歷并處理好整顆語法樹。
一個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);
}
}
這個方法會繼續(xù)調用_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方法還不是最終執(zhí)行者,真正的幕后英雄,是asm.emitXXXX系列函數(shù)。
就以bool賦值語句_genPushBool為例:
void _genPushBool(bool value) {
if (value) {
asm.emitPushTrue();
} else {
asm.emitPushFalse();
}
}
它分別根據(jù)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的字節(jié)碼值,emitPushTrue會將其轉為一個int大小的字節(jié)碼,并寫入到bytecode之中
Dart定義了一系列的字節(jié)碼指令集,
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,它是一個全局函數(shù),在它其中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函數(shù)中,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的實現(xiàn)如下:
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就是前面通過命令行參數(shù)傳入的/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的數(shù)據(jù)是如何在這里被寫入到文件的。這個邏輯,主要在_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的各個數(shù)據(jù),sink.writeByteList(metadata.bytecodes);完成了bytecodes的寫入,此外包括version、flags等等信息也一同寫入了文件。sink在這里指的是kernel二進制文件寫入者,是一個BinaryPrinter類,綁定一個具體文件,也就是app.dill。
至此,kernel文件的編譯和生成流程就走完了。
生成AOT可執(zhí)行文件
現(xiàn)在,就可以根據(jù)上一步得到的app.dill來生成AOT可執(zhí)行文件了。回顧一下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二進制可執(zhí)行文件。其對應的是Dart虛擬機中的C++代碼:dart/runtime/bin/gen_snapshot.cc,入口在main函數(shù)中:
int main(int argc, char** argv) {
... ...
error = Dart_Initialize(&init_params);
... ...
return GenerateSnapshotFromKernel(kernel_buffer, kernel_buffer_size);
... ...
}
這里主要做了兩件事。首先,根據(jù)傳入的參數(shù),初始化出一個Dart運行環(huán)境,主要是加載kernel文件,把所有Dart類都加載到運行環(huán)境中。接著,會根據(jù)已有的運行環(huán)境,直接編譯生成二進制可執(zhí)行文件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,
};
而我們的命令行參數(shù)傳入的是--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()來實現(xiàn)編譯的,具體細節(jié)比較復雜,大概說來,它會先根據(jù)前面Dart_Initialize得到的Dart運行環(huán)境的數(shù)據(jù)生成FlowGraph對象,再進行各種執(zhí)行流圖的優(yōu)化,最后把優(yōu)化后的FlowGraph對象翻譯為具體架構(arm/arm64/x86等)的二進制指令。
而后面兩步就是把內存中的二進制數(shù)據(jù)最終落地到文件中,也就是isolate_snapshot_data、isolate_snapshot_instr、vm_snapshot_data、vm_snapshot_instr這四個文件。
至此,flutter build aot執(zhí)行完畢,Dart代碼完全編譯成了二進制可執(zhí)行文件。
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 build apk的時候有提到,flutter build會通過flutter命令行腳本轉化為啟動一個Dart虛擬機并執(zhí)行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模式,參數(shù)中會帶上--precompiled,因此不會在這里編譯kernel文件了。其最終執(zhí)行的是以下代碼:
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執(zhí)行完畢后,所有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包的完整流程就全部分析完了。我們以一張圖再來歸納一下整個編譯流程:

而如果是執(zhí)行的編譯debug包的操作flutter build apk --debug,它的流程是這樣的:

當然,其中有些命令行的具體參數(shù)是有所不同的??傮w而言,debug模式下沒有了build aot這一步,而編譯kernel文件這一步,也由release版本下的build aot中轉移到了build bundle中。
本文完整講解了Android環(huán)境下Flutter編譯apk的流程,但這其中還有很多細節(jié)沒有完全展開,包括Dart的pub機制、抽象語法樹的構建、機器碼編譯等等,如果每一點都要分析清楚也都是長篇大論。
可以說,flutter作為一門新技術,有太多值得去品味與探索的實現(xiàn)細節(jié),并且在閱讀代碼的過程中我們也發(fā)現(xiàn),一些代碼實現(xiàn)目前也沒有十分穩(wěn)定,官方也在不斷優(yōu)化中。我們通過對其深層原理的學習,不僅可以學以致用,實現(xiàn)特定需求的改進,還可以共同改進與推進這門新技術,使得移動開發(fā)技術領域的環(huán)境更加多樣和完善。
其他Android深度技術文章,可關注本公眾號
