你的位置:首页 > 信息动态 > 新闻中心
信息动态
联系我们

Flutter 命令本质之 Flutter tools 机制源码深入分析,一次哔哩哔哩面试经历

2021/12/14 13:38:15

=============================================================================

通过flutter -h命令我们可以直观全局感受都支持哪些参数,有些参数还有子参数。我们所执行的所有参数本质都走进了下面模块的源码入口中。

在这里插入图片描述

因此我们如果直接想从源码方式使用 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文件

《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》

【docs.qq.com/doc/DSkNLaERkbnFoS0ZF】 完整内容开源分享

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<Type, Generator> 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<Future>(() 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<T?> 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<String, Command>;

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: <Type, Generator>{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,