Flutter 命令本质之 Flutter tools 机制源码深刻分析

背景

上一篇《Flutter Android 工程结构及应用层编译源码深刻分析》咱们分析了 Flutter Android 相关的应用层主要编译流程,其中分析到底层本质命令工具【Flutter SDK 下bin/flutter编译命令分析】小节时只提到,咱们执行任何 flutter 命令的本质都是把参数传递到了FLUTTER_SDK_DIR/packages/flutter_tools/bin/flutter_tools.dart源码的 main 方法中,没有对这里面进行深刻分析。本文要作的事就是层层递进揭开这里的本质,并与上篇呼应解释编译产物的由来。java

flutter_tools 介绍

经过flutter -h命令咱们能够直观全局感觉都支持哪些参数,有些参数还有子参数。咱们所执行的全部参数本质都走进了下面模块的源码入口中。 在这里插入图片描述 所以咱们若是直接想从源码方式使用 flutter tools,则能够直接当前目录中以下命令:android

# ARGS 就是一堆参数,譬如咱们上篇的 build apk
dart bin/flutter_tools.dart ARGS
复制代码

若是想从新生成 Flutter Tools snapshot,能够直接当前目录中执行以下命令:shell

rm ../../bin/cache/flutter_tools.stamp ../../bin/cache/flutter_tools.snapshot
复制代码

这样就成功删除了上篇中 shell 脚本调用的 Flutter Tools snapshot,而后在执行时会自动从新生成一个。缓存

源码分析

上面既然交代了整个背景,那么咱们接下来就基于 Flutter SDK 入口packages/flutter_tools/bin/flutter_tools.dart开始分析,整个分析继续承接上篇flutter build apk命令,以下:bash

//一、导入packages/flutter_tools/lib/executable.dart文件
import 'package:flutter_tools/executable.dart' as executable;
//二、入口重点,执行executable.main方法,并将咱们`build apk`参数传入
void main(List<String> args) {
  executable.main(args);
}
复制代码

接下来咱们去packages/flutter_tools/lib/executable.dart看看他的 main 方法,以下:markdown

Future<void> main(List<String> args) async {
  //一堆参数解析判断啥的,譬如解析flutter doctor的doctor参数啥的
  ......
  //一、重点!runner的本质是import 'runner.dart' as runner;
  // 本质就是调用run方法的各类参数传递,重点关注第一个和第二个参数便可
  await runner.run(
    args,
    () => generateCommands(
      verboseHelp: verboseHelp,
      verbose: verbose,
    ),
    ......,
  );
}
//二、步骤1中runner.run的第二个核心参数方法定义
//FlutterCommand为packages/flutter_tools/lib/src/runner/flutter_command.dart中定义的抽象类
//这个方法本质就是把flutter执行的命令参数列表所有加入列表,相似命令模式
List<FlutterCommand> generateCommands({
  @required bool verboseHelp,
  @required bool verbose,
}) => <FlutterCommand>[
  AnalyzeCommand(
    ......
  ),
  AssembleCommand(verboseHelp: verboseHelp, buildSystem: globals.buildSystem),
  AttachCommand(verboseHelp: verboseHelp),
  BuildCommand(verboseHelp: verboseHelp),
  ChannelCommand(verboseHelp: verboseHelp),
  CleanCommand(verbose: verbose),
  ConfigCommand(verboseHelp: verboseHelp),
  CreateCommand(verboseHelp: verboseHelp),
  DaemonCommand(hidden: !verboseHelp),
  DevicesCommand(verboseHelp: verboseHelp),
  DoctorCommand(verbose: verbose),
  DowngradeCommand(verboseHelp: verboseHelp),
  DriveCommand(verboseHelp: verboseHelp,
    ......
  ),
  EmulatorsCommand(),
  FormatCommand(),
  GenerateCommand(),
  GenerateLocalizationsCommand(
    ......
  ),
  InstallCommand(),
  LogsCommand(),
  MakeHostAppEditableCommand(),
  PackagesCommand(),
  PrecacheCommand(
    ......
  ),
  RunCommand(verboseHelp: verboseHelp),
  ScreenshotCommand(),
  ShellCompletionCommand(),
  TestCommand(verboseHelp: verboseHelp),
  UpgradeCommand(verboseHelp: verboseHelp),
  SymbolizeCommand(
    ......
  ),
  // Development-only commands. These are always hidden,
  IdeConfigCommand(),
  UpdatePackagesCommand(),
];
......
复制代码

