Flutter命令执行原理 Flutter命令脚本 查看{flutter_sdk}/bin/flutter
脚本,内部会调用shared.sh
脚本的execute方法
1 2 source "$BIN_DIR/internal/shared.sh" shared::execute "$@"
查看shared.sh
脚本,最终会执行dart命令:
1 "$DART" --disable-dart-dev --packages="$FLUTTER_TOOLS_DIR/.packages" $FLUTTER_TOOL_ARGS "$SNAPSHOT_PATH" "$@"
$DART:Dart可执行文件,用于启动Dart虚拟机。对应{flutter_sdk}/bin/cache/dart-sdk/bin/dart
$FLUTTER_TOOLS_DIR:flutter_tools
项目路径,对应{flutter_sdk}/packages/flutter_tools
$SNAPSHOT_PATH:flutter_tools
项目的snapshot文件,包含编译过的kernel中间代码,可以被Dart虚拟机执行,对应{flutter_sdk}/bin/cache/flutter_tools.snapshot
$FLUTTER_TOOL_ARGS:用于调试Flutter SDK,一般情况为空。可以开启断言和调试端口。
$@:输入的参数
因此实际的命令即:flutter/bin/cache/dart-sdk/bin/dart flutter/bin/cache/flutter_tools.snapshot build apk
类似java执行jar文件:java -jar main.jar
,jar运行在java环境,snapshot运行在dart环境。
在AS中打开flutter/packages/flutter_tools/
源码,配置Dart SDK,支持代码跳转。
flutter_tools.snapshot
类似jar文件,也有对应的main函数入口,如下:
1 2 3 4 5 import 'package:flutter_tools/executable.dart' as executable;void main(List <String > args) { executable.main(args); }
查看executable.dart
源码如下:创建了多个Command子类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 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>[ CreateCommand(verboseHelp: verboseHelp), BuildCommand(verboseHelp: verboseHelp), RunCommand(verboseHelp: verboseHelp), AssembleCommand(verboseHelp: verboseHelp, buildSystem: globals.buildSystem), ];
查看runner.run
源码:根据参数找到对应的Command类
1 2 3 4 5 6 7 8 9 10 11 12 13 Future<int > run( List <String > args, List <FlutterCommand> Function () commands) async { return runInContext<int >(() async { final FlutterCommandRunner runner = FlutterCommandRunner(verboseHelp: verboseHelp); commands().forEach(runner.addCommand); return runZoned<Future<int >>(() async { await runner.run(args); }, overrides: overrides); }
FlutterCommandRunner
会调用父类CommandRunner
的run方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class CommandRunner <T > { Future<T?> run(Iterable <String > args) => Future.sync (() => runCommand(parse(args))); 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
类,Build后面包含多个子命令(如下)。
最终会匹配到BuildApkCommand
类,然后调用BuildApkCommand
的run方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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)); } }
XXCommand继承自FlutterCommand类,调用FlutterCommand父类的run方法,如下:verifyThenRunCommand
验证命令并调用runCommand方法。注释中说明了让子类重写runCommand
来执行命令。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 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 { } }, ); }
flutter_tools.snapshot
对应的源码位于{flutter_framework}/packages/flutter_tools/
中,可以直接修改。
修改了flutter_tools
源码之后如何编译并替换呢?
查看bin/internal/shared.sh
脚本的upgrade_flutter
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 function upgrade_flutter () ( mkdir -p "$FLUTTER_ROOT/bin/cache" local revision="$(cd "$FLUTTER_ROOT"; git rev-parse HEAD)" # Invalidate cache if : # * SNAPSHOT_PATH is not a file, or # * STAMP_PATH is not a file with nonzero size, or # * Contents of STAMP_PATH is not our local git HEAD revision, or # * pubspec.yaml last modified after pubspec.lock if [[ ! -f "$SNAPSHOT_PATH" || ! -s "$STAMP_PATH" || "$(cat "$STAMP_PATH")" != "$revision" || "$FLUTTER_TOOLS_DIR/pubspec.yaml" -nt "$FLUTTER_TOOLS_DIR/pubspec.lock" ]]; then # 等待锁 _wait_for_lock # 获取锁之后再次判断,防止并发 # A different shell process might have updated the tool/SDK. if [[ -f "$SNAPSHOT_PATH" && -s "$STAMP_PATH" && "$(cat "$STAMP_PATH")" == "$revision" && "$FLUTTER_TOOLS_DIR/pubspec.yaml" -ot "$FLUTTER_TOOLS_DIR/pubspec.lock" ]]; then exit $? fi #... # 重新编译生成快照文件snapshot "$DART" --verbosity=error --disable-dart-dev $FLUTTER_TOOL_ARGS --snapshot="$SNAPSHOT_PATH" --packages="$FLUTTER_TOOLS_DIR/.packages" --no-enable-mirrors "$SCRIPT_PATH" echo "$revision" > "$STAMP_PATH" fi exit $? )
什么时候会重新编译flutter_tools
?
注释已经写的很清楚了:
snapshot或者stamp文件不存在
stamp的commit id不是当前的HEAD id
pubspec.yaml文件被修改
替换本地引擎原理 根据官方文档 ,Flutter可以指定local-engine
和local-engine-src-path
选项来替换为本地编译引擎,这是如何生效的呢?
直接找到解析local-engine-src-path
参数的地方:
1 2 3 4 5 6 7 8 9 10 11 12 13 class FlutterCommandRunner extends CommandRunner <void > { @override Future<void > runCommand(ArgResults topLevelResults) async { final EngineBuildPaths engineBuildPaths = await globals.localEngineLocator.findEnginePath( topLevelResults['local-engine-src-path' ] as String , topLevelResults['local-engine' ] as String , topLevelResults['packages' ] as String , ); } }
查看findEnginePath
方法,如何获取引擎路径
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 class LocalEngineLocator { Future<EngineBuildPaths?> findEnginePath(String? engineSourcePath, String? localEngine, String? packagePath) async { engineSourcePath ??= _platform.environment[kFlutterEngineEnvironmentVariableName]; if (engineSourcePath == null && localEngine != null ) { try { engineSourcePath = _findEngineSourceByLocalEngine(localEngine); engineSourcePath ??= await _findEngineSourceByPackageConfig(packagePath); } on FileSystemException catch (e) { _logger.printTrace('Local engine auto-detection file exception: $e ' ); engineSourcePath = null ; } engineSourcePath ??= _tryEnginePath( _fileSystem.path.join(_fileSystem.directory(_flutterRoot).parent.path, 'engine' , 'src' ), ); } return null ; } String? _findEngineSourceByLocalEngine(String localEngine) { if (_fileSystem.path.isAbsolute(localEngine)) { final Directory localEngineDirectory = _fileSystem.directory(localEngine); final Directory outDirectory = localEngineDirectory.parent; final Directory srcDirectory = outDirectory.parent; if (localEngineDirectory.existsSync() && outDirectory.basename == 'out' && srcDirectory.basename == 'src' ) { _logger.printTrace('Parsed engine source from local engine as ${srcDirectory.path} .' ); return srcDirectory.path; } } return null ; } Future<String? > _findEngineSourceByPackageConfig(String? packagePath) async { } String? _tryEnginePath(String enginePath) { if (_fileSystem.isDirectorySync(_fileSystem.path.join(enginePath, 'out' ))) { return enginePath; } return null ; } }
如何获取Flutter编译引擎源码路径?--local-engine-src-path
什么时候可以省略?
流程如下
如果没有指定该参数,从系统环境变量$FLUTTER_ENGINE
读取源码路径
如果--local-engine
参数是绝对路径,且父文件夹为src/out/,则使用src目录
根据--packages
参数加载package_config.json
文件,读取sky_engine
配置路径,例如{flutter-engine-local-path}/src/out/host_debug_unopt/gen/dart-pkg/sky_engine/lib/
如果engine源码和flutter sdk目录同级,且存在src/out
文件夹,则使用src目录
总结
Flutter命令脚本会启动一个dart虚拟机,执行flutter_tools
的dart程序。
flutter_tools
封装了不同的命令类,通过解析参数,执行对应的Command类,并调用run方法,例如BuildApkCommand.runCommand()
替换flutter_tools
:删除{flutter_framework}/bin/cache/
下的flutter_tools.snapshot
和flutter_tools.stamp
文件,执行flutter命令的时候会重新构建`
替换本地引擎:通过local-engine
和local-engine-src-path
选项指定本地引擎路径
Flutter Android应用构建源码分析 以flutter build apk
为例,分析apk构建执行过程(其他构建可以自行分析)。
上文已经介绍了flutter命令查找过程,这里直接找到BuildApkCommand
命令
查看BuildApkCommand
的runCommand
方法:通过androidBuilder.buildApk
执行构建,AndroidBuilder
从context.get<AndroidBuilder>()
中获取,使用箭头函数,在调用的时候才会创建对象,实现了懒加载。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class BuildApkCommand extends BuildSubCommand { @override Future<FlutterCommandResult> runCommand() async { if (globals.androidSdk == null ) { exitWithNoSdkMessage(); } final BuildInfo buildInfo = await getBuildInfo(); final AndroidBuildInfo androidBuildInfo = AndroidBuildInfo( buildInfo, splitPerAbi: boolArg('split-per-abi' ), targetArchs: stringsArg('target-platform' ).map<AndroidArch>(getAndroidArchForName), ); validateBuild(androidBuildInfo); displayNullSafetyMode(androidBuildInfo.buildInfo); await androidBuilder.buildApk( project: FlutterProject.current(), target: targetFile, androidBuildInfo: androidBuildInfo, ); return FlutterCommandResult.success(); } }
最终会调用AndroidGradleBuilder
的buildGradleApp
方法,内部就是拼接gradle
命令,指定不同的参数,然后执行。例如
1 {project_path}/gradlew -q -Ptarget=lib/main.dart -Ptrack-widget-creation=false -Ptarget-platform=android-arm -Psplit-per-abi=true assembleRelease
flutter.gradle Android本身的代码构建基本没什么疑问,这里主要关心Flutter项目中的dart代码如何构建打包成so的。
查看build.gradle
文件,主要是引入了flutter.gradle
,源码位于flutter_tools
中
1 apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
flutter.gradle
自定义了gradle插件,解析gradle命令参数,并添加自定义task完成flutter项目的构建和打包。
下面分析几段关键代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 class FlutterPlugin implements Plugin <Project > { @Override void apply(Project project) { this .addFlutterTasks(project) if (shouldSplitPerAbi()) { project.android { splits { abi { enable true reset() universalApk false } } } } getTargetPlatforms().each { targetArch -> String abiValue = PLATFORM_ARCH_MAP[targetArch] project.android { if (shouldSplitPerAbi()) { splits { abi { include abiValue } } } } } project.android.buildTypes.all this .&addFlutterDependencies } }
查看addFlutterTask
方法:主要是添加compileTask用于编译Flutter,生成so库。再通过其他Task对产物进行重命名、移动、合并,最终打包出apk
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 class FlutterPlugin implements Plugin <Project > { private void addFlutterTasks(Project project) { def targetPlatforms = getTargetPlatforms() def addFlutterDeps = { variant -> String taskName = toCammelCase(["compile" , FLUTTER_BUILD_PREFIX, variant.name]) FlutterTask compileTask = project.tasks.create(name: taskName, type: FlutterTask) { } File libJar = project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter/${variant.name}/libs.jar" ) Task packFlutterAppAotTask = project.tasks.create(name: "packLibs${FLUTTER_BUILD_PREFIX}${variant.name.capitalize()}" , type: Jar) { destinationDir libJar.parentFile archiveName libJar.name dependsOn compileTask targetPlatforms.each { targetPlatform -> String abi = PLATFORM_ARCH_MAP[targetPlatform] from("${compileTask.intermediateDir}/${abi}" ) { include "*.so" rename { String filename -> return "lib/${abi}/lib${filename}" } } } } addApiDependencies(project, variant.name, project.files { packFlutterAppAotTask }) Task packageAssets = project.tasks.findByPath(":flutter:package${variant.name.capitalize()}Assets" ) Task cleanPackageAssets = project.tasks.findByPath(":flutter:cleanPackage${variant.name.capitalize()}Assets" ) Task copyFlutterAssetsTask = project.tasks.create( name: "copyFlutterAssets${variant.name.capitalize()}" , type: Copy, ) { } return copyFlutterAssetsTask } if (isFlutterAppProject()) { project.android.applicationVariants.all { variant -> Task assembleTask = getAssembleTask(variant) if (!shouldConfigureFlutterTask(assembleTask)) { return } Task copyFlutterAssetsTask = addFlutterDeps(variant) def variantOutput = variant.outputs.first() def processResources = variantOutput.hasProperty("processResourcesProvider" ) ? variantOutput.processResourcesProvider.get() : variantOutput.processResources processResources.dependsOn(copyFlutterAssetsTask) variant.outputs.all { output -> assembleTask.doLast { } } } configurePlugins() return } }
查看FlutterTask
源码如何编译Flutter代码生成libapp.so
:调用flutter assemble
命令编译flutter产物
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 class FlutterTask extends BaseFlutterTask { @TaskAction void build() { buildBundle() } } abstract class BaseFlutterTask extends DefaultTask { void buildBundle() { if (!sourceDir.isDirectory()) { throw new GradleException("Invalid Flutter source directory: ${sourceDir}" ) } intermediateDir.mkdirs() String[] ruleNames; if (buildMode == "debug" ) { ruleNames = ["debug_android_application" ] } else if (deferredComponents) { ruleNames = targetPlatformValues.collect { "android_aot_deferred_components_bundle_${buildMode}_$it" } } else { ruleNames = targetPlatformValues.collect { "android_aot_bundle_${buildMode}_$it" } } project.exec { logging.captureStandardError LogLevel.ERROR executable flutterExecutable.absolutePath workingDir sourceDir if (localEngine != null ) { args "--local-engine" , localEngine args "--local-engine-src-path" , localEngineSrcPath } if (verbose) { args "--verbose" } else { args "--quiet" } args "assemble" args "--no-version-check" args "--depfile" , "${intermediateDir}/flutter_build.d" args "--output" , "${intermediateDir}" args ruleNames } }
Deferred Component延迟组件:可以在运行时下载Dart代码编译,减少包大小。目前只在Android上可用,利用Android和Google Play商店的动态功能模块实现延迟加载。
参考Flutter延迟加载组件
flutter assemble编译 直接找到AssembleCommand
类的runCommand
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 class AssembleCommand extends FlutterCommand { @override Future<FlutterCommandResult> runCommand() async { final List <Target> targets = createTargets(); final List <Target> nonDeferredTargets = <Target>[]; final List <Target> deferredTargets = <AndroidAotDeferredComponentsBundle>[]; Target target; final BuildResult result = await _buildSystem.build( target, environment, buildSystemConfig: BuildSystemConfig( resourcePoolSize: argResults.wasParsed('resource-pool-size' ) ? int .tryParse(stringArg('resource-pool-size' )) : null , ), ); return FlutterCommandResult.success(); } List <Target> createTargets() { if (argResults.rest.isEmpty) { throwToolExit('missing target name for flutter assemble.' ); } final String name = argResults.rest.first; final Map <String , Target> targetMap = <String , Target>{ for (final Target target in _kDefaultTargets) target.name: target }; final List <Target> results = <Target>[ for (final String targetName in argResults.rest) if (targetMap.containsKey(targetName)) targetMap[targetName] ]; if (results.isEmpty) { throwToolExit('No target named "$name " defined.' ); } return results; } }
FlutterBuildSystem
继承自BuildSystem
,源码较长,简单来说就是将编译目标Target和其依赖的编译目标,组成一个个编译节点,依次调用Target的build方法。默认的编译目标如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 List <Target> _kDefaultTargets = <Target>[ const CopyAssets(), const KernelSnapshot(), const AotElfProfile(TargetPlatform.android_arm), const AotElfRelease(TargetPlatform.android_arm), const AotAssemblyProfile(), const AotAssemblyRelease(), const DebugMacOSFramework(), const DebugMacOSBundleFlutterAssets(), const ProfileMacOSBundleFlutterAssets(), const ReleaseMacOSBundleFlutterAssets(), const DebugBundleLinuxAssets(TargetPlatform.linux_x64), const DebugBundleLinuxAssets(TargetPlatform.linux_arm64), const ProfileBundleLinuxAssets(TargetPlatform.linux_x64), const ProfileBundleLinuxAssets(TargetPlatform.linux_arm64), const ReleaseBundleLinuxAssets(TargetPlatform.linux_x64), const ReleaseBundleLinuxAssets(TargetPlatform.linux_arm64), const WebServiceWorker(), const ReleaseAndroidApplication(), const CopyFlutterBundle(), const DebugAndroidApplication(), const ProfileAndroidApplication(), androidArmProfileBundle, androidArm64ProfileBundle, androidx64ProfileBundle, androidArmReleaseBundle, androidArm64ReleaseBundle, androidx64ReleaseBundle, androidArmProfileDeferredComponentsBundle, androidArm64ProfileDeferredComponentsBundle, androidx64ProfileDeferredComponentsBundle, androidArmReleaseDeferredComponentsBundle, androidArm64ReleaseDeferredComponentsBundle, androidx64ReleaseDeferredComponentsBundle, const DebugIosApplicationBundle(), const ProfileIosApplicationBundle(), const ReleaseIosApplicationBundle(), const UnpackWindows(), const DebugBundleWindowsAssets(), const ProfileBundleWindowsAssets(), const ReleaseBundleWindowsAssets(), const DebugBundleWindowsAssetsUwp(), const ProfileBundleWindowsAssetsUwp(), const ReleaseBundleWindowsAssetsUwp(), ];
挑一个androidArmReleaseBundle
编译目标的源码查看,如下:
androidArmReleaseBundle
编译目标对应AndroidAotBundle
类
依赖androidArmRelease
编译目标生成app.so
文件,对应AndroidAot
类
依赖AotAndroidAssetBundle
生成flutter_assets
文件,继承自AndroidAssetBundle
AndroidAot
和AndroidAssetBundle
编译目标都依赖KernelSnapshot
编译目标
此外,Debug模式的编译目标DebugAndroidApplication
也继承自AndroidAssetBundle
,并且拷贝生成kernel_blob.bin
、vm_snapshot_data
、isolate_snapshot_data
到flutter_assets
下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 const AndroidAotBundle androidArmReleaseBundle = AndroidAotBundle(androidArmRelease); class AndroidAotBundle extends Target { const AndroidAotBundle(this .dependency); @override String get name => 'android_aot_bundle_${getNameForBuildMode(dependency.buildMode)} _' '${getNameForTargetPlatform(dependency.targetPlatform)} ' ; @override List <Source> get inputs => <Source>[ Source.pattern('{BUILD_DIR}/$_androidAbiName /app.so' ), ]; @override List <Source> get outputs => <Source>[ Source.pattern('{OUTPUT_DIR}/$_androidAbiName /app.so' ), ]; @override List <Target> get dependencies => <Target>[ dependency, const AotAndroidAssetBundle(), ]; }
首先KernelSnapshot
目标调用KernelCompiler.compile
方法,将Flutter代码编译为app.dill
文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class KernelSnapshot extends Target { @override String get name => 'kernel_snapshot' ; @override Future<void > build(Environment environment) async { final KernelCompiler compiler = KernelCompiler( fileSystem: environment.fileSystem, logger: environment.logger, processManager: environment.processManager, artifacts: environment.artifacts, fileSystemRoots: <String >[], fileSystemScheme: null , ); final CompilerOutput output = await compiler.compile( sdkRoot: environment.artifacts.getArtifactPath( Artifact.flutterPatchedSdkPath, platform: targetPlatform, mode: buildMode, ), outputFilePath: environment.buildDir.childFile('app.dill' ).path, ); } }
compile内部实际是执行frontend_server.dart.snapshot
程序,命令如下,省略部分参数(亲测:修改路径之后可以直接执行,生成app.dill文件)
1 2 3 4 5 6 7 8 9 10 dart {flutter_sdk}/bin/cache/artifacts/engine/darwin-x64/frontend_server.dart.snapshot --sdk-root {flutter_sdk}/bin/cache/artifacts/engine/common/flutter_patched_sdk/ --target=flutter --aot --tfa -Ddart.vm.profile=false -Ddart.vm.product=true --packages .packages --output-dill {build_dir}/app.dill --depfile {build_dir}/kernel_snapshot.d {project}/main.dart
再查看AndroidAot
编译目标:可以将app.dill
文件编译成app.so
文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 const AndroidAot androidArmRelease = AndroidAot(TargetPlatform.android_arm, BuildMode.release);class AndroidAot extends AotElfBase { @override String get name => 'android_aot_${getNameForBuildMode(buildMode)} _' '${getNameForTargetPlatform(targetPlatform)} ' ; @override List <Target> get dependencies => const <Target>[ KernelSnapshot(), ]; @override Future<void > build(Environment environment) async { final AOTSnapshotter snapshotter = AOTSnapshotter( ); final int snapshotExitCode = await snapshotter.build( platform: targetPlatform, buildMode: buildMode, mainPath: environment.buildDir.childFile('app.dill' ).path, outputPath: output.path, bitcode: false , extraGenSnapshotOptions: extraGenSnapshotOptions, splitDebugInfo: splitDebugInfo, dartObfuscation: dartObfuscation, ); } }
AOTSnapshotter
内部调用_genSnapshot.run
方法,针对不同目标平台编译不同目标代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class AOTSnapshotter { Future<int > build( ) async { final String assembly = _fileSystem.path.join(outputDir.path, 'snapshot_assembly.S' ); if (platform == TargetPlatform.ios || platform == TargetPlatform.darwin) { genSnapshotArgs.addAll(<String >[ '--snapshot_kind=app-aot-assembly' , '--assembly=$assembly ' , ]); } else { final String aotSharedLibrary = _fileSystem.path.join(outputDir.path, 'app.so' ); genSnapshotArgs.addAll(<String >[ '--snapshot_kind=app-aot-elf' , '--elf=$aotSharedLibrary ' , ]); } final SnapshotType snapshotType = SnapshotType(platform, buildMode); final int genSnapshotExitCode = await _genSnapshot.run( snapshotType: snapshotType, additionalArgs: genSnapshotArgs, darwinArch: darwinArch, ); return 0 ; } }
_genSnapshot.run
内部实际是调用对应CPU架构引擎的gen_snapshot
程序,命令如下,省略部分参数(亲测:修改路径之后可以直接执行,将上步生成的app.dill
文件生成app.so
文件)
1 2 3 4 5 {flutter_sdk}/bin/cache/artifacts/engine/android-arm-release/darwin-x64/gen_snapshot --deterministic --snapshot_kind=app-aot-elf --elf={output_path}/app.so app.dill
查看AotAndroidAssetBundle
目标:调用copyAssets
生成Flutter静态资源文件(如图片、字体文件,配置文件等)并拷贝,如果是Debug模式,还会拷贝快照文件到flutter_assets
中,而不是打包成libapp.so
。Debug模式下产物如下:
kernel_blob.bin
:Dart编译前端Debug模式(不带--aot --tfa
参数)生成的Kernel快照文件,即app.dill
(aot编译的Kernel文件无法直接运行)
vm_snapshot_data
和isolate_snapshot_data
:包含Flutter引擎的虚拟机和特定isolate的Dart堆的初始状态,用于快速启动Dart虚拟机。分别对应引擎产物vm_isolate_snapshot.bin
、isolate_snapshot.bin
。
位于缓存引擎路径{flutter_sdk}/bin/cache/artifacts/engine/darwin-x64
下
或者本地编译引擎路径下{flutter_engine}/src/out/host_debug_unopt/gen/flutter/lib/snapshot/
下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 abstract class AndroidAssetBundle extends Target { @override Future<void > build(Environment environment) async { final Directory outputDirectory = environment.outputDir .childDirectory('flutter_assets' ) ..createSync(recursive: true ); if (buildMode == BuildMode.debug) { final String vmSnapshotData = environment.artifacts.getArtifactPath(Artifact.vmSnapshotData, mode: BuildMode.debug); final String isolateSnapshotData = environment.artifacts.getArtifactPath(Artifact.isolateSnapshotData, mode: BuildMode.debug); environment.buildDir.childFile('app.dill' ) .copySync(outputDirectory.childFile('kernel_blob.bin' ).path); environment.fileSystem.file(vmSnapshotData) .copySync(outputDirectory.childFile('vm_snapshot_data' ).path); environment.fileSystem.file(isolateSnapshotData) .copySync(outputDirectory.childFile('isolate_snapshot_data' ).path); } final Depfile assetDepfile = await copyAssets( environment, outputDirectory, targetPlatform: TargetPlatform.android, buildMode: buildMode, ); } }
FlutterTask用于编译Flutter资源,实际是执行flutter assemble
命令。编译流程如下:
frontend_server.dart.snapshot
是一个dart程序,作为Dart编译前端,可以将dart源码编译成.dill
中间代码文件。源码入口为{flutter_engine}/flutter_frontend_server/bin/starter.dart
,内部会调用Dart SDK中的frontend_server
,源码位于{dart_sdk}/pkg/frontend_server
中
gen_snapshot
是一个可执行程序,作为Dart编译后端,可以将.dill
文件编译成目标代码,生成.so
或者App.framework
,源码入口为{dart_sdk}/runtime/bin/gen_snapshot.cc
总结
BuildApkCommand
类中解析参数,调用gradle命令进行构建,例如gradlew assembleRelease
flutter_tools
中自定义了一个gradle脚本flutter.gradle
,将FlutterTask编译任务加到Android构建中,生成so库,合并打包生成最终的apk。
FlutterTask通过执行flutter assemble
命令来编译Flutter代码和资源。依次构建多个编译目标,例如:
KernelSnapshot
目标:调用KernelCompiler.compile()
方法,执行dart frontend_server.dart.snapshot
命令,生成.dill
文件
AndroidAot
目标:调用AOTSnapshotter.build()
方法,执行gen_snapshot
命令,生成app.so
文件
AotAndroidAssetBundle
目标:调用AssetBundle.build()
,生成flutter_assets
资源
可以自行调用编译器前端和后端进行编译生成so库,不通过flutter build
Flutter构建源码分析整体流程如下:
Android中的Flutter Release产物编译总体流程如下:
![](2022-01-12-Flutter应用构建流程分析/Flutter Android应用编译流程.png)
Flutter Linux应用构建源码分析 Linux桌面应用构建方式如下:
创建Linux工程:flutter create --platforms=linux .
开启Linux支持:flutter config --enable-linux-desktop
开始构建:flutter build linux --release
分析flutter_tools
源码,BuildLinuxCommand
会调用cmake和ninja命令,如下
1 2 3 4 5 # 执行cmake生成build.ninja文件 $ cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -DFLUTTER_TARGET_PLATFORM=linux-x64 /Users/Afauria/AndroidStudioProjects/flutter_app/linux # ninja编译 $ ninja -C build/linux/x64/release install
flutter_tools
内部通过flutter_tools/lib/src/base/process.dart
的ProcessUtil.stream()
方法执行命令行,可以在该方法中添加打印,查看执行的具体命令
查看build.ninja
文件,ninja会调用tool_backend.sh
脚本,内部会执行flutter assmeble
命令,找到对应的BuildTargets
,和Android类似,经过前后端编译生成app.so
文件
1 linux/x64/release/build.ninja:240: COMMAND = cd /Users/Afauria/AndroidStudioProjects/flutter_app/build/linux/x64/release/flutter && /usr/local/Cellar/cmake/3.19.5/bin/cmake -E env FLUTTER_ROOT=/Users/Afauria/Flutter/flutter PROJECT_DIR=/Users/Afauria/AndroidStudioProjects/flutter_app DART_OBFUSCATION=false TRACK_WIDGET_CREATION=true TREE_SHAKE_ICONS=true PACKAGE_CONFIG=/Users/Afauria/AndroidStudioProjects/flutter_app/.dart_tool/package_config.json FLUTTER_TARGET=lib/main.dart /Users/Afauria/Flutter/flutter/packages/flutter_tools/bin/tool_backend.sh linux-x64 Release
同时build.ninja
中使用clang编译linux平台代码(main.cc、my_application.cc
等)。
最终链接动态库生成可执行文件。
![](2022-01-12-Flutter应用构建流程分析/Flutter Linux应用编译流程.png)
mac上交叉编译Linux(尝试) Linux应用只能在Linux主机上进行编译,使用mac主机编译Linux会报各种错误。
首先flutter_tools
限制如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class BuildLinuxCommand extends BuildSubCommand { @override Future<FlutterCommandResult> runCommand() async { if (!featureFlags.isLinuxEnabled) { throwToolExit('"build linux" is not currently supported. To enable, run "flutter config --enable-linux-desktop".' ); } if (!globals.platform.isLinux) { throwToolExit('"build linux" only supported on Linux hosts.' ); } if (_operatingSystemUtils.hostPlatform != HostPlatform.linux_x64 && targetPlatform != TargetPlatform.linux_arm64) { throwToolExit('"cross-building" only supported on Linux x64 hosts.' ); } if (_operatingSystemUtils.hostPlatform == HostPlatform.linux_x64 && targetPlatform == TargetPlatform.linux_arm64) { throwToolExit( 'Cross-build from Linux x64 host to Linux arm64 target is not currently supported.' ); } } }
注释掉上面的限制,替换sdk中的flutter_tools
:删除{flutter_sdk}/bin/cache/
下的flutter_tools.snapshot
和flutter_tools.stamp
文件,重新执行build
命令
执行cmake命令时会报错找不到GTK3+库,使用brew进行安装,再次执行build
命令
1 2 3 # mac安装GTK+ $ brew install pkg-config $ brew install gtk+3
ninja执行前端编译成功,可以生成app.dill
文件(位于.dart_tool/flutter_build
中),后端编译失败:找不到对应的{flutter_sdk}/bin/cache/artifacts/engine/linux-x64-release/gen_snapshot
程序
注:gen_snapshot
本身是个可执行程序,这个程序要在Mac主机上运行(Mac的目标代码),gen_snapshot
同时是个编译器,我们希望在Mac上交叉编译出Linux的目标代码。
而Flutter SDK官方只提供了Android、iOS的交叉编译工具(gen_snapshot),以及桌面环境的本地编译工具,不支持交叉编译Linux。
由于gen_snapshot
是由Flutter引擎源码编译生成的,可以尝试修改源码,生成能在Mac上运行的Linux交叉编译工具。这里简单尝试了一下,没成功:./src/flutter/tools/gn --taget-os=linux --linux-cpu=x64 --release
。缺少llvm环境、sysroot等。
要想交叉编译Linux应用,可以利用Docker容器作为编译环境,参考Flutter编译环境搭建探索
Flutter Run原理 上面分析了如何Flutter应用的构建,那么如何将构建好的产物运行起来,程序入口是哪?
直接从RunCommand
开始分析。这里不具体分析了,简单介绍下流程:
src/commands/run.dart
:RunCommand
创建FlutterDevice,根据参数判断调用ColdRunner
还是HotRunner
src/resident_runner.dart
:最终会调用Device的startApp方法
src/android/android_device.dart
:AndroidDevice中是通过adb shell
安装apk,然后调用am start
命令,并传参数给FlutterActivity
src/desktop_device.dart
:DesktopDevice(桌面设备)中是直接找到可执行程序路径,通过命令启动。
gen_snapshot产物 上面提到了gen_snapshot将app.dill
编译为app.so
,主要由4个文件组成:参考Flutter-engine-operation-in-AOT-Mode
Dart VM Snapshot:虚拟机快照,所有Isolate共享的Dart堆的初始状态,存放在数据段中
Dart VM Instructions:虚拟机说明,所有Isolate共享的AOT指令,存放在文本段中
Isolate Snapshot:Isolate快照,特定Isolate的Dart堆的初始状态,存放在数据段中
Isolate Instructions:Isolate说明,特定Isolate执行的AOT指令,存放在文本段中
注:这里的vm_snapshot_data和isolate_snapshot_data和上文Debug模式中拷贝的Flutter资源不一样。上文拷贝的是Flutter引擎编译出来的快照,这里是开发者写的Dart代码编译出来的快照。
结合gen_snapshot
帮助和源码分析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 static const char * const kSnapshotKindNames[] = { "core" , "core-jit" , "app" , "app-jit" , "app-aot-assembly" , "app-aot-elf" , "vm-aot-assembly" , NULL , };
gen_snapshot支持多种编译方式:(亲测可编译)
core:输出Dart VM Snapshot、Isolate Snapshot
core-jit:输出Dart VM Snapshot、Isolate Snapshot、Dart VM Instructions、Isolate Instructions
app和app-jit:输出Dart VM Snapshot、Isolate Snapshot、Isolate Instructions
app-aot-elf:输出ELF共享库文件,如.so
文件
app-aot-assembly和vm-aot-assembly:输出.S
汇编文件
流程如下:
结语 了解编译和构建流程有几大作用:
便于分析定位构建错误,一般报错之后可以在flutter_tools
中搜索关键字,找到报错的地方,分析代码上下文。
可以修改flutter_tools
流程,定制构建命令,修改编译和打包流程
了解Flutter代码的编译,如何生成产物,合并打包
Flutter SDK包含框架代码、脚手架、编译工具、调试工具和各种脚本等。
Flutter只是一个框架,不是一门语言,Flutter使用了Dart语言,Flutter引擎中的嵌入层(UI渲染、输入输出、以及PlatformChannel等)使用了平台原生语言(如C++,Java等)。
Flutter的编译和跨平台执行实际是Dart代码的编译和跨平台执行,可以看到front_end
、gen_snapshot
都是Dart SDK中的代码,Flutter只是对编译命令进行了一些封装,并且将Flutter框架代码也加入编译。
通过ninja实现Dart SDK的多平台编译,生成不同平台的gen_snapshot程序,使用不同平台的gen_snapshot程序,可以生成不同平台的目标代码。
要定制Flutter引擎,交叉编译到不同嵌入式平台运行,可以先研究理解纯Dart程序的交叉编译和运行。
关于Dart编译前端(生成Dart Kernel文件,即.dill
)和编译后端(生成目标代码)过程可以参考:Dart的编译和执行
参考资料: