background

In our previous article, An In-depth analysis of the Engineering Structure and Application Layer Compilation source code of Flutter Android, we analyzed the main application layer compilation processes related to Flutter Android, When analyzing the underlying command tool [bin/ Flutter compile command analysis under the Flutter SDK], The essence of any flutter command we execute is to pass parameters to the main method of the FLUTTER_SDK_DIR/packages/flutter_tools/bin/flutter_tools.dart source code. What I want to do in this article is to uncover the essence of this layer by layer, and explain the origin of the compilation products in response to the previous part.

Flutter_tools introduction

throughflutter -hCommand to get an intuitive global sense of which parameters are supported, and some parameters have sub-parameters. All of the parameters we execute essentially go into the source entry of the module below.So if we want to use the Flutter tools directly from the source, we can use the following command directly from the current directory:

# ARGS is just a bunch of parameters, like build apk in our last post
dart bin/flutter_tools.dart ARGS
Copy the code

To regenerate the Flutter Tools snapshot, run the following command directly in the current directory:

rm .. /.. /bin/cache/flutter_tools.stamp .. /.. /bin/cache/flutter_tools.snapshotCopy the code

This successfully removes the Flutter Tools snapshot called by the shell script in the previous article and will automatically regenerate one during execution.

Source code analysis

The Flutter SDK entry packages/flutter_tools/bin/flutter_tools. Dart

/ / 1, import packages/flutter_tools/lib/the executable dart files
import 'package:flutter_tools/executable.dart' as executable;
//2, enter the focus, execute the executable. Main method and pass in our 'build apk' argument
void main(List<String> args) {
  executable.main(args);
}
Copy the code

Next we go to packages/flutter_tools/lib/the executable dart see his main method, as follows:

Future<void> main(List<String> args) async {
  // A bunch of parameters are resolved, e.g. the doctor parameters of the flutter doctor.//1. The essence of runner is import 'runner. Dart 'as runner;
  // This is essentially the argument pass that calls the run method, focusing on the first and second arguments
  await runner.run(
    args,
    () = > generateCommands(
      verboseHelp: verboseHelp,
      verbose: verbose,
    ),
    ......,
  );
}
//2, in step 1 runner. Run second core parameter method definition
/ / FlutterCommand for packages/flutter_tools/lib/SRC/runner/flutter_command dart defined in abstract classes
// This method essentially adds the parameter list of all the commands executed by the Flutter to the list, similar to the command mode
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(), ]; .Copy the code

Let’s move on to the run method of the Runner. Dart file, and then go back and see how step 1 in the above code calls Step 2, as follows:

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{.../ / 1, FlutterCommandRunner is located in the packages/flutter_tools/lib/SRC/runner/flutter_command_runner dart
  return runInContext<int>(() async {
    reportCrashes ??= !await globals.isRunningOnBot;
    //2. Create an instance of the Runner object and append the list of FlutterCommands returned by the step 2 method in the previous snippetfinal FlutterCommandRunner runner = FlutterCommandRunner(verboseHelp: verboseHelp); commands().forEach(runner.addCommand); .return runZoned<Future<int>>(() async {
      try {
      	//3, execute the run method of the Runner instance according to the args parameter
        awaitrunner.run(args); . }catch (error, stackTrace) {  // ignore: avoid_catches_without_on_clauses. }},onError: (Object error, StackTrace stackTrace) async { // ignore: deprecated_member_use. }); },overrides: overrides);
}
Copy the code

As you can see, we first instantiate a FlutterCommandRunner object, then add a list of all supported FlutterCommands to the Runner object, and then call the Runner’s run method. So now we check the packages/flutter_tools/lib/SRC/runner/flutter_command_runner dart files run method, as follows:

. @override Future<void> run(Iterable<String> args){...// The run method of the parent class CommandRunner is called, and the run method of the subclass FlutterCommandRunner is called
  // The runCommand of the subclass FlutterCommandRunner finally calls the runCommand method of the superclass CommandRunner
  return super.run(args); }...Copy the code

So let’s look at the runCommand method of CommandRunner, as follows:

Future<T? > runCommand(ArgResults topLevelResults)async {
  	//1. The flutter command is followed by an argument such as build apk
    var argResults = topLevelResults;
    //2, the list of supported commands added in the runner
    var commands = _commands;
    //3. Define a Command variable that is eventually assigned to the corresponding Command object instance based on the parameter
    Command? command;
    var commandString = executableName;
	//4. The while condition is true because Commands are the supported argument list
    while (commands.isNotEmpty) {
      ......
      //5argResults = argResults.command! ; command = commands[argResults.name]! ; command._globalResults = topLevelResults; command._argResults = argResults; commands = command._subcommandsas Map<String, Command<T>>;
      commandString += ' ${argResults.name}'; . }...//6. Run the corresponding command
    return (await command.run()) asT? ; }... }Copy the code

As you can see, this is a standard command pattern design, where the supported commands are added to the list and then executed by traversing the parameters to match the corresponding commands. Below we to flutter build apk command as an example to see its corresponding BuildCommand command (packages/flutter_tools/lib/SRC/commands/build dart) implementation, as follows:

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));
  }
  // This is what you get from command = commands[argresults.name]
  //name=build is the build string that executes the flutter build APk
  @override
  final String name = 'build';

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

  @override
  Future<FlutterCommandResult> runCommand() async= >null;
}
Copy the code