让咱们把目光先移动到runner.dart文件的 run 方法,而后回过头来看上面代码中的步骤1如何调用步骤2,以下:app

Future<int> run(
  List<String> args,
  List<FlutterCommand> Function() commands, {
    bool muteCommandLogging = false,
    bool verbose = false,
    bool verboseHelp = false,
    bool reportCrashes,
    String flutterVersion,
    Map<Type, Generator> overrides,
  }) async {
  ......
  //一、FlutterCommandRunner位于packages/flutter_tools/lib/src/runner/flutter_command_runner.dart
  return runInContext<int>(() async {
    reportCrashes ??= !await globals.isRunningOnBot;
    //二、建立runner对象实例,并把上一片断代码中步骤2方法返回的FlutterCommand列表追加进runner中
    final FlutterCommandRunner runner = FlutterCommandRunner(verboseHelp: verboseHelp);
    commands().forEach(runner.addCommand);
    ......
    return runZoned<Future<int>>(() async {
      try {
      	//三、依据args参数执行runner实例的run方法
        await runner.run(args);
		......
      } catch (error, stackTrace) {  // ignore: avoid_catches_without_on_clauses
        ......
      }
    }, onError: (Object error, StackTrace stackTrace) async { // ignore: deprecated_member_use
      ......
    });
  }, overrides: overrides);
}
复制代码

能够看到,首先实例化了一个 FlutterCommandRunner 对象,接着把全部支持的 FlutterCommand 列表加入 runner 对象中,而后调用了 runner 的 run 方法,因此咱们如今查看packages/flutter_tools/lib/src/runner/flutter_command_runner.dart文件的 run 方法,以下:async

......
@override
Future<void> run(Iterable<String> args) {
  ......
  //本质调用了父类CommandRunner的run方法,run方法调用了子类FlutterCommandRunner的runCommand方法
  //子类FlutterCommandRunner的runCommand最终又调用了父类CommandRunner的runCommand方法
  return super.run(args);
}
......
复制代码

因此咱们接下来看父类 CommandRunner 的 runCommand 方法,以下:ide

Future<T?> runCommand(ArgResults topLevelResults) async {
  	//一、flutter命令后面传递进来参数,譬如build apk
    var argResults = topLevelResults;
    //二、前面分析过的,runner中添加的支持命令列表
    var commands = _commands;
    //三、定义一个Command变量,用来最终依据参数赋值为对应的Command对象实例
    Command? command;
    var commandString = executableName;
	//四、while条件为真,由于commands为支持的参数列表
    while (commands.isNotEmpty) {
      ......
      //五、填充指令
      argResults = argResults.command!;
      command = commands[argResults.name]!;
      command._globalResults = topLevelResults;
      command._argResults = argResults;
      commands = command._subcommands as Map<String, Command<T>>;
      commandString += ' ${argResults.name}';
	  ......
    }
    ......
    //六、执行对应命令的run方法
    return (await command.run()) as T?;
  }
  ......
}
复制代码

能够看到,这就是一个标准的命令模式设计,先把支持的命令添加到列表,而后依据参数遍历匹配对应命令进行执行。下面咱们以flutter build apk命令为例来看其对应的 BuildCommand 命令(packages/flutter_tools/lib/src/commands/build.dart)实现,以下:工具

class BuildCommand extends FlutterCommand {
  BuildCommand({ bool verboseHelp = false }) {
    addSubcommand(BuildAarCommand(verboseHelp: verboseHelp));
    addSubcommand(BuildApkCommand(verboseHelp: verboseHelp));
    addSubcommand(BuildAppBundleCommand(verboseHelp: verboseHelp));
    addSubcommand(BuildIOSCommand(verboseHelp: verboseHelp));
    addSubcommand(BuildIOSFrameworkCommand(
      buildSystem: globals.buildSystem,
      verboseHelp: verboseHelp,
    ));
    addSubcommand(BuildIOSArchiveCommand(verboseHelp: verboseHelp));
    addSubcommand(BuildBundleCommand(verboseHelp: verboseHelp));
    addSubcommand(BuildWebCommand(verboseHelp: verboseHelp));
    addSubcommand(BuildMacosCommand(verboseHelp: verboseHelp));
    addSubcommand(BuildLinuxCommand(
      operatingSystemUtils: globals.os,
      verboseHelp: verboseHelp
    ));
    addSubcommand(BuildWindowsCommand(verboseHelp: verboseHelp));
    addSubcommand(BuildWindowsUwpCommand(verboseHelp: verboseHelp));
    addSubcommand(BuildFuchsiaCommand(verboseHelp: verboseHelp));
  }
  //上一小段代码中command = commands[argResults.name]就是这么获得的
  //name=build就是执行flutter build apk中的build字符串
  @override
  final String name = 'build';

