Flutter 命令本質之 Flutter tools 機制源碼深入分析

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"背景","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上一篇","attrs":{}},{"type":"link","attrs":{"href":"https://yanbober.blog.csdn.net/article/details/118758871","title":"","type":null},"content":[{"type":"text","text":"《Flutter Android 工程結構及應用層編譯源碼深入分析》","attrs":{}}]},{"type":"text","text":"我們分析了 Flutter Android 相關的應用層主要編譯流程,其中分析到底層本質命令工具【Flutter SDK 下","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"bin/flutter","attrs":{}}],"attrs":{}},{"type":"text","text":"編譯命令分析】小節時只提到,我們執行任何 flutter 命令的本質都是把參數傳遞到了","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"FLUTTER_SDK_DIR/packages/flutter_tools/bin/flutter_tools.dart","attrs":{}}],"attrs":{}},{"type":"text","text":"源碼的 main 方法中,沒有對這裏面進行深入分析。本文要做的事就是層層遞進揭開這裏的本質,並與上篇呼應解釋編譯產物的由來。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"flutter_tools 介紹","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"flutter -h","attrs":{}}],"attrs":{}},{"type":"text","text":"命令我們可以直觀全局感受都支持哪些參數,有些參數還有子參數。我們所執行的所有參數本質都走進了下面模塊的源碼入口中。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/c4/c4b396d7227e5950ba38b9400ce48bdc.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因此我們如果直接想從源碼方式使用 flutter tools,則可以直接當前目錄中如下命令:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"shell"},"content":[{"type":"text","text":"# ARGS 就是一堆參數,譬如我們上篇的 build apk\ndart bin/flutter_tools.dart ARGS\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果想重新生成 Flutter Tools snapshot,可以直接當前目錄中執行如下命令:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"shell"},"content":[{"type":"text","text":"rm ../../bin/cache/flutter_tools.stamp ../../bin/cache/flutter_tools.snapshot\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這樣就成功刪除了上篇中 shell 腳本調用的 Flutter Tools snapshot,然後在執行時會自動重新生成一個。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"源碼分析","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上面既然交代了整個背景,那麼我們接下來就基於 Flutter SDK 入口","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"packages/flutter_tools/bin/flutter_tools.dart","attrs":{}}],"attrs":{}},{"type":"text","text":"開始分析,整個分析繼續承接上篇","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"flutter build apk","attrs":{}}],"attrs":{}},{"type":"text","text":"命令,如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"//1、導入packages/flutter_tools/lib/executable.dart文件\nimport 'package:flutter_tools/executable.dart' as executable;\n//2、入口重點,執行executable.main方法,並將我們`build apk`參數傳入\nvoid main(List args) {\n executable.main(args);\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"接下來我們去","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"packages/flutter_tools/lib/executable.dart","attrs":{}}],"attrs":{}},{"type":"text","text":"看看他的 main 方法,如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"Future main(List args) async {\n //一堆參數解析判斷啥的,譬如解析flutter doctor的doctor參數啥的\n ......\n //1、重點!runner的本質是import 'runner.dart' as runner;\n // 本質就是調用run方法的各種參數傳遞,重點關注第一個和第二個參數即可\n await runner.run(\n args,\n () => generateCommands(\n verboseHelp: verboseHelp,\n verbose: verbose,\n ),\n ......,\n );\n}\n//2、步驟1中runner.run的第二個核心參數方法定義\n//FlutterCommand爲packages/flutter_tools/lib/src/runner/flutter_command.dart中定義的抽象類\n//這個方法本質就是把flutter執行的命令參數列表全部加入列表,類似命令模式\nList generateCommands({\n @required bool verboseHelp,\n @required bool verbose,\n}) => [\n AnalyzeCommand(\n ......\n ),\n AssembleCommand(verboseHelp: verboseHelp, buildSystem: globals.buildSystem),\n AttachCommand(verboseHelp: verboseHelp),\n BuildCommand(verboseHelp: verboseHelp),\n ChannelCommand(verboseHelp: verboseHelp),\n CleanCommand(verbose: verbose),\n ConfigCommand(verboseHelp: verboseHelp),\n CreateCommand(verboseHelp: verboseHelp),\n DaemonCommand(hidden: !verboseHelp),\n DevicesCommand(verboseHelp: verboseHelp),\n DoctorCommand(verbose: verbose),\n DowngradeCommand(verboseHelp: verboseHelp),\n DriveCommand(verboseHelp: verboseHelp,\n ......\n ),\n EmulatorsCommand(),\n FormatCommand(),\n GenerateCommand(),\n GenerateLocalizationsCommand(\n ......\n ),\n InstallCommand(),\n LogsCommand(),\n MakeHostAppEditableCommand(),\n PackagesCommand(),\n PrecacheCommand(\n ......\n ),\n RunCommand(verboseHelp: verboseHelp),\n ScreenshotCommand(),\n ShellCompletionCommand(),\n TestCommand(verboseHelp: verboseHelp),\n UpgradeCommand(verboseHelp: verboseHelp),\n SymbolizeCommand(\n ......\n ),\n // Development-only commands. These are always hidden,\n IdeConfigCommand(),\n UpdatePackagesCommand(),\n];\n......\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"讓我們把目光先移動到","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"runner.dart","attrs":{}}],"attrs":{}},{"type":"text","text":"文件的 run 方法,然後回過頭來看上面代碼中的步驟1如何調用步驟2,如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"Future run(\n List args,\n List Function() commands, {\n bool muteCommandLogging = false,\n bool verbose = false,\n bool verboseHelp = false,\n bool reportCrashes,\n String flutterVersion,\n Map overrides,\n }) async {\n ......\n //1、FlutterCommandRunner位於packages/flutter_tools/lib/src/runner/flutter_command_runner.dart\n return runInContext(() async {\n reportCrashes ??= !await globals.isRunningOnBot;\n //2、創建runner對象實例,並把上一片段代碼中步驟2方法返回的FlutterCommand列表追加進runner中\n final FlutterCommandRunner runner = FlutterCommandRunner(verboseHelp: verboseHelp);\n commands().forEach(runner.addCommand);\n ......\n return runZoned>(() async {\n try {\n //3、依據args參數執行runner實例的run方法\n await runner.run(args);\n ......\n } catch (error, stackTrace) { // ignore: avoid_catches_without_on_clauses\n ......\n }\n }, onError: (Object error, StackTrace stackTrace) async { // ignore: deprecated_member_use\n ......\n });\n }, overrides: overrides);\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以看到,首先實例化了一個 FlutterCommandRunner 對象,接着把所有支持的 FlutterCommand 列表加入 runner 對象中,然後調用了 runner 的 run 方法,所以我們現在查看","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"packages/flutter_tools/lib/src/runner/flutter_command_runner.dart","attrs":{}}],"attrs":{}},{"type":"text","text":"文件的 run 方法,如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"......\n@override\nFuture run(Iterable args) {\n ......\n //本質調用了父類CommandRunner的run方法,run方法調用了子類FlutterCommandRunner的runCommand方法\n //子類FlutterCommandRunner的runCommand最終又調用了父類CommandRunner的runCommand方法\n return super.run(args);\n}\n......\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以我們接下來看父類 CommandRunner 的 runCommand 方法,如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":" Future runCommand(ArgResults topLevelResults) async {\n //1、flutter命令後面傳遞進來參數,譬如build apk\n var argResults = topLevelResults;\n //2、前面分析過的,runner中添加的支持命令列表\n var commands = _commands;\n //3、定義一個Command變量,用來最終依據參數賦值爲對應的Command對象實例\n Command? command;\n var commandString = executableName;\n //4、while條件爲真,因爲commands爲支持的參數列表\n while (commands.isNotEmpty) {\n ......\n //5、填充指令\n argResults = argResults.command!;\n command = commands[argResults.name]!;\n command._globalResults = topLevelResults;\n command._argResults = argResults;\n commands = command._subcommands as Map>;\n commandString += ' ${argResults.name}';\n ......\n }\n ......\n //6、執行對應命令的run方法\n return (await command.run()) as T?;\n }\n ......\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以看到,這就是一個標準的命令模式設計,先把支持的命令添加到列表,然後依據參數遍歷匹配對應命令進行執行。下面我們以","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"flutter build apk","attrs":{}}],"attrs":{}},{"type":"text","text":"命令爲例來看其對應的 BuildCommand 命令(","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"packages/flutter_tools/lib/src/commands/build.dart","attrs":{}}],"attrs":{}},{"type":"text","text":")實現,如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"class BuildCommand extends FlutterCommand {\n BuildCommand({ bool verboseHelp = false }) {\n addSubcommand(BuildAarCommand(verboseHelp: verboseHelp));\n addSubcommand(BuildApkCommand(verboseHelp: verboseHelp));\n addSubcommand(BuildAppBundleCommand(verboseHelp: verboseHelp));\n addSubcommand(BuildIOSCommand(verboseHelp: verboseHelp));\n addSubcommand(BuildIOSFrameworkCommand(\n buildSystem: globals.buildSystem,\n verboseHelp: verboseHelp,\n ));\n addSubcommand(BuildIOSArchiveCommand(verboseHelp: verboseHelp));\n addSubcommand(BuildBundleCommand(verboseHelp: verboseHelp));\n addSubcommand(BuildWebCommand(verboseHelp: verboseHelp));\n addSubcommand(BuildMacosCommand(verboseHelp: verboseHelp));\n addSubcommand(BuildLinuxCommand(\n operatingSystemUtils: globals.os,\n verboseHelp: verboseHelp\n ));\n addSubcommand(BuildWindowsCommand(verboseHelp: verboseHelp));\n addSubcommand(BuildWindowsUwpCommand(verboseHelp: verboseHelp));\n addSubcommand(BuildFuchsiaCommand(verboseHelp: verboseHelp));\n }\n //上一小段代碼中command = commands[argResults.name]就是這麼得到的\n //name=build就是執行flutter build apk中的build字符串\n @override\n final String name = 'build';\n\n @override\n final String description = 'Build an executable app or install bundle.';\n\n @override\n Future runCommand() async => null;\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以看到,任意一個命令基本都繼承自 FlutterCommand 實現,命令的執行都是調用了 FlutterCommand 的 run 方法,如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"abstract class FlutterCommand extends Command {\n ......\n //runner對象中最終執行調用的方法是這個\n @override\n Future run() {\n ......\n return context.run(\n name: 'command',\n overrides: {FlutterCommand: () => this},\n body: () async {\n ......\n try {\n //見名知意,先校驗再運行命令\n commandResult = await verifyThenRunCommand(commandPath);\n } finally {\n ......\n }\n },\n );\n }\n ......\n @mustCallSuper\n Future verifyThenRunCommand(String commandPath) async {\n //1、如果需要更新緩存就先更新緩存\n if (shouldUpdateCache) {\n await globals.cache.updateAll({DevelopmentArtifact.universal});\n await globals.cache.updateAll(await requiredArtifacts);\n }\n globals.cache.releaseLock();\n //2、校驗命令\n await validateCommand();\n //3、如果需要先執行pub就先執行,譬如pub get下載依賴\n if (shouldRunPub) {\n ......\n //4、執行pub get下載依賴,即下載pubspec.yaml裏配置的依賴\n await pub.get(\n context: PubContext.getVerifyContext(name),\n generateSyntheticPackage: project.manifest.generateSyntheticPackage,\n checkUpToDate: cachePubGet,\n );\n await project.regeneratePlatformSpecificTooling();\n if (reportNullSafety) {\n await _sendNullSafetyAnalyticsEvents(project);\n }\n }\n\n setupApplicationPackages();\n ......\n //5、真正開始執行命令\n return runCommand();\n }\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"繞一圈最終我們又回到 BuildCommand 類,可以發現其 runCommand 方法重寫爲空實現,而其構造時通過 addSubcommand 方法追加了很多子命令,譬如執行","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"flutter build aar","attrs":{}}],"attrs":{}},{"type":"text","text":"編譯 aar 的 BuildAarCommand 命令、執行","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"flutter build apk","attrs":{}}],"attrs":{}},{"type":"text","text":"編譯 apk 的 BuildApkCommand 命令。整個 sub command 與其宿主又算是一個責任鏈,所以上面同樣的套路順序對於 sub command 同樣適用,因此我們去看下編譯 apk 產物的 BuildApkCommand 源碼(","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"packages/flutter_tools/lib/src/commands/build_apk.dart","attrs":{}}],"attrs":{}},{"type":"text","text":"),如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"class BuildApkCommand extends BuildSubCommand {\n BuildApkCommand({bool verboseHelp = false}) {\n ......\n //一堆參數的確認\n }\n //對應flutter build apk裏面子命令字符串apk\n @override\n final String name = 'apk';\n ......\n //本質命令執行方法\n @override\n Future runCommand() async {\n ......\n //調用androidBuilder的buildApk方法進行真正的編譯,目測裏面的產物也就是上一篇文章分析的那些\n //androidBuilder位於packages/flutter_tools/lib/src/android/android_builder.dart\n await androidBuilder.buildApk(\n project: FlutterProject.current(),\n target: targetFile,\n androidBuildInfo: androidBuildInfo,\n );\n return FlutterCommandResult.success();\n }\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"順着這條路我們繼續跟進位於","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"packages/flutter_tools/lib/src/android/android_builder.dart","attrs":{}}],"attrs":{}},{"type":"text","text":"的 androidBuilder 屬性的 buildApk 方法,如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"//本質是packages/flutter_tools/lib/src/context_runner.dart中context.run方法中的AndroidGradleBuilder實例\nAndroidBuilder get androidBuilder {\n return context.get();\n}\n//抽象類定義,AndroidBuilder\nabstract class AndroidBuilder {\n const AndroidBuilder();\n // 定義編譯aar的方法\n Future buildAar({\n @required FlutterProject project,\n @required Set androidBuildInfo,\n @required String target,\n @required String outputDirectoryPath,\n @required String buildNumber,\n });\n\n // 定義編譯apk的方法\n Future buildApk({\n @required FlutterProject project,\n @required AndroidBuildInfo androidBuildInfo,\n @required String target,\n });\n\n // 定義編譯aab的方法\n Future buildAab({\n @required FlutterProject project,\n @required AndroidBuildInfo androidBuildInfo,\n @required String target,\n bool validateDeferredComponents = true,\n bool deferredComponentsEnabled = false,\n });\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以我們繼續去看 AndroidGradleBuilder 實現類(","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"packages/flutter_tools/lib/src/android/gradle.dart","attrs":{}}],"attrs":{}},{"type":"text","text":")的 buildApk 方法,如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"class AndroidGradleBuilder implements AndroidBuilder {\n AndroidGradleBuilder({\n ......\n }) : ......;\n ......\n //1、編譯 apk 的方法\n @override\n Future buildApk({\n @required FlutterProject project,\n @required AndroidBuildInfo androidBuildInfo,\n @required String target,\n }) async {\n //2、調用\n await buildGradleApp(\n project: project,\n androidBuildInfo: androidBuildInfo,\n target: target,\n isBuildingBundle: false,\n localGradleErrors: gradleErrors,\n );\n }\n ......\n //3、真的編譯\n Future buildGradleApp({\n @required FlutterProject project, //FlutterProject.current()\n @required AndroidBuildInfo androidBuildInfo, //build configuration\n @required String target, //dart代碼入口,缺省lib/main.dart\n @required bool isBuildingBundle, //是aab還是apk,默認false則apk\n @required List localGradleErrors,\n bool shouldBuildPluginAsAar = false, //是不是將插件編譯爲aar\n bool validateDeferredComponents = true,\n bool deferredComponentsEnabled = false,\n int retries = 1,\n }) async {\n //4、檢查支持的android版本,獲取android編譯產物目錄,即gradle中配置的build產物目錄,默認爲項目根目錄下的build目錄\n if (!project.android.isSupportedVersion) {\n _exitWithUnsupportedProjectMessage(_usage, _logger.terminal);\n }\n final Directory buildDirectory = project.android.buildDirectory;\n //5、讀取安卓相關屬性文件判斷是否使用androidx,然後發送編譯事件參數\n final bool usesAndroidX = isAppUsingAndroidX(project.android.hostAppGradleRoot);\n if (usesAndroidX) {\n BuildEvent('app-using-android-x', flutterUsage: _usage).send();\n } else if (!usesAndroidX) {\n BuildEvent('app-not-using-android-x', flutterUsage: _usage).send();\n ......\n }\n //6、更新安卓項目中local.properties中的versionName和versionCode值,值來自於public.yaml文件配置\n updateLocalProperties(project: project, buildInfo: androidBuildInfo.buildInfo);\n //7、編譯aar的話就走buildPluginsAsAar方法進行\n if (shouldBuildPluginAsAar) {\n // Create a settings.gradle that doesn't import the plugins as subprojects.\n createSettingsAarGradle(project.android.hostAppGradleRoot, _logger);\n await buildPluginsAsAar(\n project,\n androidBuildInfo,\n buildDirectory: buildDirectory.childDirectory('app'),\n );\n }\n //8、獲取編譯apk或者aab對應的標準安卓task name,構建參數等信息,也就是gradle命令後面一堆的參數構造\n final BuildInfo buildInfo = androidBuildInfo.buildInfo;\n final String assembleTask = isBuildingBundle\n ? getBundleTaskFor(buildInfo)\n : getAssembleTaskFor(buildInfo);\n ......\n final List command = [\n _gradleUtils.getExecutable(project),\n ];\n ......\n //9、依據條件追加command的一堆參數,譬如-Psplit-per-abi=true、-Pverbose=true、--no-daemon等\n ......\n try {\n exitCode = await _processUtils.stream(\n command,\n workingDirectory: project.android.hostAppGradleRoot.path,\n allowReentrantFlutter: true,\n environment: {\n if (javaPath != null)\n 'JAVA_HOME': javaPath,\n },\n mapFunction: consumeLog,\n );\n } on ProcessException catch (exception) {\n ......\n } finally {\n status.stop();\n }\n ......\n }\n ......\n}\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"哈哈,真相了,這下配合","attrs":{}},{"type":"link","attrs":{"href":"https://yanbober.blog.csdn.net/article/details/118758871","title":"","type":null},"content":[{"type":"text","text":"《Flutter Android 工程結構及應用層編譯源碼深入分析》","attrs":{}}]},{"type":"text","text":"一文首尾呼應後你應該徹底明白 Flutter android apk 是怎麼編譯的流程!","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"總結","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"現在我們結合","attrs":{}},{"type":"link","attrs":{"href":"https://yanbober.blog.csdn.net/article/details/118758871","title":"","type":null},"content":[{"type":"text","text":"《Flutter Android 工程結構及應用層編譯源碼深入分析》","attrs":{}}]},{"type":"text","text":"和這篇進行關聯總結,可以總結出執行","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"flutter build apk","attrs":{}}],"attrs":{}},{"type":"text","text":"命令背後的大致主流程如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/fa/fadfe27ab7b25f488c40391dbabd8410.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"既然執行","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"flutter build apk","attrs":{}}],"attrs":{}},{"type":"text","text":"命令你都搞明白了,那麼其他 flutter 相關的任何命令你是否也可以自己舉一反三進行分析學習,本質都一樣哈。由於我這裏時間有限,所以對於","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"flutter pub get","attrs":{}}],"attrs":{}},{"type":"text","text":"、","attrs":{}},{"type":"codeinline","content":[{"type":"text","text":"flutter doctor","attrs":{}}],"attrs":{}},{"type":"text","text":"等其他命令不再做詳細分析。","attrs":{}}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章