Android编译源码流程
settings.gradle
配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 include ':app' 2、获取flutter.sdk的值,也就是你本地flutter SDK安装目录 / def localPropertiesFile = new File(rootProject.projectDir, "local.properties" ) def properties = new Properties() assert localPropertiesFile.exists()localPropertiesFile.withReader("UTF-8" ) { reader -> properties.load(reader) } def flutterSdkPath = properties.getProperty("flutter.sdk" ) assert flutterSdkPath != null , "flutter.sdk not set in local.properties" apply from: "$flutterSdkPath /packages/flutter_tools/gradle/app_plugin_loader.gradle"
查看app_plugin_loader.gradle
文件
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 import groovy.json.JsonSlurperdef flutterProjectRoot = rootProject.projectDir.parentFile def pluginsFile = new File(flutterProjectRoot, '.flutter-plugins-dependencies' ) if (!pluginsFile.exists()) { return } 2、简单校验json内容字段的类型合法性。 / def object = new JsonSlurper().parseText(pluginsFile.text) assert object instanceof Map assert object.plugins instanceof Map assert object.plugins.android instanceof List object.plugins.android.each { androidPlugin -> assert androidPlugin.name instanceof String assert androidPlugin.path instanceof String def pluginDirectory = new File(androidPlugin.path, 'android' ) assert pluginDirectory.exists() include ":${androidPlugin.name} " project(":${androidPlugin.name} " ).projectDir = pluginDirectory }
我们在flutter项目中配置不同的plugin、package,会在项目根目录生成.flutter-plugins-dependencies
,app_plugin_loader.gradle
会将其中的Android部分库添加进来,用Android Studio打开android文件夹,会自动加载这些库。
在android文件夹下的build.gradle
中:
1 2 3 4 5 6 7 8 //......省略无关紧要的常见配置 // 看到了吧,他将所有 android 依赖的构建产物挪到了根目录下的 build 中,所有产物都在那儿 rootProject.buildDir = '../build' subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" project.evaluationDependsOn(':app') //运行其他配置之前,先运行app依赖 }
然后看app模块下的build.gradle
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 def localProperties = new Properties()def localPropertiesFile = rootProject.file('local.properties' )if (localPropertiesFile.exists()) { localPropertiesFile.withReader('UTF-8' ) { reader -> localProperties.load(reader) } } def flutterRoot = localProperties.getProperty('flutter.sdk' )if (flutterRoot == null ) { throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file." ) } def flutterVersionCode = localProperties.getProperty('flutter.versionCode' )if (flutterVersionCode == null ) { flutterVersionCode = '1' } def flutterVersionName = localProperties.getProperty('flutter.versionName' )if (flutterVersionName == null ) { flutterVersionName = '1.0' } apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { compileSdkVersion 30 sourceSets { main.java.srcDirs += 'src/main/kotlin' } defaultConfig { applicationId "cn.yan.f1" minSdkVersion 21 targetSdkVersion 30 versionCode flutterVersionCode.toInteger() versionName flutterVersionName } } flutter { source '../..' }
看看重点1flutter.gradle
文件,它实际上运行了flutter.groovy
这个文件
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 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 buildscript { repositories { google() jcenter() } dependencies { classpath 'com.android.tools.build:gradle:4.1.0' } } android { compileOptions { sourceCompatibility 1.8 targetCompatibility 1.8 } } apply plugin: FlutterPlugin class FlutterPlugin implements Plugin <Project> { @Override void apply(Project project) { this .project = project String hostedRepository = System.env.FLUTTER_STORAGE_BASE_URL ?: DEFAULT_MAVEN_HOST String repository = useLocalEngine() ? project.property('local-engine-repo' ) : "$hostedRepository/download.flutter.io" project.rootProject.allprojects { repositories { maven { url repository } } } project.extensions.create("flutter" , FlutterExtension) this .addFlutterTasks(project) if (shouldSplitPerAbi()) { project.android { splits { abi { enable true reset() universalApk false } } } } if (project.hasProperty('deferred-component-names' )) { String[] componentNames = project.property('deferred-component-names' ).split(',' ).collect {":${it}" } project.android { dynamicFeatures = componentNames } } getTargetPlatforms().each { targetArch -> String abiValue = PLATFORM_ARCH_MAP[targetArch] project.android { if (shouldSplitPerAbi()) { splits { abi { include abiValue } } } } } String flutterRootPath = resolveProperty("flutter.sdk" , System.env.FLUTTER_ROOT) if (flutterRootPath == null ) { throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file or with a FLUTTER_ROOT environment variable." ) } flutterRoot = project.file(flutterRootPath) if (!flutterRoot.isDirectory()) { throw new GradleException("flutter.sdk must point to the Flutter SDK directory" ) } engineVersion = useLocalEngine() ? "+" : "1.0.0-" + Paths.get(flutterRoot.absolutePath, "bin" , "internal" , "engine.version" ).toFile().text.trim() String flutterExecutableName = Os.isFamily(Os.FAMILY_WINDOWS) ? "flutter.bat" : "flutter" flutterExecutable = Paths.get(flutterRoot.absolutePath, "bin" , flutterExecutableName).toFile(); String flutterProguardRules = Paths.get(flutterRoot.absolutePath, "packages" , "flutter_tools" , "gradle" , "flutter_proguard_rules.pro" ) project.android.buildTypes { profile { initWith debug if (it.hasProperty("matchingFallbacks" )) { matchingFallbacks = ["debug" , "release" ] } } } project.android.buildTypes.all this .&addFlutterDependencies } } class FlutterExtension { String source String target }
接下来看看addFluterTasks方法,这是整个编译的重点:
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 private void addFlutterTasks(Project project) { if (project.state.failure) { return } String[] fileSystemRootsValue = null if (project.hasProperty('filesystem-roots' )) { fileSystemRootsValue = project.property('filesystem-roots' ).split('\\|' ) } String fileSystemSchemeValue = null if (project.hasProperty('filesystem-scheme' )) { fileSystemSchemeValue = project.property('filesystem-scheme' ) } Boolean trackWidgetCreationValue = true if (project.hasProperty('track-widget-creation' )) { trackWidgetCreationValue = project.property('track-widget-creation' ).toBoolean() } String extraFrontEndOptionsValue = null if (project.hasProperty('extra-front-end-options' )) { extraFrontEndOptionsValue = project.property('extra-front-end-options' ) } String extraGenSnapshotOptionsValue = null if (project.hasProperty('extra-gen-snapshot-options' )) { extraGenSnapshotOptionsValue = project.property('extra-gen-snapshot-options' ) } String splitDebugInfoValue = null if (project.hasProperty('split-debug-info' )) { splitDebugInfoValue = project.property('split-debug-info' ) } Boolean dartObfuscationValue = false if (project.hasProperty('dart-obfuscation' )) { dartObfuscationValue = project.property('dart-obfuscation' ).toBoolean(); } Boolean treeShakeIconsOptionsValue = false if (project.hasProperty('tree-shake-icons' )) { treeShakeIconsOptionsValue = project.property('tree-shake-icons' ).toBoolean() } String dartDefinesValue = null if (project.hasProperty('dart-defines' )) { dartDefinesValue = project.property('dart-defines' ) } String bundleSkSLPathValue; if (project.hasProperty('bundle-sksl-path' )) { bundleSkSLPathValue = project.property('bundle-sksl-path' ) } String performanceMeasurementFileValue; if (project.hasProperty('performance-measurement-file' )) { performanceMeasurementFileValue = project.property('performance-measurement-file' ) } String codeSizeDirectoryValue; if (project.hasProperty('code-size-directory' )) { codeSizeDirectoryValue = project.property('code-size-directory' ) } Boolean deferredComponentsValue = false if (project.hasProperty('deferred-components' )) { deferredComponentsValue = project.property('deferred-components' ).toBoolean() } Boolean validateDeferredComponentsValue = true if (project.hasProperty('validate-deferred-components' )) { validateDeferredComponentsValue = project.property('validate-deferred-components' ).toBoolean() } def targetPlatforms = getTargetPlatforms() ...... }
可以看到,addFlutterTasks 方法的第一部分比较简单,基本都是从 Project 中读取各自配置属性供后续步骤使用。所以我们接着继续看 addFlutterTasks 这个方法步骤 1 之后的部分
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 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 private void addFlutterTasks(Project project) { def addFlutterDeps = { variant -> if (shouldSplitPerAbi()) { variant.outputs.each { output -> def abiVersionCode = ABI_VERSION.get(output.getFilter(OutputFile.ABI)) if (abiVersionCode != null ) { output.versionCodeOverride = abiVersionCode * 1000 + variant.versionCode } } } String variantBuildMode = buildModeFor(variant.buildType) String taskName = toCammelCase(["compile" , FLUTTER_BUILD_PREFIX, variant.name]) FlutterTask compileTask = project.tasks.create(name: taskName, type: FlutterTask) { flutterRoot this .flutterRoot flutterExecutable this .flutterExecutable buildMode variantBuildMode localEngine this .localEngine localEngineSrcPath this .localEngineSrcPath targetPath getFlutterTarget() verbose isVerbose() fastStart isFastStart() fileSystemRoots fileSystemRootsValue fileSystemScheme fileSystemSchemeValue trackWidgetCreation trackWidgetCreationValue targetPlatformValues = targetPlatforms sourceDir getFlutterSourceDirectory() intermediateDir project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter/${variant.name}/" ) extraFrontEndOptions extraFrontEndOptionsValue extraGenSnapshotOptions extraGenSnapshotOptionsValue splitDebugInfo splitDebugInfoValue treeShakeIcons treeShakeIconsOptionsValue dartObfuscation dartObfuscationValue dartDefines dartDefinesValue bundleSkSLPath bundleSkSLPathValue performanceMeasurementFile performanceMeasurementFileValue codeSizeDirectory codeSizeDirectoryValue deferredComponents deferredComponentsValue validateDeferredComponents validateDeferredComponentsValue doLast { project.exec { if (Os.isFamily(Os.FAMILY_WINDOWS)) { commandLine('cmd' , '/c' , "attrib -r ${assetsDirectory}/* /s" ) } else { commandLine('chmod' , '-R' , 'u+w' , assetsDirectory) } } } } 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 }) boolean isBuildingAar = project.hasProperty('is-plugin' ) Task packageAssets = project.tasks.findByPath(":flutter:package${variant.name.capitalize()}Assets" ) Task cleanPackageAssets = project.tasks.findByPath(":flutter:cleanPackage${variant.name.capitalize()}Assets" ) boolean isUsedAsSubproject = packageAssets && cleanPackageAssets && !isBuildingAar Task copyFlutterAssetsTask = project.tasks.create( name: "copyFlutterAssets${variant.name.capitalize()}" , type: Copy, ) { dependsOn compileTask with compileTask.assets if (isUsedAsSubproject) { dependsOn packageAssets dependsOn cleanPackageAssets into packageAssets.outputDir return } def mergeAssets = variant.hasProperty("mergeAssetsProvider" ) ? variant.mergeAssetsProvider.get() : variant.mergeAssets dependsOn mergeAssets dependsOn "clean${mergeAssets.name.capitalize()}" mergeAssets.mustRunAfter("clean${mergeAssets.name.capitalize()}" ) into mergeAssets.outputDir } if (!isUsedAsSubproject) { def variantOutput = variant.outputs.first() def processResources = variantOutput.hasProperty("processResourcesProvider" ) ? variantOutput.processResourcesProvider.get() : variantOutput.processResources processResources.dependsOn(copyFlutterAssetsTask) } return copyFlutterAssetsTask } ...... }
以上大部分代码只是为了执行以下脚本时准备配置参数
1 2 3 4 5 6 7 8 9 10 11 12 flutter assemble --no-version-check \ --depfile build/app/intermediates/flutter/release/flutter_build.d \ --output build/app/intermediates/flutter/release/ \ -dTargetFile=lib/main.dart \ -dTargetPlatform=android \ -dBuildMode=release \ -dDartObfuscation=true \ android_aot_bundle_release_android-arm \ android_aot_bundle_release_android-arm64 \ android_aot_bundle_release_android-x86 \ android_aot_bundle_release_android-x64
Flutter SDK下bin/flutter编译命令分析 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 # !/usr/bin/env bash # 1、该命令之后出现的代码,一旦出现了返回值非零,整个脚本就会立即退出,那么就可以避免一些脚本的危险操作。 set -e # 2、清空CDPATH变量值 unset CDPATH # 在Mac上,readlink -f不起作用,因此follow_links一次遍历一个链接的路径,然后遍历cd 进入链接目的地并找出它。 # 返回的文件系统路径必须是Dart的URI解析器可用的格式,因为Dart命令行工具将其参数视为文件URI,而不是文件名。 # 例如,多个连续的斜杠应该减少为一个斜杠,因为双斜杠表示URI的authority。 function follow_links() ( cd -P "$(dirname -- "$1")" file="$PWD/$(basename -- "$1")" while [[ -h "$file" ]]; do cd -P "$(dirname -- "$file")" file="$(readlink -- "$file")" cd -P "$(dirname -- "$file")" file="$PWD/$(basename -- "$file")" done echo "$file" ) # 这个变量的值就是Flutter SDK根目录下的bin/flutter PROG_NAME="$(follow_links "${BASH_SOURCE[0]}")" BIN_DIR="$(cd "${PROG_NAME%/*}" ; pwd -P)" OS="$(uname -s)" # 平台兼容 if [[ $OS =~ MINGW.* || $OS =~ CYGWIN.* ]]; then exec "${BIN_DIR}/flutter.bat" "$@" fi # 3、source 导入这个shell脚本后执行其内部的shared::execute方法 source "$BIN_DIR/internal/shared.sh" shared::execute "$@"
关注shared.sh
文件
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 # ...... function shared::execute() { # 1、默认FLUTTER_ROOT值为FlutterSDK根路径 export FLUTTER_ROOT="$(cd "${BIN_DIR}/.." ; pwd -P)" # 2、如果存在就先执行bootstrap脚本,默认SDK下面是没有这个文件的,我猜是预留给我们自定义初始化挂载用的。 BOOTSTRAP_PATH="$FLUTTER_ROOT/bin/internal/bootstrap.sh" if [ -f "$BOOTSTRAP_PATH" ]; then source "$BOOTSTRAP_PATH" fi # 3、一堆基于FlutterSDK路径的位置定义 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" # 4、路径文件平台兼容,常规操作,忽略 case "$(uname -s)" in MINGW*) DART="$DART.exe" PUB="$PUB.bat" ;; esac # 5、测试运行脚本的账号是否为超级账号,是的话警告提示,Docker和CI环境不警告。 if [[ "$EUID" == "0" && ! -f /.dockerenv && "$CI" != "true" && "$BOT" != "true" && "$CONTINUOUS_INTEGRATION" != "true" ]]; then >&2 echo " Woah! You appear to be trying to run flutter as root." >&2 echo " We strongly recommend running the flutter tool without superuser privileges." >&2 echo " /" >&2 echo "📎" fi # 6、测试git命令行环境配置是否正常,不正常就抛出错误。 if ! hash git 2>/dev/null; then >&2 echo "Error: Unable to find git in your PATH." exit 1 fi # 7、FlutterSDK是否来自clone 等测试。 if [[ ! -e "$FLUTTER_ROOT/.git" ]]; then >&2 echo "Error: The Flutter directory is not a clone of the GitHub project." >&2 echo " The flutter tool requires Git in order to operate properly;" >&2 echo " to install Flutter, see the instructions at:" >&2 echo " https://flutter.dev/get-started" exit 1 fi # To debug the tool, you can uncomment the following lines to enable checked # mode and set an observatory port: # FLUTTER_TOOL_ARGS="--enable-asserts $FLUTTER_TOOL_ARGS " # FLUTTER_TOOL_ARGS="$FLUTTER_TOOL_ARGS --observe=65432" # 7、日常编译遇到命令lock文件锁住问题就是他,本质该方法就是创建/bin/cache目录并维持锁状态等事情,不是我们关心的重点。 upgrade_flutter 7< "$PROG_NAME" # 8、相关参数值,别问我怎么知道的,问就是自己在源码对应位置echo 输出打印的 # BIN_NAME=flutter、PROG_NAME=FLUTTER_SDK_DIR/bin/flutter # DART=FLUTTER_SDK_DIR/bin/cache/dart-sdk/bin/dart # FLUTTER_TOOLS_DIR=FLUTTER_SDK_DIR/packages/flutter_tools # FLUTTER_TOOL_ARGS=空 # SNAPSHOT_PATH=FLUTTER_SDK_DIR/bin/cache/flutter_tools.snapshot # @=build apk BIN_NAME="$(basename "$PROG_NAME")" case "$BIN_NAME" in flutter*) # FLUTTER_TOOL_ARGS aren't quoted below, because it is meant to be # considered as separate space-separated args. "$DART" --disable-dart-dev --packages="$FLUTTER_TOOLS_DIR/.packages" $FLUTTER_TOOL_ARGS "$SNAPSHOT_PATH" "$@" ;; dart*) "$DART" "$@" ;; *) >&2 echo "Error! Executable name $BIN_NAME not recognized!" exit 1 ;; esac }
可以看到,由于 Flutter SDK 内部内置了 Dart,所以当配置环境变量后 flutter、dart 命令都可以使用了。而我们安装 Flutter SDK 后首先做的事情就是把 SDK 的 bin 目录配置到了环境变量,所以执行的 flutter build apk、flutter upgrade、flutter pub xxx 等命令本质都是走进了上面这些脚本,且 flutter 命令只是对 dart 命令的一个包装,所以执行flutter pub get其实等价于dart pub get。所以假设我们执行flutter build apk命令,本质走到上面脚本最终执行的命令如下:
1 2 3 4 5 FLUTTER_SDK_DIR/bin/cache/dart-sdk/bin/dart \ --disable-dart-dev --packages=FLUTTER_SDK_DIR/packages/flutter_tools/.packages \ FLUTTER_SDK_DIR/bin/cache/flutter_tools.snapshot \ build apk
上面命令行中 FLUTTER_SDK_DIR 代表的就是 Flutter SDK 的根目录,–packages可以理解成是一堆 SDK 相关依赖,FLUTTER_SDK_DIR/bin/cache/flutter_tools.snapshot就是FLUTTER_SDK_DIR/packages/flutter_tools的编译产物。所以,上面其实通过 dart 命令执行flutter_tools.snapshot文件也就是等价于执行flutter_tools.dart的main()方法。因此上面命令继续简化大致如下:
1 2 3 dart --disable-dart-dev --packages=xxx flutter_tools.dart build apk
也就是说,我们执行的任何 flutter 命令,本质都是把参数传递到了FLUTTER_SDK_DIR/packages/flutter_tools/bin/flutter_tools.dart源码的 main 方法中
flutter_tools会执行flutter_tools/lib文件夹中的代码,不同的参数对应commands中不同的文件,比如build对应lib/src/commands/build.dart
参考