Flutter App 安卓编译源码流程

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

下面我们从纯 Flutter 项目的 app 编译安卓端 apk 流程说起。

settings.gradle 源码流程分析


既然是安卓的编译流程,那就先从android/settings.gradle看起,如下:

// 当前 app module
include ‘:app’
/**
  • 1、读取android/local.properties文件内容
  • 2、获取flutter.sdk的值,也就是你本地flutter SDK安装目录
  • 3、gradle 脚本常规操作 apply flutter SDK路径下/packages/flutter_tools/gradle/app_plugin_loader.gradle文件
*/
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”
通过上面步骤我们可以将目光转向你 Flutter SDK 安装目录下的/packages/flutter_tools/gradle/app_plugin_loader.gradle文件,内容如下:
import groovy.json.JsonSlurper
//得到自己新建的 flutter 项目的根路径,因为已经被自己新建的 project apply,所以这里是项目根路径哦
def flutterProjectRoot = rootProject.projectDir.parentFile
//获取自己项目根路径下的.flutter-plugins-dependencies json配置文件
// Note: if this logic is changed, also change the logic in module_plugin_loader.gradle.
def pluginsFile = new File(flutterProjectRoot, ‘.flutter-plugins-dependencies’)
if (!pluginsFile.exists()) {
return
}
/**
  • 1、通过groovy的JsonSlurper解析json文件内容。
  • 2、简单校验json内容字段的类型合法性。
  • 3、把安卓平台依赖的Flutter plugins全部自动include进来
*/
def object = new JsonSlurper().parseText(pluginsFile.text)
assert object instanceof Map
assert object.plugins instanceof Map
assert object.plugins.android instanceof List
// Includes the Flutter plugins that support the Android platform.
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
}

上面的 gradle 脚本很简单,大家看注释即可。为了直观说明问题,这里新建了一个典型 demo 项目,然后其pubspec.yaml文件依赖配置如下:

dependencies:
flutter:
sdk: flutter
dio: ^4.0.0 #来自pub.dev仓库的Flutter Package包
webview_flutter: ^2.0.10 #来自pub.dev仓库的Flutter Plugin包
f_package: #来自自己本地新建的Flutter Package包
path: ./…/f_package
f_plugin: #来自自己本地新建的Flutter Plugin包
path: ./…/f_plugin

接着我们看看这个项目根路径的.flutter-plugins-dependencies文件,如下:

{
“info”:“This is a generated file; do not edit or check into version control.”,
“plugins”:{
“ios”:[
{“name”:“f_plugin”,“path”:“E:\\f_plugin\\”,“dependencies”:[]},
{“name”:“webview_flutter”,“path”:“D:\\software\\flutter\\flutter\\.pub-cache\\hosted\\pub.dartlang.org\\webview_flutter-2.0.10\\”,“dependencies”:[]}
],
“android”:[
{“name”:“f_plugin”,“path”:“E:\\f_plugin\\”,“dependencies”:[]},
{“name”:“webview_flutter”,“path”:“D:\\software\\flutter\\flutter\\.pub-cache\\hosted\\pub.dartlang.org\\webview_flutter-2.0.10\\”,“dependencies”:[]}
],
“macos”:[],
“linux”:[],
“windows”:[],
“web”:[
{“name”:“f_plugin”,“path”:“E:\\f_plugin\\”,“dependencies”:[]}
]
},
“dependencyGraph”:[
{“name”:“f_plugin”,“dependencies”:[]},
{“name”:“webview_flutter”,“dependencies”:[]}
],
“date_created”:“202x-0x-15 21:41:39.225336”,
“version”:“2.2.3”
}

这时候我们回过头去看自己项目android/settings.gradle,在 Gradle 生命周期的初始化阶段(即解析settings.gradle),我们项目的settings.gradle经过apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"处理后自动变成如下伪代码:

include ‘:app’
// 自动通过匹配依赖然后app_plugin_loader.gradle解析生成
//include “😒{androidPlugin.name}”
//project(“😒{androidPlugin.name}”).projectDir = pluginDirectory
include “:f_plugin”
project(“:f_plugin”).projectDir = new File(“E:\\f_plugin\\”, ‘android’)
include “:webview_flutter”
project(“:webview_flutter”).projectDir = new File(“D:\\software\\flutter\\flutter\\.pub-cache\\hosted\\pub.dartlang.org\\webview_flutter-2.0.10\\”, ‘android’)

