美文网首页Flutter中文社区FlutterFlutter圈子
研读Flutter——打包编译流程详解

研读Flutter——打包编译流程详解

作者: 千山0xA2DB01D | 来源:发表于2019-02-19 19:45 被阅读41次

    初入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.dartmain函数,我们继续往下看:

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

    这里的runnerflutter的一个命令行运行解析的通用类,在执行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的时候,执行的是这个父类FlutterCommandrun方法:

      /// 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

    • 接着ensureReadyForPlatformSpecificToolingsetupApplicationPackages会依据对应的平台设定好相应的编译环境,这里设定好Android的gradle环境,并且加上一些额外的gradle属性。

    • 最后,调用子类真正的runCommand的方法。

    由于这里执行的是build apk,所以子类是BuildApkCommand,因此继续看BuildApkCommandrunCommand

    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.sdkflutter.versionCodeflutter.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依赖了flutterTaskmergeXXXAssets。也就是说,当flutterTask使得flutter编译完成,并且mergeXXXAssets执行完毕,也就是正常Android的assets处理完成后,flutter相应的产物就会被copyFlutterAssetsTask复制到build/app/intermediates/merged_assets/debug/mergeXXXAssets/out目录下。这里的XXX指代各种build variant,也就是Debug或者Release

    flutter的编译产物,具体是由flutterTaskgetAssets方法指定的:

    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 aotflutter 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文件的形式存在的。

    生成kernel文件

    我们先来分析第一步,也就是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_runGlobalTransformationsrunWithFrontEndCompilerContext

    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都各自包含了自己包下面的所有ClassFieldProcedure等组成部分。

    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对象的时候,就会顺势访问到其中所有的LibraryBytecodeGenerator这个类比较繁琐,他对于语法树中各个语法类型的情况分别有不同的处理,我们来大概浏览一下:

    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中,包含了ConstructorProcedureField等几个基本的组成单元,在以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,
    }
    

    语法树的各个部分将被翻译为上面的不同指令,最终,整个语法树完整解析为二进制指令流,并存放于BytecodeAssemblerbytecode成员中。

    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方法来解析语法树,并且生成二进制指令流到BytecodeAssemblerbytecode成员中,这个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的写入,此外包括versionflags等等信息也一同写入了文件。sink在这里指的是kernel二进制文件写入者,是一个BinaryPrinter类,绑定一个具体文件,也就是app.dill

    至此,kernel文件的编译和生成流程就走完了。

    生成AOT可执行文件

    现在,就可以根据上一步得到的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 build apk的时候有提到,flutter build会通过flutter命令行脚本转化为启动一个Dart虚拟机并执行flutter_tool.snapshot,因此上述命令转化为:

    flutter/bin/cache/dart-sdk/bin/dart 
    FLUTTER_TOOL_ARGS= 
    SNAPSHOT_PATH=flutter/bin/cache/flutter_tools.snapshot
    build bundle 
    --suppress-analytics 
    --target lib/main.dart 
    --target-platform android-arm 
    --precompiled 
    --asset-dir /path-to-project/flutter_hello/build/app/intermediates/flutter/release/flutter_assets 
    --release
    

    build bundle对应BuildBundleCommand,由于是relase模式,参数中会带上--precompiled,因此不会在这里编译kernel文件了。其最终执行的是以下代码:

    Future<void> writeBundle(
        Directory bundleDir, Map<String, DevFSContent> assetEntries) async {
      if (bundleDir.existsSync())
        bundleDir.deleteSync(recursive: true);
      bundleDir.createSync(recursive: true);
    
      await Future.wait<void>(
          assetEntries.entries.map<Future<void>>((MapEntry<String, DevFSContent> entry) async {
        final File file = fs.file(fs.path.join(bundleDir.path, entry.key));
        file.parent.createSync(recursive: true);
        await file.writeAsBytes(await entry.value.contentsAsBytes());
      }));
    }
    

    实际上只是把一些文件放进了build/app/intermediates/flutter/release/flutter_assets目录下,这些文件分别是:

    packages/cupertino_icons/assets/CupertinoIcons.ttf
    fonts/MaterialIcons-Regular.ttf
    AssetManifest.json
    FontManifest.json
    LICENSE
    

    因此,当build bundle执行完毕后,所有flutter所需要的文件都已经放入flutter_assets中了。

    我们前面在讲flutter.gradle的时候提到。build/app/intermediates/flutter/release/flutter_assets里的东西会被全部复制到build/app/intermediates/merged_assets/debug/mergeXXXAssets/out下。这样,这些flutter文件会在最后,一起跟着Android的标准taskmergeXXXAssets打入到APK中。

    总结

    到这里,flutter编译release包的完整流程就全部分析完了。我们以一张图再来归纳一下整个编译流程:

    而如果是执行的编译debug包的操作flutter build apk --debug,它的流程是这样的:

    当然,其中有些命令行的具体参数是有所不同的。总体而言,debug模式下没有了build aot这一步,而编译kernel文件这一步,也由release版本下的build aot中转移到了build bundle中。

    本文完整讲解了Android环境下Flutter编译apk的流程,但这其中还有很多细节没有完全展开,包括Dart的pub机制、抽象语法树的构建、机器码编译等等,如果每一点都要分析清楚也都是长篇大论。

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

    相关文章

      网友评论

        本文标题:研读Flutter——打包编译流程详解

        本文链接:https://www.haomeiwen.com/subject/blafyqtx.html