As can be seen, any command is basically inherited from FlutterCommand implementation, and the execution of the command calls the run method of FlutterCommand, as follows:

abstract class FlutterCommand extends Command<void> {...// This is the method in the Runner object that ultimately executes the call
  @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 {//1 (shouldUpdateCache) { await globals.cache.updateAll(<DevelopmentArtifact>{DevelopmentArtifact.universal}); await globals.cache.updateAll(await requiredArtifacts); } globals.cache.releaseLock(); //2, await validateCommand(); If (shouldRunPub) {if (shouldRunPub) {...... //4, perform pubspec.yaml to download await dependencies, i.e., to download await dependencies configured in pubspec. PubContext.getVerifyContext(name), generateSyntheticPackage: project.manifest.generateSyntheticPackage, checkUpToDate: cachePubGet, ); await project.regeneratePlatformSpecificTooling(); if (reportNullSafety) { await _sendNullSafetyAnalyticsEvents(project); } } setupApplicationPackages(); . Return runCommand(); }}Copy the code

And finally we go back to the BuildCommand class, and we see that its runCommand method is overridden with an empty implementation, and its construction appends a lot of subcommands through the addSubcommand method, For example, run the flutter build AAR to compile the BuildAarCommand of AAR, and run the flutter build APk to compile the BuildApkCommand of APK. The whole sub command is also a chain of responsibility with its host, so the same sequence applies to sub commands. So we go to the next compile apk product BuildApkCommand source (packages/flutter_tools/lib/SRC/commands/build_apk dart), as follows:

class BuildApkCommand extends BuildSubCommand {
  BuildApkCommand({bool verboseHelp = false}){...// Verify a bunch of parameters
  }
  // Corresponds to the command string apk in the flutter build apk
  @override
  final String name = 'apk'; .// The essential command execution method
  @override
  Future<FlutterCommandResult> runCommand() async{...// Call androidBuilder's buildApk method to do the actual build and visually inspect the artifacts that were analyzed in the last article
    / / androidBuilder located on the packages/flutter_tools/lib/SRC/android/android_builder dart
    await androidBuilder.buildApk(
      project: FlutterProject.current(),
      target: targetFile,
      androidBuildInfo: androidBuildInfo,
    );
    returnFlutterCommandResult.success(); }}Copy the code

We continue down this road to follow in the packages/flutter_tools/lib/SRC/android/android_builder androidBuilder attribute of the dart buildApk method, as follows:

// AndroidGradleBuilder instance in context.run method in flutter_tools/lib/ SRC /context_runner. Dart
AndroidBuilder get androidBuilder {
  return context.get<AndroidBuilder>();
}
// Abstract class definition, AndroidBuilder
abstract class AndroidBuilder {
  const AndroidBuilder();
  // Define the method to compile the AAR
  Future<void> buildAar({
    @required FlutterProject project,
    @required Set<AndroidBuildInfo> androidBuildInfo,
    @required String target,
    @required String outputDirectoryPath,
    @required String buildNumber,
  });

  // Define the method to compile apK
  Future<void> buildApk({
    @required FlutterProject project,
    @required AndroidBuildInfo androidBuildInfo,
    @required String target,
  });

  // Define a method to compile aAB
  Future<void> buildAab({
    @required FlutterProject project,
    @required AndroidBuildInfo androidBuildInfo,
    @required String target,
    bool validateDeferredComponents = true,
    bool deferredComponentsEnabled = false}); }Copy the code

So we continue to see AndroidGradleBuilder implementation class (packages/flutter_tools/lib/SRC/android/gradle dart) buildApk method is as follows:

class AndroidGradleBuilder implements AndroidBuilder { AndroidGradleBuilder({ ...... }) :... ; .//1, compile apk method
  @override
  Future<void> buildApk({
    @required FlutterProject project,
    @required AndroidBuildInfo androidBuildInfo,
    @required String target,
  }) async {
  	/ / 2, call
    await buildGradleApp(
      project: project,
      androidBuildInfo: androidBuildInfo,
      target: target,
      isBuildingBundle: false.localGradleErrors: gradleErrors, ); }...//3
  Future<void> buildGradleApp({
    @required FlutterProject project, //FlutterProject.current()
    @required AndroidBuildInfo androidBuildInfo, //build configuration
    @required String target, // Dart code entry, default lib/main.dart
    @required bool isBuildingBundle, // Aab or apk, default false apk
    @required List<GradleHandledError> localGradleErrors,
    bool shouldBuildPluginAsAar = false.// Whether to compile the plug-in as an AAR
    bool validateDeferredComponents = true,
    bool deferredComponentsEnabled = false,
    int retries = 1,})async {
    //4. Check the supported Android versions and get the Android build artifacts directory, which is the build artifacts directory configured in Gradle. The default is the Build directory in the project root directory
    if(! project.android.isSupportedVersion) { _exitWithUnsupportedProjectMessage(_usage, _logger.terminal); } final Directory buildDirectory = project.android.buildDirectory;//5, read the Android properties file to determine whether to use AndroidX, and then send the compile event parameter
    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, update the versionName and versionCode values in the local.properties of the Android project from the public
    updateLocalProperties(project: project, buildInfo: androidBuildInfo.buildInfo);
	//7. Build an aar using the buildPluginsAsAar method
    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. Obtain the standard Android task name, build parameters, and other information for compiling apK or AAB
    final BuildInfo buildInfo = androidBuildInfo.buildInfo;
    final StringassembleTask = isBuildingBundle ? getBundleTaskFor(buildInfo) : getAssembleTaskFor(buildInfo); . final List<String> command = <String>[ _gradleUtils.getExecutable(project), ]; .//9, append command parameters based on conditions, such as -psplit-per-abi =true, -pverbose =true, --no-daemon, etc.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(); }... }... }
      ,>Copy the code

You should have a complete understanding of how the ENGINEERING Structure and Application Level of the Flutter Android APK is compiled.

conclusion

Now we combineAn In-depth analysis of the Engineering Structure and Application Layer Compilation source code of Flutter AndroidIn conjunction with this article, we can summarize the implementationflutter build apkThe general process behind the command is as follows:Since the executionflutter build apkIf you have all the commands figured out, can you analyze and learn any other commands related to the flutter? The essence is the same. Because I have a limited amount of time here, so forflutter pub get,flutter doctorOther commands are not analyzed in detail.