咋说!是不是一下就恍然大悟了,其实就是“约定大于配置”的软件工程原则,你只管按照规则摆放,本质最后都是我们平时标准 Android 项目那样。

build.gradle源码流程分析


先看项目 android 下根目录的build.gradle,如下:

//…省略无关紧要的常见配置
// 看到了吧,他将所有 android 依赖的构建产物挪到了根目录下的 build 中,所以产物都在那儿
rootProject.buildDir = ‘…/build’
subprojects {
project.buildDir = “{project.name}”project.evaluationDependsOn(‘:app’) //运行其他配置之前,先运行app依赖
}

接着我们看看 app 模块下的build.gradle,如下:

/**
• 1、读取local.properties配置信息。
• 2、获取flutter.sdk路径。
• 3、获取flutter.versionCode值,此值在编译时自动从pubspec.yaml中读取赋值,所以修改版本号请修改yaml。
• 4、获取flutter.versionName值,此值在编译时自动从pubspec.yaml中读取赋值,所以修改版本号请修改yaml。
*/
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’
//重点1:apply 了 flutter SDK 下面的packages/flutter_tools/gradle/flutter.gradle脚本文件
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() //赋值为yaml中读取的值
versionName flutterVersionName //赋值为yaml中读取的值
}
//…省略常规操作,不解释
}
//重点2:一个拓展配置,指定source路径为当前的两级父级,也就是项目根目录
flutter {
source ‘…/…’
}

//…省略常规操作,不解释

下面我们看看上面提到的重点1,也就是 Flutter SDK 中的packages/flutter_tools/gradle/flutter.gradle,我们按照脚本运行时宏观到细节的方式来分析,如下:

//…省略一堆import头文件

/**

  • 常规脚本配置:脚本依赖仓库及依赖的 AGP 版本
  • 如果你自己没有全局配国内maven镜像,修改这里repositories也可以。
  • 如果你项目对于AGP这个版本不兼容,自己修改这里然后兼容也可以。
*/
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath ‘com.android.tools.build:gradle:4.1.0’
}
}
//java8编译配置
android {
compileOptions {
sourceCompatibility 1.8
targetCompatibility 1.8
}
}
//又 apply 了一个插件,只是这个插件源码直接定义在下方
apply plugin: FlutterPlugin
//FlutterPlugin插件实现源码,参考标准插件写法一样,基本语法不解释,这里重点看逻辑。
class FlutterPlugin implements Plugin {
//…
//重点入口!!!!!!
@Override
void apply(Project project) {
this.project = project
//1、配置maven仓库地址,环境变量有配置FLUTTER_STORAGE_BASE_URL就优先用,没就缺省
String hostedRepository = System.env.FLUTTER_STORAGE_BASE_URL ?: DEFAULT_MAVEN_HOST
String repository = useLocalEngine()
 
   “$hostedRepository/download.flutter.io” 
 
project.rootProject.allprojects {
repositories {
maven {
url repository
}
}
}
//2、创建app模块中配置的flutter{ source: ‘…/…/’}闭包extensions
project.extensions.create(“flutter”, FlutterExtension)
//3、添加flutter构建相关的各种task
this.addFlutterTasks(project)
//4、判断编译命令flutter build apk --split-per-abi是否添加–split-per-abi参数,有的话就拆分成多个abi包。
if (shouldSplitPerAbi()) {
project.android {
splits {
abi {
// Enables building multiple APKs per ABI.
enable true
// Resets the list of ABIs that Gradle should create APKs for to none.
reset()
// Specifies that we do not want to also generate a universal APK that includes all ABIs.
universalApk false
}
}
}
}
//5、判断编译命令是否添加deferred-component-names参数,有就配置android dynamicFeatures bundle特性。
if (project.hasProperty(‘deferred-component-names’)) {
String[] componentNames = project.property(‘deferred-component-names’).split(‘,’).collect {“😒{it}”}
project.android {
dynamicFeatures = componentNames
}
}
//6、判断编译命令是否添加–target-platform=xxxABI参数,没有就用缺省,有就看这个ABI是否flutter支持的,支持就配置,否则抛出异常。
getTargetPlatforms().each { targetArch ->
String abiValue = PLATFORM_ARCH_MAP[targetArch]
project.android {
if (shouldSplitPerAbi()) {
splits {
abi {
include abiValue
}
}
}
}
}
//7、通过属性配置获取flutter.sdk,或者通过环境变量FLUTTER_ROOT获取,都没有就抛出环境异常。
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”)
}
//8、获取Flutter Engine的版本号,如果通过local-engine-repo参数使用本地自己编译的Engine则版本为+,否则读取SDK目录下bin\internal\engine.version文件值,一串类似MD5的值。
engineVersion = useLocalEngine()
 
   “1.0.0-” + Paths.get(flutterRoot.absolutePath, “bin”, “internal”, “engine.version”).toFile().text.trim() 
 
