背景
上一篇《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 tools,则可以直接当前目录中如下命令:
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 命令,如下:
import 'package:flutter_tools/executable.dart' as executable;
void main(List<String> args) {
executable.main(args);
}
接下来我们去packages/flutter_tools/lib/executable.dart 看看他的 main 方法,如下:
Future<void> main(List<String> args) async {
......
await runner.run(
args,
() => generateCommands(
verboseHelp: verboseHelp,
verbose: verbose,
),
......,
);
}
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(
......
),
IdeConfigCommand(),
UpdatePackagesCommand(),
];
......
让我们把目光先移动到runner.dart 文件的 run 方法,然后回过头来看上面代码中的步骤1如何调用步骤2,如下:
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 {
......
return runInContext<int>(() async {
reportCrashes ??= !await globals.isRunningOnBot;
final FlutterCommandRunner runner = FlutterCommandRunner(verboseHelp: verboseHelp);
commands().forEach(runner.addCommand);
......
return runZoned<Future<int>>(() async {
try {
await runner.run(args);
......
} catch (error, stackTrace) {
......
}
}, onError: (Object error, StackTrace stackTrace) async {
......
});
}, overrides: overrides);
}
可以看到,首先实例化了一个 FlutterCommandRunner 对象,接着把所有支持的 FlutterCommand 列表加入 runner 对象中,然后调用了 runner 的 run 方法,所以我们现在查看packages/flutter_tools/lib/src/runner/flutter_command_runner.dart 文件的 run 方法,如下:
......
@override
Future<void> run(Iterable<String> args) {
......
return super.run(args);
}
......
所以我们接下来看父类 CommandRunner 的 runCommand 方法,如下:
Future<T?> runCommand(ArgResults topLevelResults) async {
var argResults = topLevelResults;
var commands = _commands;
Command? command;
var commandString = executableName;
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}';
......
}
......
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));
}
@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> {
......
@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();
if (shouldRunPub) {
......
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}) {
......
}
@override
final String name = 'apk';
......
@override
Future<FlutterCommandResult> runCommand() async {
......
await androidBuilder.buildApk(
project: FlutterProject.current(),
target: targetFile,
androidBuildInfo: androidBuildInfo,
);
return FlutterCommandResult.success();
}
}
顺着这条路我们继续跟进位于packages/flutter_tools/lib/src/android/android_builder.dart 的 androidBuilder 属性的 buildApk 方法,如下:
AndroidBuilder get androidBuilder {
return context.get<AndroidBuilder>();
}
abstract class AndroidBuilder {
const AndroidBuilder();
Future<void> buildAar({
@required FlutterProject project,
@required Set<AndroidBuildInfo> androidBuildInfo,
@required String target,
@required String outputDirectoryPath,
@required String buildNumber,
});
Future<void> buildApk({
@required FlutterProject project,
@required AndroidBuildInfo androidBuildInfo,
@required String target,
});
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({
......
}) : ......;
......
@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,
@required AndroidBuildInfo androidBuildInfo,
@required String target,
@required bool isBuildingBundle,
@required List<GradleHandledError> localGradleErrors,
bool shouldBuildPluginAsAar = false,
bool validateDeferredComponents = true,
bool deferredComponentsEnabled = false,
int retries = 1,
}) async {
if (!project.android.isSupportedVersion) {
_exitWithUnsupportedProjectMessage(_usage, _logger.terminal);
}
final Directory buildDirectory = project.android.buildDirectory;
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();
......
}
updateLocalProperties(project: project, buildInfo: androidBuildInfo.buildInfo);
if (shouldBuildPluginAsAar) {
createSettingsAarGradle(project.android.hostAppGradleRoot, _logger);
await buildPluginsAsAar(
project,
androidBuildInfo,
buildDirectory: buildDirectory.childDirectory('app'),
);
}
final BuildInfo buildInfo = androidBuildInfo.buildInfo;
final String assembleTask = isBuildingBundle
? getBundleTaskFor(buildInfo)
: getAssembleTaskFor(buildInfo);
......
final List<String> command = <String>[
_gradleUtils.getExecutable(project),
];
......
......
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 get 、flutter doctor 等其他命令不再做详细分析。
|