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

Flutter 系列文章连载~

  • 《Flutter Android 工程结构及应用层编译源码深入分析》
  • 《Flutter 命令本质之 Flutter tools 机制源码深入分析》
  • 《Flutter 的 runApp 与三棵树诞生流程源码分析》
  • 《Flutter Android 端 Activity/Fragment 流程源码分析》
  • 《Flutter Android 端 FlutterInjector 及依赖流程源码分析》
  • 《Flutter Android 端 FlutterEngine Java 相关流程源码分析》
  • 《Flutter Android 端 FlutterView 相关流程源码分析》
  • 《Flutter 绘制动机 VSYNC 流程源码全方位分析》
  • 《Flutter 安卓 Platform 与 Dart 端消息通信方式 Channel 源码解析》
背景 上一篇《Flutter Android 工程结构及应用层编译源码深入分析》我们分析了 Flutter Android 相关的应用层主要编译流程,其中分析到底层本质命令工具【Flutter SDK 下bin/flutter编译命令分析】小节时只提到,我们执行任何 flutter 命令的本质都是把参数传递到了FLUTTER_SDK_DIR/packages/flutter_tools/bin/flutter_tools.dart源码的 main 方法中,没有对这里面进行深入分析。本文要做的事就是层层递进揭开这里的本质,并与上篇呼应解释编译产物的由来。
flutter_tools 介绍 通过flutter -h命令我们可以直观全局感受都支持哪些参数,有些参数还有子参数。我们所执行的所有参数本质都走进了下面模块的源码入口中。
Flutter 命令本质之 Flutter tools 机制源码深入分析
文章图片

因此我们如果直接想从源码方式使用 flutter tools,则可以直接当前目录中如下命令:
# ARGS 就是一堆参数,譬如我们上篇的 build apk dart bin/flutter_tools.dart ARGS

如果想重新生成 Flutter Tools snapshot,可以直接当前目录中执行如下命令:
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命令,如下:
//1、导入packages/flutter_tools/lib/executable.dart文件 import 'package:flutter_tools/executable.dart' as executable; //2、入口重点,执行executable.main方法,并将我们`build apk`参数传入 void main(List args) { executable.main(args); }