  @override
  final String description = 'Build an executable app or install bundle.';

  @override
  Future<FlutterCommandResult> runCommand() async => null;
}
复制代码

能够看到,任意一个命令基本都继承自 FlutterCommand 实现,命令的执行都是调用了 FlutterCommand 的 run 方法,以下:

abstract class FlutterCommand extends Command<void> {
  ......
  //runner对象中最终执行调用的方法是这个
  @override
  Future<void> run() {
    ......
    return context.run<void>(
      name: 'command',
      overrides: <Type, Generator>{FlutterCommand: () => this},
      body: () async {
        ......
        try {
          //见名知意,先校验再运行命令
          commandResult = await verifyThenRunCommand(commandPath);
        } finally {
          ......
        }
      },
    );
  }
  ......
    @mustCallSuper
  Future<FlutterCommandResult> verifyThenRunCommand(String commandPath) async {
    //一、若是须要更新缓存就先更新缓存
    if (shouldUpdateCache) {
      await globals.cache.updateAll(<DevelopmentArtifact>{DevelopmentArtifact.universal});
      await globals.cache.updateAll(await requiredArtifacts);
    }
    globals.cache.releaseLock();
	//二、校验命令
    await validateCommand();
	//三、若是须要先执行pub就先执行,譬如pub get下载依赖
    if (shouldRunPub) {
      ......
	  //四、执行pub get下载依赖,即下载pubspec.yaml里配置的依赖
      await pub.get(
        context: PubContext.getVerifyContext(name),
        generateSyntheticPackage: project.manifest.generateSyntheticPackage,
        checkUpToDate: cachePubGet,
      );
      await project.regeneratePlatformSpecificTooling();
      if (reportNullSafety) {
        await _sendNullSafetyAnalyticsEvents(project);
      }
    }

    setupApplicationPackages();
	......
	//五、真正开始执行命令
    return runCommand();
  }
}
复制代码

绕一圈最终咱们又回到 BuildCommand 类,能够发现其 runCommand 方法重写为空实现,而其构造时经过 addSubcommand 方法追加了不少子命令,譬如执行flutter build aar编译 aar 的 BuildAarCommand 命令、执行flutter build apk编译 apk 的 BuildApkCommand 命令。整个 sub command 与其宿主又算是一个责任链,因此上面一样的套路顺序对于 sub command 一样适用,所以咱们去看下编译 apk 产物的 BuildApkCommand 源码(packages/flutter_tools/lib/src/commands/build_apk.dart),以下:

class BuildApkCommand extends BuildSubCommand {
  BuildApkCommand({bool verboseHelp = false}) {
    ......
    //一堆参数的确认
  }
  //对应flutter build apk里面子命令字符串apk
  @override
  final String name = 'apk';
  ......
  //本质命令执行方法
  @override
  Future<FlutterCommandResult> runCommand() async {
    ......
    //调用androidBuilder的buildApk方法进行真正的编译,目测里面的产物也就是上一篇文章分析的那些
    //androidBuilder位于packages/flutter_tools/lib/src/android/android_builder.dart
    await androidBuilder.buildApk(
      project: FlutterProject.current(),
      target: targetFile,
      androidBuildInfo: androidBuildInfo,
    );
    return FlutterCommandResult.success();
  }
}
复制代码

顺着这条路咱们继续跟进位于packages/flutter_tools/lib/src/android/android_builder.dart的 androidBuilder 属性的 buildApk 方法,以下:

//本质是packages/flutter_tools/lib/src/context_runner.dart中context.run方法中的AndroidGradleBuilder实例
AndroidBuilder get androidBuilder {
  return context.get<AndroidBuilder>();
}
//抽象类定义,AndroidBuilder
abstract class AndroidBuilder {
  const AndroidBuilder();
  // 定义编译aar的方法
  Future<void> buildAar({
    @required FlutterProject project,
    @required Set<AndroidBuildInfo> androidBuildInfo,
    @required String target,
    @required String outputDirectoryPath,
    @required String buildNumber,
  });

  // 定义编译apk的方法
  Future<void> buildApk({
    @required FlutterProject project,
    @required AndroidBuildInfo androidBuildInfo,
    @required String target,
  });