//9、依据平台获取对应flutter命令脚本,都位于SDK目录下bin\中,名字为flutter
String flutterExecutableName = Os.isFamily(Os.FAMILY_WINDOWS) ? “flutter.bat” : “flutter”
flutterExecutable = Paths.get(flutterRoot.absolutePath, “bin”, flutterExecutableName).toFile();
//10、获取flutter混淆配置清单,位于SDK路径下packages\flutter_tools\gradle\flutter_proguard_rules.pro。
//里面配置只有 -dontwarn io.flutter.plugin.** 和 -dontwarn android.**
String flutterProguardRules = Paths.get(flutterRoot.absolutePath, “packages”, “flutter_tools”,
“gradle”, “flutter_proguard_rules.pro”)
project.android.buildTypes {
//11、新增profile构建类型,在当前project下的android.buildTypes中进行配置
profile {
initWith debug //initWith操作复制所有debug里面的属性
if (it.hasProperty(“matchingFallbacks”)) {
matchingFallbacks = [“debug”, “release”]
}
}
//…
}
//…
//12、给所有buildTypes添加依赖,addFlutterDependencies
project.android.buildTypes.all this.&addFlutterDependencies
}
//…
}
//flutter{}闭包Extension定义
class FlutterExtension {
String source
String target
}
//…

可以看到,上面脚本的本质是一个标准插件,其内部主要就是基于我们传递的参数进行一些配置。上面的步骤 4 的表现看产物,这里不再演示。步骤 11 其实就是新增了一种编译类型,对应项目中就是性能模式,如下:

Flutter 中Android 工程 new 没有 java 文件选项 flutter android sdk_android

步骤 12 对应追加依赖的脚本如下:

/**

  • 给每个buildType添加Flutter项目的dependencies依赖,主要包括embedding和libflutter.so
*/
void addFlutterDependencies(buildType) {
//获取build类型,值为debug、profile、release
String flutterBuildMode = buildModeFor(buildType)
//对使用本地Engine容错,官方Engine忽略这个条件即可,继续往下
if (!supportsBuildMode(flutterBuildMode)) {
return
}
//如果插件不是applicationVariants类型,即android library,或者项目根目录下.flutter-plugins文件中安卓插件个数为空。
if (!isFlutterAppProject() || getPluginList().size() == 0) {
//简单理解就是给Flutter Plugin的android插件添加编译依赖
//譬如io.flutter:flutter_embedding_debug:1.0.0,来自maven仓库
addApiDependencies(project, buildType.name,
“io.flutter:flutter_embedding_engineVersion”)}
//给project添加对应编译依赖
//譬如io.flutter:arm64_v8a_debug:1.0.0,来自maven仓库
List platforms = getTargetPlatforms().collect()
// Debug mode includes x86 and x64, which are commonly used in emulators.
if (flutterBuildMode == “debug” && !useLocalEngine()) {
platforms.add(“android-x86”)
platforms.add(“android-x64”)
}
platforms.each { platform ->
String arch = PLATFORM_ARCH_MAP[platform].replace(“-”, “_”)
// Add the libflutter.so dependency.
addApiDependencies(project, buildType.name,
“io.flutter:KaTeX parse error: Expected group after '_' at position 7: {arch}_̲flutterBuildMode:$engineVersion”)
}
}
private static void addApiDependencies(Project project, String variantName, Object dependency, Closure config = null) {
String configuration;
// compile dependencies are now api dependencies.
if (project.getConfigurations().findByName(“api”)) {
configuration = “${variantName}Api”;
} else {
configuration = “${variantName}Compile”;
}
project.dependencies.add(configuration, dependency, config)
}

上面这段脚本的本质就是给 Flutter 项目自动添加编译依赖,这个依赖本质也是 maven 仓库的,很像我们自己编写 gradle 中添加的 okhttp 等依赖,没啥区别。譬如我们创建的 demo 项目导入 Android Studio 后自动 sync 的 dependencies 依赖如下:

Flutter 中Android 工程 new 没有 java 文件选项 flutter android sdk_ci_02

接下来我们把重心放回步骤 3(addFlutterTasks),这才是我们整个 Flutter app 编译的重点,也是最复杂的部分,如下:

private void addFlutterTasks(Project project) {
//gradle项目配置评估失败则返回,常规操作,忽略
if (project.state.failure) {
return
}
//1、一堆属性获取与赋值操作
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 之后的部分:

private void addFlutterTasks(Project project) {
//一堆属性获取与赋值操作
//…
//1、定义 addFlutterDeps 箭头函数,参数variant为标准构建对应的构建类型
def addFlutterDeps = { variant ->
if (shouldSplitPerAbi()) {
//2、常规操作:如果是构建多个变体apk模式就处理vc问题
variant.outputs.each { output ->
//由于GP商店不允许同一个应用的多个APK全都具有相同的版本信息,因此在上传到Play商店之前,您需要确保每个APK都有自己唯一的versionCode,这里就是做这个事情的。
//具体可以看官方文档 https://developer.android.com/studio/build/configure-apk-splits
def abiVersionCode = ABI_VERSION.get(output.getFilter(OutputFile.ABI))
if (abiVersionCode != null) {
output.versionCodeOverride =
abiVersionCode * 1000 + variant.versionCode
}
}
}
//3、获取编译类型,variantBuildMode值为debug、profile、release之一
String variantBuildMode = buildModeFor(variant.buildType)
//4、依据参数生成一个task名字,譬如这里的compileFlutterBuildDebug、compileFlutterBuildProfile、compileFlutterBuildRelease
String taskName = toCammelCase([“compile”, FLUTTER_BUILD_PREFIX, variant.name])
//5、给当前project创建compileFlutterBuildDebug、compileFlutterBuildProfile、compileFlutterBuildRelease Task
//实现为FlutterTask,主要用来编译Flutter代码,这个task稍后单独分析
FlutterTask compileTask = project.tasks.create(name: taskName, type: FlutterTask) {
//各种task属性赋值操作,基本都来自上面的属性获取或者匹配分析
flutterRoot this.flutterRoot
flutterExecutable this.flutterExecutable
buildMode variantBuildMode
localEngine this.localEngine
localEngineSrcPath this.localEngineSrcPath
//默认dart入口lib/main.dart、可以通过target属性自定义指向
targetPath getFlutterTarget()
verbose isVerbose()
fastStart isFastStart()
fileSystemRoots fileSystemRootsValue
fileSystemScheme fileSystemSchemeValue
trackWidgetCreation trackWidgetCreationValue
targetPlatformValues = targetPlatforms
sourceDir getFlutterSourceDirectory()
//学到一个小技能,原来中间API是AndroidProject.FD_INTERMEDIATES,这也是flutter中间产物目录
intermediateDir project.file(“{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)
}
}
}
}
//项目构建中间产物的文件,也就是根目录下build/intermediates/flutter/debug/libs.jar文件
File libJar = project.file(“{AndroidProject.FD_INTERMEDIATES}/flutter/${variant.name}/libs.jar”)//6、创建packLibsFlutterBuildProfile、packLibsFlutterBuildDebug、packLibsFlutterBuildRelease任务,主要是产物的复制挪位置操作,Jar 类型的 task
//作用就是把build/intermediates/flutter/debug/下依据abi生成的app.so通过jar命令打包成build/intermediates/flutter/debug/libs.jar
Task packFlutterAppAotTask = project.tasks.create(name: “packLibs{variant.name.capitalize()}”, type: Jar) {//目标路径为build/intermediates/flutter/debug目录
destinationDir libJar.parentFile
//文件名为libs.jar
archiveName libJar.name
//依赖前面步骤5定义的compileFlutterBuildDebug,也就是说,这个task基本作用是产物处理
dependsOn compileTask
//targetPlatforms取值为android-arm、android-arm64、android-x86、android-x64
targetPlatforms.each { targetPlatform ->
//abi取值为armeabi-v7a、arm64-v8a、x86、x86_64
String abi = PLATFORM_ARCH_MAP[targetPlatform]
//数据来源来自步骤5的compileFlutterBuildDebug任务中间产物目录
//即把build/intermediates/flutter/debug/下依据abi生成的app.so通过jar命令打包成一个build/intermediates/flutter/debug/libs.jar文件
from(“{abi}”) {include “*.so”
// Move app.so to lib/<abi>/libapp.so
rename { String filename ->
return “lib/{filename}”}
}
}
}
//前面有介绍过addApiDependencies作用,把 packFlutterAppAotTask 产物加到依赖项里面参与编译
//类似implementation files(‘libs.jar’),然后里面的so会在项目执行标准mergeDebugNativeLibs task时打包进标准lib目录
addApiDependencies(project, variant.name, project.files {
packFlutterAppAotTask
})
// 当构建有is-plugin属性时则编译aar
boolean isBuildingAar = project.hasProperty(‘is-plugin’)
//7、当是Flutter Module方式,即Flutter以aar作为已存在native安卓项目依赖时才有这些:flutter:模块依赖,否则没有这些task
//可以参见新建的FlutterModule中.android/include_flutter.groovy中gradle.project(“:flutter”).projectDir实现
Task packageAssets = project.tasks.findByPath(“:flutter:package${variant.name.capitalize()}Assets”)
Task cleanPackageAssets = project.tasks.findByPath(“:flutter:cleanPackage${variant.name.capitalize()}Assets”)
//判断是否为FlutterModule依赖
boolean isUsedAsSubproject = packageAssets && cleanPackageAssets && !isBuildingAar
//8、新建copyFlutterAssetsDebug task,目的就是copy产物,也就是assets归档
//常规merge中间产物类似,不再过多解释,就是把步骤5 task产物的assets目录在mergeAssets时复制到主包中间产物目录
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
}
// variant.mergeAssets will be removed at the end of 2019.
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
} // end def addFlutterDeps
…
}

