开篇 当我们创建一个Flutter App项目后,在当前项目路径下运行命令flutter run,就可以编译生成一个APK,并且将APK安装到模拟器中并启动。那么Flutter究竟是如何编译Dart资源的?又是如何将Dart资源放入到APK中?
接下来让我们慢慢跟着代码来分析flutter run 命令的执行过程。
flutter命令 在Mac上,flutter命令是指 ${flutterSdk}/bin/flutter 这个shell程序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ... update逻辑 ... PROG_NAME="$(path_uri "$(follow_links "$BASH_SOURCE")")" BIN_DIR="$(cd "${PROG_NAME%/*}" ; pwd -P)" export FLUTTER_ROOT="$(cd "${BIN_DIR}/.." ; pwd -P)" FLUTTER_TOOLS_DIR="$FLUTTER_ROOT/packages/flutter_tools" SNAPSHOT_PATH="$FLUTTER_ROOT/bin/cache/flutter_tools.snapshot" STAMP_PATH="$FLUTTER_ROOT/bin/cache/flutter_tools.stamp" SCRIPT_PATH="$FLUTTER_TOOLS_DIR/bin/flutter_tools.dart" DART_SDK_PATH="$FLUTTER_ROOT/bin/cache/dart-sdk" DART="$DART_SDK_PATH/bin/dart" PUB="$DART_SDK_PATH/bin/pub" // 实际上抛除更新逻辑,flutter的这个shell就是直接执行了dart命令 // $FLUTTER_TOOL_ARGS 这个参数可以无视 // $SNAPSHOT_PATH 这个指的就是$FLUTTER_ROOT/bin/cache/flutter_tools.snapshot这个snapshot // $@ 这个就是flutter 后面跟的参数 "$DART" $FLUTTER_TOOL_ARGS "$SNAPSHOT_PATH" "$@"
我们可以看到,实际上flutter run 命令,执行的就是 dart flutter_tools.snapshot run
flutter_toools.snapshot实际上就是${flutterSdk}/packages/flutter_tools这个项目编译生成的snapshot文件。所以flutter run 实际上就是使用dart来执行了这个dart项目的main方法,run就是参数。
我们可以将上面shell中的snapshot地址修改成本地flutter_tools项目地址,这样运行flutter命令走的就是本地项目代码,可以用于调试修改源码。
1 2 3 将这一行 "$DART" $FLUTTER_TOOL_ARGS "$SNAPSHOT_PATH" "$@" 修改成 "$DART" $FLUTTER_TOOL_ARGS "$FLUTTER_ROOT/packages/flutter_tools/bin/flutter_tools.dart" "$@"
执行main方法 flutter_tools项目的main方法定义在 bin/flutter_tools.dart中。
1 2 3 4 5 6 7 8 void main(List <String > args) { if (args != null ) { for (String arg in args) { print ('参数 == $arg ' ); } } executable.main(args); }
创建所有命令的对应对象 再看看 lib/executable.dart,在这里会预先创建好每一种命令对应的对象,然后后面会根据解析的参数来选择指定的命令对象来执行。
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 Future<Null > main(List <String > args) async { ... await runner.run(args, <FlutterCommand>[ AnalyzeCommand(verboseHelp: verboseHelp), AttachCommand(verboseHelp: verboseHelp), BuildCommand(verboseHelp: verboseHelp), ChannelCommand(verboseHelp: verboseHelp), CleanCommand(), ConfigCommand(verboseHelp: verboseHelp), CreateCommand(), DaemonCommand(hidden: !verboseHelp), DevicesCommand(), DoctorCommand(verbose: verbose), DriveCommand(), EmulatorsCommand(), FormatCommand(), FuchsiaReloadCommand(), IdeConfigCommand(hidden: !verboseHelp), InjectPluginsCommand(hidden: !verboseHelp), InstallCommand(), LogsCommand(), MakeHostAppEditableCommand(), PackagesCommand(), PrecacheCommand(), RunCommand(verboseHelp: verboseHelp), ScreenshotCommand(), ShellCompletionCommand(), StopCommand(), TestCommand(verboseHelp: verboseHelp), TraceCommand(), UpdatePackagesCommand(hidden: !verboseHelp), UpgradeCommand(), ], verbose: verbose, muteCommandLogging: muteCommandLogging, verboseHelp: verboseHelp); }
执行参数中指定的命令对象 在runner.run中会通过runZoned创建一个新的分区,在新分区中运行,最终会在command_runner.dart的runCommand方法中解析参数,选定指定的命令对象来执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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; commandString += " ${argResults.name} " ; ... } ... return (await command.run()) as T; }
执行Run命令 上面的command.run方法实际上执行的是其父类的run方法,最终会执行到runCommand方法,该方法由子类实现。所以我们主要关注RunCommand.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 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 @override Future<FlutterCommandResult> runCommand() async { Cache.releaseLockEarly(); final bool hotMode = shouldUseHotMode(); ... final List <FlutterDevice> flutterDevices = devices.map((Device device) { return FlutterDevice( device, trackWidgetCreation: argResults['track-widget-creation' ], dillOutputPath: argResults['output-dill' ], fileSystemRoots: argResults['filesystem-root' ], fileSystemScheme: argResults['filesystem-scheme' ], ); }).toList(); ResidentRunner runner; final String applicationBinaryPath = argResults['use-application-binary' ]; if (hotMode) { runner = HotRunner( flutterDevices, target: targetFile, debuggingOptions: _createDebuggingOptions(), benchmarkMode: argResults['benchmark' ], applicationBinary: applicationBinaryPath == null ? null : fs.file(applicationBinaryPath), projectRootPath: argResults['project-root' ], packagesFilePath: globalResults['packages' ], dillOutputPath: argResults['output-dill' ], stayResident: stayResident, ipv6: ipv6, ); } else { runner = ColdRunner( flutterDevices, target: targetFile, debuggingOptions: _createDebuggingOptions(), traceStartup: traceStartup, applicationBinary: applicationBinaryPath == null ? null : fs.file(applicationBinaryPath), stayResident: stayResident, ipv6: ipv6, ); } ... final int result = await runner.run( appStartedCompleter: appStartedTimeRecorder, route: route, shouldBuild: !runningWithPrebuiltApplication && argResults['build' ], ); if (result != 0 ) throwToolExit(null , exitCode: result); return FlutterCommandResult( ExitStatus.success, timingLabelParts: <String >[ hotMode ? 'hot' : 'cold' , getModeName(getBuildMode()), devices.length == 1 ? getNameForTargetPlatform(await devices[0 ].targetPlatform) : 'multiple' , devices.length == 1 && await devices[0 ].isLocalEmulator ? 'emulator' : null ], endTimeOverride: appStartedTime, ); }
热加载模式运行 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 @override Future<int > run({ Completer<DebugConnectionInfo> connectionInfoCompleter, Completer<void > appStartedCompleter, String route, bool shouldBuild = true }) async { ... if (!await _refreshDartDependencies()) { return 1 ; } firstBuildTime = DateTime .now(); for (FlutterDevice device in flutterDevices) { final int result = await device.runHot( hotRunner: this , route: route, shouldBuild: shouldBuild, ); if (result != 0 ) { return result; } } return attach( connectionInfoCompleter: connectionInfoCompleter, appStartedCompleter: appStartedCompleter ); }
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 Future<int > runHot({ HotRunner hotRunner, String route, bool shouldBuild, }) async { final bool prebuiltMode = hotRunner.applicationBinary != null ; final String modeName = hotRunner.debuggingOptions.buildInfo.modeName; final TargetPlatform targetPlatform = await device.targetPlatform; package = await getApplicationPackageForPlatform( targetPlatform, applicationBinary: hotRunner.applicationBinary ); ... final bool hasDirtyDependencies = hotRunner.hasDirtyDependencies(this ); final Future<LaunchResult> futureResult = device.startApp( package, mainPath: hotRunner.mainPath, debuggingOptions: hotRunner.debuggingOptions, platformArgs: platformArgs, route: route, prebuiltApplication: prebuiltMode, applicationNeedsRebuild: shouldBuild || hasDirtyDependencies, usesTerminalUi: hotRunner.usesTerminalUI, ipv6: hotRunner.ipv6, ); final LaunchResult result = await futureResult; if (!result.started) { printError('Error launching application on ${device.name} .' ); await stopEchoingDeviceLog(); return 2 ; } observatoryUris = <Uri >[result.observatoryUri]; return 0 ; }
创建应用对象 具体的编译运行逻辑根据不同平台设备的特性不同,由不同的对象去执行
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 Future<ApplicationPackage> getApplicationPackageForPlatform( TargetPlatform platform, {File applicationBinary}) async { switch (platform) { case TargetPlatform.android_arm: case TargetPlatform.android_arm64: case TargetPlatform.android_x64: case TargetPlatform.android_x86: return applicationBinary == null ? await AndroidApk.fromAndroidProject((await FlutterProject.current()).android) : AndroidApk.fromApk(applicationBinary); case TargetPlatform.ios: return applicationBinary == null ? IOSApp.fromIosProject((await FlutterProject.current()).ios) : IOSApp.fromPrebuiltApp(applicationBinary); case TargetPlatform.tester: return FlutterTesterApp.fromCurrentDirectory(); case TargetPlatform.darwin_x64: case TargetPlatform.linux_x64: case TargetPlatform.windows_x64: case TargetPlatform.fuchsia: return null ; } assert (platform != null ); return null ; }
创建Android应用对象 获取到Apk文件以及包名和启动的Activity名字来构建一个AndroidApk对象返回。
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 static Future<AndroidApk> fromAndroidProject(AndroidProject androidProject) async { File apkFile; if (androidProject.isUsingGradle) { apkFile = await getGradleAppOut(androidProject); if (apkFile.existsSync()) { return AndroidApk.fromApk(apkFile); } } else { apkFile = fs.file(fs.path.join(getAndroidBuildDirectory(), 'app.apk' )); } final File manifest = androidProject.appManifestFile; if (!manifest.existsSync()) return null ; final String manifestString = manifest.readAsStringSync(); final xml.XmlDocument document = xml.parse(manifestString); final Iterable <xml.XmlElement> manifests = document .findElements('manifest' ); if (manifests.isEmpty) return null ; final String packageId = manifests.first.getAttribute('package' ); String launchActivity; for (xml.XmlElement category in document .findAllElements('category' )) { if (category.getAttribute('android:name' ) == 'android.intent.category.LAUNCHER' ) { final xml.XmlElement activity = category.parent.parent; final String enabled = activity.getAttribute('android:enabled' ); if (enabled == null || enabled == 'true' ) { final String activityName = activity.getAttribute('android:name' ); launchActivity = '$packageId /$activityName ' ; break ; } } } if (packageId == null || launchActivity == null ) return null ; return AndroidApk( id: packageId, file: apkFile, launchActivity: launchActivity ); }
Initializing gradle 在第一次执行gradlew命令时,会初始化gradlew命令,也就是获取gradlew命令全路径,并且通过 gradlew -v 来验证gradlew可以正常运行。
1 2 3 4 5 6 7 8 9 10 11 Future<String > _initializeGradle(FlutterProject project) async { final Directory android = project.android.hostAppGradleRoot; final Status status = logger.startProgress('Initializing gradle...' , expectSlowOperation: true ); String gradle = _locateGradlewExecutable(android); ... await runCheckedAsync(<String >[gradle, '-v' ], environment: _gradleEnv); status.stop(); return gradle; }
Resolving dependencies 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 Future<GradleProject> _readGradleProject() async { final FlutterProject flutterProject = await FlutterProject.current(); final String gradle = await _ensureGradle(flutterProject); updateLocalProperties(project: flutterProject); final Status status = logger.startProgress('Resolving dependencies...' , expectSlowOperation: true ); GradleProject project; try { final RunResult runResult = await runCheckedAsync( <String >[gradle, 'app:properties' ], workingDirectory: flutterProject.android.hostAppGradleRoot.path, environment: _gradleEnv, ); final String properties = runResult.stdout.trim(); project = GradleProject.fromAppProperties(properties); } catch (exception) { ... } status.stop(); return project; }
启动应用 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 @override Future<LaunchResult> startApp( ApplicationPackage package, { String mainPath, String route, DebuggingOptions debuggingOptions, Map <String , dynamic > platformArgs, bool prebuiltApplication = false , bool applicationNeedsRebuild = false , bool usesTerminalUi = true , bool ipv6 = false , }) async { ... if (!prebuiltApplication) { printTrace('Building APK' ); final FlutterProject project = await FlutterProject.current(); await buildApk( project: project, target: mainPath, buildInfo: buildInfo, ); package = await AndroidApk.fromAndroidProject(project.android); } printTrace("Stopping app '${package.name} ' on $name ." ); await stopApp(package); if (!await _installLatestApp(package)) return LaunchResult.failed(); ... List <String > cmd; cmd = adbCommandForDevice(<String >[ 'shell' , 'am' , 'start' , '-a' , 'android.intent.action.RUN' , '-f' , '0x20000000' , '--ez' , 'enable-background-compilation' , 'true' , '--ez' , 'enable-dart-profiling' , 'true' , ]); ... cmd.add(apk.launchActivity); final String result = (await runCheckedAsync(cmd)).stdout; if (result.contains('Error: ' )) { printError(result.trim()); return LaunchResult.failed(); } ... try { Uri observatoryUri; if (debuggingOptions.buildInfo.isDebug || debuggingOptions.buildInfo.isProfile) { observatoryUri = await observatoryDiscovery.uri; } return LaunchResult.succeeded(observatoryUri: observatoryUri); } catch (error) { printError('Error waiting for a debug connection: $error ' ); return LaunchResult.failed(); } finally { await observatoryDiscovery.cancel(); } }
构建Android Apk assembleTask 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 Future<Null > _buildGradleProjectV2( FlutterProject flutterProject, String gradle, BuildInfo buildInfo, String target) async { final GradleProject project = await _gradleProject(); final String assembleTask = project.assembleTaskFor(buildInfo); ... final Status status = logger.startProgress( "Gradle task '$assembleTask '..." , expectSlowOperation: true , multilineOutput: true , ); final String gradlePath = fs.file(gradle).absolute.path; final List <String > command = <String >[gradlePath]; ... command.add(assembleTask); final int exitCode = await runCommandAndStreamOutput( command, workingDirectory: flutterProject.android.hostAppGradleRoot.path, allowReentrantFlutter: true , environment: _gradleEnv, filter: logger.isVerbose ? null : ndkMessageFilter, ); status.stop(); if (exitCode != 0 ) throwToolExit('Gradle task $assembleTask failed with exit code $exitCode ' , exitCode: exitCode); final File apkFile = _findApkFile(project, buildInfo); ... apkFile.copySync(project.apkDirectory.childFile('app.apk' ).path); printTrace('calculateSha: ${project.apkDirectory} /app.apk' ); final File apkShaFile = project.apkDirectory.childFile('app.apk.sha1' ); apkShaFile.writeAsStringSync(calculateSha(apkFile)); ... }
flutter.gradle 我们可以看到在flutter_tools中,只是简单的执行了一下assemble任务,并没有对Dart资源进行编译和处理,那么具体的逻辑,对于Android项目来说,是在gradle/flutter.gradle中。
在flutter.gradle中,创建了flutterPlugin。
1 apply plugin: FlutterPlugin
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 69 70 71 72 73 74 75 76 77 78 79 @Override void apply(Project project) { ... flutterExecutable = Paths.get(flutterRoot.absolutePath, "bin" , flutterExecutableName).toFile(); if (project.hasProperty('localEngineOut' )) { ... } else { Path baseEnginePath = Paths.get(flutterRoot.absolutePath, "bin" , "cache" , "artifacts" , "engine" ) String targetArch = 'arm' if (project.hasProperty('target-platform' ) && project.property('target-platform' ) == 'android-arm64' ) { targetArch = 'arm64' } debugFlutterJar = baseEnginePath.resolve("android-${targetArch}" ).resolve("flutter.jar" ).toFile() profileFlutterJar = baseEnginePath.resolve("android-${targetArch}-profile" ).resolve("flutter.jar" ).toFile() releaseFlutterJar = baseEnginePath.resolve("android-${targetArch}-release" ).resolve("flutter.jar" ).toFile() dynamicProfileFlutterJar = baseEnginePath.resolve("android-${targetArch}-dynamic-profile" ).resolve("flutter.jar" ).toFile() dynamicReleaseFlutterJar = baseEnginePath.resolve("android-${targetArch}-dynamic-release" ).resolve("flutter.jar" ).toFile() if (!debugFlutterJar.isFile()) { project.exec { executable flutterExecutable.absolutePath args "--suppress-analytics" args "precache" } if (!debugFlutterJar.isFile()) { throw new GradleException("Unable to find flutter.jar in SDK: ${debugFlutterJar}" ) } } flutterX86Jar = project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter/flutter-x86.jar" ) Task flutterX86JarTask = project.tasks.create("${flutterBuildPrefix}X86Jar" , Jar) { destinationDir flutterX86Jar.parentFile archiveName flutterX86Jar.name from("${flutterRoot}/bin/cache/artifacts/engine/android-x86/libflutter.so" ) { into "lib/x86" } from("${flutterRoot}/bin/cache/artifacts/engine/android-x64/libflutter.so" ) { into "lib/x86_64" } } project.android.buildTypes.each { addFlutterJarApiDependency(project, it, flutterX86JarTask) } project.android.buildTypes.whenObjectAdded { addFlutterJarApiDependency(project, it, flutterX86JarTask) } } project.extensions.create("flutter" , FlutterExtension) project.afterEvaluate this .&addFlutterTask File pluginsFile = new File(project.projectDir.parentFile.parentFile, '.flutter-plugins' ) Properties plugins = readPropertiesIfExist(pluginsFile) plugins.each { name, _ -> def pluginProject = project.rootProject.findProject(":$name" ) if (pluginProject != null ) { project.dependencies { if (project.getConfigurations().findByName("implementation" )) { implementation pluginProject } else { compile pluginProject } } pluginProject.afterEvaluate this .&addFlutterJarCompileOnlyDependency } else { project.logger.error("Plugin project :$name not found. Please update settings.gradle." ) } } }
在flutterPlugin中创建了flutterTask
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 private void addFlutterTask(Project project) { String target = project.flutter.target if (target == null ) { target = 'lib/main.dart' } if (project.hasProperty('target' )) { target = project.property('target' ) } ... def addFlutterDeps = { variant -> String flutterBuildMode = buildModeFor(variant.buildType) if (flutterBuildMode == 'debug' && project.tasks.findByName('${flutterBuildPrefix}X86Jar' )) { Task task = project.tasks.findByName("compile${variant.name.capitalize()}JavaWithJavac" ) if (task) { task.dependsOn project.flutterBuildX86Jar } task = project.tasks.findByName("compile${variant.name.capitalize()}Kotlin" ) if (task) { task.dependsOn project.flutterBuildX86Jar } } FlutterTask flutterTask = project.tasks.create(name: "${flutterBuildPrefix}${variant.name.capitalize()}" , type: FlutterTask) { flutterRoot this .flutterRoot flutterExecutable this .flutterExecutable buildMode flutterBuildMode localEngine this .localEngine localEngineSrcPath this .localEngineSrcPath targetPath target verbose verboseValue fileSystemRoots fileSystemRootsValue fileSystemScheme fileSystemSchemeValue trackWidgetCreation trackWidgetCreationValue compilationTraceFilePath compilationTraceFilePathValue buildHotUpdate buildHotUpdateValue buildSharedLibrary buildSharedLibraryValue targetPlatform targetPlatformValue sourceDir project.file(project.flutter.source) intermediateDir project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter/${variant.name}" ) extraFrontEndOptions extraFrontEndOptionsValue extraGenSnapshotOptions extraGenSnapshotOptionsValue } Task copyFlutterAssetsTask = project.tasks.create(name: "copyFlutterAssets${variant.name.capitalize()}" , type: Copy) { dependsOn flutterTask dependsOn variant.mergeAssets dependsOn "clean${variant.mergeAssets.name.capitalize()}" into variant.mergeAssets.outputDir with flutterTask.assets } variant.outputs[0 ].processResources.dependsOn(copyFlutterAssetsTask) } if (project.android.hasProperty("applicationVariants" )) { project.android.applicationVariants.all addFlutterDeps } else { project.android.libraryVariants.all addFlutterDeps } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 CopySpec getAssets() { return project.copySpec { from "${intermediateDir}" include "flutter_assets/**" if (buildMode != 'debug' || compilationTraceFilePath) { if (buildSharedLibrary) { include "app.so" } else { include "vm_snapshot_data" include "vm_snapshot_instr" include "isolate_snapshot_data" include "isolate_snapshot_instr" } } } }
执行flutterTask,编译dart资源
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 void buildBundle() { if (!sourceDir.isDirectory()) { throw new GradleException("Invalid Flutter source directory: ${sourceDir}" ) } intermediateDir.mkdirs() if (buildMode == "profile" || buildMode == "release" ) { ... } project.exec { executable flutterExecutable.absolutePath workingDir sourceDir if (localEngine != null ) { args "--local-engine" , localEngine args "--local-engine-src-path" , localEngineSrcPath } args "build" , "bundle" args "--suppress-analytics" args "--target" , targetPath ... } }
编译dart代码 在flutterTask中会执行flutter build bundle 来编译dart代码,具体build bundle是怎样编译的我们放在下一篇中详细讲解。
总之flutterTask通过执行flutter build bundle编译dart,将生成的dart资源放在build目录中。然后copyFlutterAssetsTask执行,将build目录中的dart资源复制到android的build/intermediates中,最后在processResources任务中和Android的assets资源一起被处理。