接下来我们去packages/flutter_tools/lib/executable.dart看看他的 main 方法,如下:
Future main(List args) async { //一堆参数解析判断啥的,譬如解析flutter doctor的doctor参数啥的 ...... //1、重点!runner的本质是import 'runner.dart' as runner; // 本质就是调用run方法的各种参数传递,重点关注第一个和第二个参数即可 await runner.run( args, () => generateCommands( verboseHelp: verboseHelp, verbose: verbose, ), ......, ); } //2、步骤1中runner.run的第二个核心参数方法定义 //FlutterCommand为packages/flutter_tools/lib/src/runner/flutter_command.dart中定义的抽象类 //这个方法本质就是把flutter执行的命令参数列表全部加入列表,类似命令模式 List generateCommands({ @required bool verboseHelp, @required bool verbose, }) => [ 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,如下:
Future run( List args, List Function() commands, { bool muteCommandLogging = false, bool verbose = false, bool verboseHelp = false, bool reportCrashes, String flutterVersion, Map overrides, }) async { ...... //1、FlutterCommandRunner位于packages/flutter_tools/lib/src/runner/flutter_command_runner.dart return runInContext(() async { reportCrashes ??= !await globals.isRunningOnBot; //2、创建runner对象实例,并把上一片段代码中步骤2方法返回的FlutterCommand列表追加进runner中 final FlutterCommandRunner runner = FlutterCommandRunner(verboseHelp: verboseHelp); commands().forEach(runner.addCommand); ...... return runZoned>(() async { try { //3、依据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 方法,如下:
...... @override Future run(Iterable args) { ...... //本质调用了父类CommandRunner的run方法,run方法调用了子类FlutterCommandRunner的runCommand方法 //子类FlutterCommandRunner的runCommand最终又调用了父类CommandRunner的runCommand方法 return super.run(args); } ......

所以我们接下来看父类 CommandRunner 的 runCommand 方法,如下:
Future runCommand(ArgResults topLevelResults) async { //1、flutter命令后面传递进来参数,譬如build apk var argResults = topLevelResults; //2、前面分析过的,runner中添加的支持命令列表 var commands = _commands; //3、定义一个Command变量,用来最终依据参数赋值为对应的Command对象实例 Command? command; var commandString = executableName; //4、while条件为真,因为commands为支持的参数列表 while (commands.isNotEmpty) { ...... //5、填充指令 argResults = argResults.command!; command = commands[argResults.name]!; command._globalResults = topLevelResults; command._argResults = argResults; commands = command._subcommands as Map>; commandString += ' ${argResults.name}'; ...... } ...... //6、执行对应命令的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 runCommand() async => null; }

可以看到,任意一个命令基本都继承自 FlutterCommand 实现,命令的执行都是调用了 FlutterCommand 的 run 方法,如下:
abstract class FlutterCommand extends Command { ...... //runner对象中最终执行调用的方法是这个 @override Future run() { ...... return context.run( name: 'command', overrides: {FlutterCommand: () => this}, body: () async { ...... try { //见名知意,先校验再运行命令 commandResult = await verifyThenRunCommand(commandPath); } finally { ...... } }, ); } ...... @mustCallSuper Future verifyThenRunCommand(String commandPath) async { //1、如果需要更新缓存就先更新缓存 if (shouldUpdateCache) { await globals.cache.updateAll({DevelopmentArtifact.universal}); await globals.cache.updateAll(await requiredArtifacts); } globals.cache.releaseLock(); //2、校验命令 await validateCommand(); //3、如果需要先执行pub就先执行,譬如pub get下载依赖 if (shouldRunPub) { ...... //4、执行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(); ...... //5、真正开始执行命令 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 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 abstract class AndroidBuilder { const AndroidBuilder(); // 定义编译aar的方法 Future buildAar({ @required FlutterProject project, @required Set androidBuildInfo, @required String target, @required String outputDirectoryPath, @required String buildNumber, }); // 定义编译apk的方法 Future buildApk({ @required FlutterProject project, @required AndroidBuildInfo androidBuildInfo, @required String target, }); // 定义编译aab的方法 Future 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({ ...... }) : ......; ...... //1、编译 apk 的方法 @override Future buildApk({ @required FlutterProject project, @required AndroidBuildInfo androidBuildInfo, @required String target, }) async { //2、调用 await buildGradleApp( project: project, androidBuildInfo: androidBuildInfo, target: target, isBuildingBundle: false, localGradleErrors: gradleErrors, ); } ...... //3、真的编译 Future 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 localGradleErrors, bool shouldBuildPluginAsAar = false, //是不是将插件编译为aar bool validateDeferredComponents = true, bool deferredComponentsEnabled = false, int retries = 1, }) async { //4、检查支持的android版本,获取android编译产物目录,即gradle中配置的build产物目录,默认为项目根目录下的build目录 if (!project.android.isSupportedVersion) { _exitWithUnsupportedProjectMessage(_usage, _logger.terminal); } final Directory buildDirectory = project.android.buildDirectory; //5、读取安卓相关属性文件判断是否使用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(); ...... } //6、更新安卓项目中local.properties中的versionName和versionCode值,值来自于public.yaml文件配置 updateLocalProperties(project: project, buildInfo: androidBuildInfo.buildInfo); //7、编译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'), ); } //8、获取编译apk或者aab对应的标准安卓task name,构建参数等信息,也就是gradle命令后面一堆的参数构造 final BuildInfo buildInfo = androidBuildInfo.buildInfo; final String assembleTask = isBuildingBundle ? getBundleTaskFor(buildInfo) : getAssembleTaskFor(buildInfo); ...... final List command = [ _gradleUtils.getExecutable(project), ]; ...... //9、依据条件追加command的一堆参数,譬如-Psplit-per-abi=true、-Pverbose=true、--no-daemon等 ...... try { exitCode = await _processUtils.stream( command, workingDirectory: project.android.hostAppGradleRoot.path, allowReentrantFlutter: true, environment: { if (javaPath != null) 'JAVA_HOME': javaPath, }, mapFunction: consumeLog, ); } on ProcessException catch (exception) { ...... } finally { status.stop(); } ...... } ...... }

哈哈,真相了,这下配合《Flutter Android 工程结构及应用层编译源码深入分析》一文首尾呼应后你应该彻底明白 Flutter android apk 是怎么编译的流程!
总结 【Flutter 命令本质之 Flutter tools 机制源码深入分析】现在我们结合《Flutter Android 工程结构及应用层编译源码深入分析》和这篇进行关联总结,可以总结出执行flutter build apk命令背后的大致主流程如下:
Flutter 命令本质之 Flutter tools 机制源码深入分析
文章图片

既然执行flutter build apk命令你都搞明白了,那么其他 flutter 相关的任何命令你是否也可以自己举一反三进行分析学习,本质都一样哈。由于我这里时间有限,所以对于flutter pub getflutter doctor等其他命令不再做详细分析。

    推荐阅读