天天看点

Flutter编译打包系统完全解读

初入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文件,这里是

    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的执行环境。

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

    会做一些验证,主要是下载

    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,
  );
}           

这里要求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/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;
}           

这里主要执行的有三步:

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作为一门新技术,有太多值得去品味与探索的实现细节,并且在阅读代码的过程中我们也发现,一些代码实现目前也没有十分稳定,官方也在不断优化中。我们通过对其深层原理的学习,不仅可以学以致用,实现特定需求的改进,还可以共同改进与推进这门新技术,使得移动开发技术领域的环境更加多样和完善。

其他Android深度技术文章,可关注本公众号