  // 定义编译aab的方法
  Future<void> buildAab({
    @required FlutterProject project,
    @required AndroidBuildInfo androidBuildInfo,
    @required String target,
    bool validateDeferredComponents = true,
    bool deferredComponentsEnabled = false,
  });
}
复制代码

因此咱们继续去看 AndroidGradleBuilder 实现类(packages/flutter_tools/lib/src/android/gradle.dart)的 buildApk 方法,以下:

class AndroidGradleBuilder implements AndroidBuilder {
  AndroidGradleBuilder({
    ......
  }) : ......;
  ......
  //一、编译 apk 的方法
  @override
  Future<void> buildApk({
    @required FlutterProject project,
    @required AndroidBuildInfo androidBuildInfo,
    @required String target,
  }) async {
  	//二、调用
    await buildGradleApp(
      project: project,
      androidBuildInfo: androidBuildInfo,
      target: target,
      isBuildingBundle: false,
      localGradleErrors: gradleErrors,
    );
  }
  ......
  //三、真的编译
  Future<void> buildGradleApp({
    @required FlutterProject project, //FlutterProject.current()
    @required AndroidBuildInfo androidBuildInfo, //build configuration
    @required String target, //dart代码入口,缺省lib/main.dart
    @required bool isBuildingBundle, //是aab仍是apk,默认false则apk
    @required List<GradleHandledError> localGradleErrors,
    bool shouldBuildPluginAsAar = false, //是否是将插件编译为aar
    bool validateDeferredComponents = true,
    bool deferredComponentsEnabled = false,
    int retries = 1,
  }) async {
    //四、检查支持的android版本,获取android编译产物目录,即gradle中配置的build产物目录,默认为项目根目录下的build目录
    if (!project.android.isSupportedVersion) {
      _exitWithUnsupportedProjectMessage(_usage, _logger.terminal);
    }
    final Directory buildDirectory = project.android.buildDirectory;
	//五、读取安卓相关属性文件判断是否使用androidx,而后发送编译事件参数
    final bool usesAndroidX = isAppUsingAndroidX(project.android.hostAppGradleRoot);
    if (usesAndroidX) {
      BuildEvent('app-using-android-x', flutterUsage: _usage).send();
    } else if (!usesAndroidX) {
      BuildEvent('app-not-using-android-x', flutterUsage: _usage).send();
      ......
    }
    //六、更新安卓项目中local.properties中的versionName和versionCode值,值来自于public.yaml文件配置
    updateLocalProperties(project: project, buildInfo: androidBuildInfo.buildInfo);
	//七、编译aar的话就走buildPluginsAsAar方法进行
    if (shouldBuildPluginAsAar) {
      // Create a settings.gradle that doesn't import the plugins as subprojects.
      createSettingsAarGradle(project.android.hostAppGradleRoot, _logger);
      await buildPluginsAsAar(
        project,
        androidBuildInfo,
        buildDirectory: buildDirectory.childDirectory('app'),
      );
    }
	//八、获取编译apk或者aab对应的标准安卓task name,构建参数等信息,也就是gradle命令后面一堆的参数构造
    final BuildInfo buildInfo = androidBuildInfo.buildInfo;
    final String assembleTask = isBuildingBundle
        ? getBundleTaskFor(buildInfo)
        : getAssembleTaskFor(buildInfo);
	......
    final List<String> command = <String>[
      _gradleUtils.getExecutable(project),
    ];
    ......
    //九、依据条件追加command的一堆参数,譬如-Psplit-per-abi=true、-Pverbose=true、--no-daemon等
    ......
    try {
      exitCode = await _processUtils.stream(
        command,
        workingDirectory: project.android.hostAppGradleRoot.path,
        allowReentrantFlutter: true,
        environment: <String, String>{ if (javaPath != null) 'JAVA_HOME': javaPath, }, mapFunction: consumeLog, ); } on ProcessException catch (exception) { ...... } finally { status.stop(); } ...... } ...... } 复制代码

哈哈,真相了,这下配合《Flutter Android 工程结构及应用层编译源码深刻分析》一文首尾呼应后你应该完全明白 Flutter android apk 是怎么编译的流程!

总结

如今咱们结合《Flutter Android 工程结构及应用层编译源码深刻分析》和这篇进行关联总结,能够总结出执行flutter build apk命令背后的大体主流程以下: 在这里插入图片描述 既然执行flutter build apk命令你都搞明白了,那么其余 flutter 相关的任何命令你是否也能够本身触类旁通进行分析学习,本质都同样哈。因为我这里时间有限,因此对于flutter pub getflutter doctor等其余命令再也不作详细分析。

相关文章
相关标签/搜索