上面这段比较直观,步骤5细节我们后面会分析这个 FlutterTask;对于步骤 6 其实也蛮直观,我们执行 flutter build apk 后看产物目录如下:

Flutter 中Android 工程 new 没有 java 文件选项 flutter android sdk_ci_03

这个 jar 也是重点,它里面其实不是 class,而是上图中的 abi 对应 app.so,也就是 dart app 编译的 so。所以 libs.jar 解压如下:

Flutter 中Android 工程 new 没有 java 文件选项 flutter android sdk_ci_04

这货会被类似 implementation files(‘libs.jar’) 添加进我们 project 的编译依赖项中,然后里面的 so 会在项目执行标准 mergeDebugNativeLibs task 时打包进标准 lib 目录,所以最终 apk 中 app.so 位于 lib 目录下(好奇反思:官方这里为什么不直接弄成 aar,而是把 so 打进 jar,感觉回到了 eclipse 时代,没整明白为什么)。

对于步骤 8 来说,assets 合并复制操作在 app 主包的中间产物中效果如下:

Flutter 中Android 工程 new 没有 java 文件选项 flutter android sdk_flutter_05

因此,步骤 6、步骤 8 的产物最终编译后就是 apk 中对应的东西,对应 apk 解压如下:

Flutter 中Android 工程 new 没有 java 文件选项 flutter android sdk_flutter_06

上面步骤5中的 FlutterTask 我们先放一放,让我们先继续看 addFlutterTasks 这个方法剩下的部分:

private void addFlutterTasks(Project project) {
//…上面已分析,下面接续分析
//1、如果是applicationVariants就走进去,也就是说project是app module
if (isFlutterAppProject()) {
project.android.applicationVariants.all { variant ->
//也就是assemble task咯
Task assembleTask = getAssembleTask(variant)
//正常容错,不用关心
if (!shouldConfigureFlutterTask(assembleTask)) {
return
}
//把前面定义的addFlutterDeps函数调用返回的copyFlutterAssetsTask任务拿到作为依赖项
//这货的作用和产物前面已经图示贴了产物
Task copyFlutterAssetsTask = addFlutterDeps(variant)
def variantOutput = variant.outputs.first()
def processResources = variantOutput.hasProperty(“processResourcesProvider”) ?
variantOutput.processResourcesProvider.get() : variantOutput.processResources
processResources.dependsOn(copyFlutterAssetsTask)
//2、执行flutter run或者flutter build apk的产物apk归档处理
//不多解释,下面会图解说明
variant.outputs.all { output ->
assembleTask.doLast {
// packageApplication became packageApplicationProvider in AGP 3.3.0.
def outputDirectory = variant.hasProperty(“packageApplicationProvider”)
 
   variant.packageApplication.outputDirectory 
 
// outputDirectory is a DirectoryProperty in AGP 4.1.
String outputDirectoryStr = outputDirectory.metaClass.respondsTo(outputDirectory, “get”)
 
   outputDirectory 
 
String filename = “app”
String abi = output.getFilter(OutputFile.ABI)
if (abi != null && !abi.isEmpty()) {
filename += “-${abi}”
}
if (variant.flavorName != null && !variant.flavorName.isEmpty()) {
filename += “-${variant.flavorName.toLowerCase()}”
}
filename += “-${buildModeFor(variant.buildType)}”
project.copy {
from new File(“{output.outputFileName}”)into new File(“${project.buildDir}/outputs/flutter-apk”);
rename {
return “${filename}.apk”
}
}
}
}
}
//3、小重点
configurePlugins()
return
}

//3、是不是模块源码依赖方式集成到现有项目,参见 https://flutter.cn/docs/development/add-to-app/android/project-setup

//是的话对模块也做类似一堆处理即可,不再重复分析了,也是 assets 合并

String hostAppProjectName = project.rootProject.hasProperty(‘flutter.hostAppProjectName’) ? project.rootProject.property(‘flutter.hostAppProjectName’) : “app”
Project appProject = project.rootProject.findProject(“😒{hostAppProjectName}”)
assert appProject != null : “Project 😒{hostAppProjectName} doesn’t exist. To custom the host app project name, set org.gradle.project.flutter.hostAppProjectName=<project-name> in gradle.properties.”
// Wait for the host app project configuration.
appProject.afterEvaluate {
assert appProject.android != null
project.android.libraryVariants.all { libraryVariant ->
Task copyFlutterAssetsTask
appProject.android.applicationVariants.all { appProjectVariant ->
Task appAssembleTask = getAssembleTask(appProjectVariant)
if (!shouldConfigureFlutterTask(appAssembleTask)) {
return
}
// Find a compatible application variant in the host app.
//
// For example, consider a host app that defines the following variants:
// | ----------------- | ----------------------------- |
// | Build Variant | Flutter Equivalent Variant |
// | ----------------- | ----------------------------- |
// | freeRelease | release |
// | freeDebug | debug |
// | freeDevelop | debug |
// | profile | profile |
// | ----------------- | ----------------------------- |
//
// This mapping is based on the following rules:
// 1. If the host app build variant name is profile then the equivalent
// Flutter variant is profile.
// 2. If the host app build variant is debuggable
// (e.g. buildType.debuggable = true), then the equivalent Flutter
// variant is debug.
// 3. Otherwise, the equivalent Flutter variant is release.
String variantBuildMode = buildModeFor(libraryVariant.buildType)
if (buildModeFor(appProjectVariant.buildType) != variantBuildMode) {
return
}
if (copyFlutterAssetsTask == null) {
copyFlutterAssetsTask = addFlutterDeps(libraryVariant)
}
Task mergeAssets = project
.tasks
.findByPath(“:{appProjectVariant.name.capitalize()}Assets”)assert mergeAssets
mergeAssets.dependsOn(copyFlutterAssetsTask)
}
}
}
configurePlugins()
}

上面这段代码分析中的步骤2本质就是对标准安卓构建产物进行一次重新按照格式归档,如果是 split api 模式就能很直观看出来效果,下面图示是直接运行 flutter build apk 的步骤 2 效果:

Flutter 中Android 工程 new 没有 java 文件选项 flutter android sdk_ci_07

对于上面代码片段中的步骤 3,我们可以详细来分析下:

/**

  • flutter的依赖都添加在pubspec.yaml中
  • 接着都会执行flutter pub get,然后工具会生成跟目录下.flutter-plugins等文件
  • 这里做的事情就是帮忙给module自动添加上这些插件dependencies依赖模块
*/
private void configurePlugins() {
if (!buildPluginAsAar()) {
//项目根目录下的.flutter-plugins文件
getPluginList().each this.&configurePluginProject


对于上面代码片段中的步骤 3,我们可以详细来分析下:

/**
• flutter的依赖都添加在pubspec.yaml中
• 接着都会执行flutter pub get,然后工具会生成跟目录下.flutter-plugins等文件
• 这里做的事情就是帮忙给module自动添加上这些插件dependencies依赖模块
*/
private void configurePlugins() {
if (!buildPluginAsAar()) {
//项目根目录下的.flutter-plugins文件
getPluginList().each this.&configurePluginProject