背景
本文部分配图及源码最近基于 Flutter 2.2.3 版本进行了修正更新发布。目的是为了弄清 Flutter 在安卓端应用层的整个编译来龙去脉,以便编译过程中出任何问题都能做到心里有数,另一个目的是为了能够在应用层定制 Flutter 编译。全文比较长,图文并茂,由工程结构深入到源码解析。
Flutter 模块的几种形式
早期版本的 Flutter 是不支持创建 Flutter Module,只有其他三种类型,想要这种类型都是靠自己造轮子和脚本实现的,现在新版本 Flutter 对于原生与 Flutter 混合模式的支持方便许多,所以目前 Flutter 支持创建如下四种模块。
这四种模块对应的项目结构大致如下,其使用场景也各不相同,我们要依据自己需要创建适合自己的模块。
Flutter 模块依赖及产物概览
当我们在 yaml 文件中添加依赖后执行flutter pub get
命令就会自动从依赖配置的地方下载或复制。对于纯 Dart 依赖(Flutter Package)的下载位置在你 Flutter SDK 目录下的.pub-cachehostedpub.dartlang.orgdio-4.0.0
位置(mac 下在自己账号目录下的.pub-cache
中),以 https://pub.flutter-io.cn/packages/dio为例,这个目录下 lib 为项目主要依赖,如下:
对应在 Android Studio 中依赖展开的样子如下:
对于依赖 Flutter Plugin 下载位置在你 Flutter SDK 目录下的.pub-cachehostedpub.dartlang.orgwebview_flutter-2.0.10
位置(mac 下在自己账号目录下的.pub-cache
中),以 https://pub.flutter-io.cn/packages/webview_flutter为例,这个目录下 lib 及对应平台目录为项目主要依赖,如下:
对应在 Android Studio 中依赖展开的样子如下:
对于一个 Flutter App 来说,其执行flutter build apk
命令编译后的产物宏观如下:
请务必对上图产物结构有个简单的认识,因为下文源码分析的重点都是围绕怎么编译出这些东西来了。
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 = "${rootProject.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<Project> {
//......
//重点入口!!!!!!
@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()
? project.property('local-engine-repo')
: "$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目录下bininternalengine.version文件值,一串类似MD5的值。
engineVersion = useLocalEngine()
? " " // Match any version since there's only one.
: "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路径下packagesflutter_toolsgradleflutter_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 其实就是新增了一种编译类型,对应项目中就是性能模式,如下:
步骤 12 对应追加依赖的脚本如下:
代码语言:txt复制/**
* 给每个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_$flutterBuildMode:$engineVersion")
}
//给project添加对应编译依赖
//譬如io.flutter:arm64_v8a_debug:1.0.0,来自maven仓库
List<String> 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:${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 依赖如下:
接下来我们把重心放回步骤 3(addFlutterTasks),这才是我们整个 Flutter app 编译的重点,也是最复杂的部分,如下:
代码语言:txt复制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 之后的部分:
代码语言:txt复制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("${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)
}
}
}
}
//项目构建中间产物的文件,也就是根目录下build/intermediates/flutter/debug/libs.jar文件
File libJar = project.file("${project.buildDir}/${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${FLUTTER_BUILD_PREFIX}${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("${compileTask.intermediateDir}/${abi}") {
include "*.so"
// Move `app.so` to `lib/<abi>/libapp.so`
rename { String filename ->
return "lib/${abi}/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 后看产物目录如下:
这个 jar 也是重点,它里面其实不是 class,而是上图中的 abi 对应 app.so,也就是 dart app 编译的 so。所以 libs.jar 解压如下:
这货会被类似 implementation files('libs.jar') 添加进我们 project 的编译依赖项中,然后里面的 so 会在项目执行标准 mergeDebugNativeLibs task 时打包进标准 lib 目录,所以最终 apk 中 app.so 位于 lib 目录下(好奇反思:官方这里为什么不直接弄成 aar,而是把 so 打进 jar,感觉回到了 eclipse 时代,没整明白为什么)。
对于步骤 8 来说,assets 合并复制操作在 app 主包的中间产物中效果如下:
因此,步骤 6、步骤 8 的产物最终编译后就是 apk 中对应的东西,对应 apk 解压如下:
上面步骤5中的 FlutterTask 我们先放一放,让我们先继续看 addFlutterTasks 这个方法剩下的部分:
代码语言:txt复制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.packageApplicationProvider.get().outputDirectory
: variant.packageApplication.outputDirectory
// `outputDirectory` is a `DirectoryProperty` in AGP 4.1.
String outputDirectoryStr = outputDirectory.metaClass.respondsTo(outputDirectory, "get")
? 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("$outputDirectoryStr/${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(":${hostAppProjectName}:merge${appProjectVariant.name.capitalize()}Assets")
assert mergeAssets
mergeAssets.dependsOn(copyFlutterAssetsTask)
}
}
}
configurePlugins()
}
上面这段代码分析中的步骤2本质就是对标准安卓构建产物进行一次重新按照格式归档,如果是 split api 模式就能很直观看出来效果,下面图示是直接运行 flutter build apk 的步骤 2 效果:
对于上面代码片段中的步骤 3,我们可以详细来分析下:
代码语言:txt复制/**
* flutter的依赖都添加在pubspec.yaml中
* 接着都会执行flutter pub get,然后工具会生成跟目录下.flutter-plugins等文件
* 这里做的事情就是帮忙给module自动添加上这些插件dependencies依赖模块
*/
private void configurePlugins() {
if (!buildPluginAsAar()) {
//项目根目录下的.flutter-plugins文件
getPluginList().each this.&configurePluginProject
//项目根目录下的.flutter-plugins-dependencies文件
getPluginDependencies().each this.&configurePluginDependencies
return
}
project.repositories {
maven {
url "${getPluginBuildDir()}/outputs/repo"
}
}
getPluginList().each { pluginName, pluginPath ->
configurePluginAar(pluginName, pluginPath, project)
}
}
到此整个 addFlutterTasks 核心方法我们就分析完毕。接下来让我们把目光转向 FlutterTask 的实现,Task 机制不懂就自己去补习 gradle 基础吧,重点入口就是 @TaskAction,如下(比较长,但是比较直观简单):
代码语言:txt复制abstract class BaseFlutterTask extends DefaultTask {
//......一堆task属性声明,忽略
@OutputFiles
FileCollection getDependenciesFiles() {
FileCollection depfiles = project.files()
// Includes all sources used in the flutter compilation.
depfiles = project.files("${intermediateDir}/flutter_build.d")
return depfiles
}
//重点!!!!!!!!!!!!!!!!!!!!!
//整个flutter android编译的核心实现在此!!!!
void buildBundle() {
if (!sourceDir.isDirectory()) {
throw new GradleException("Invalid Flutter source directory: ${sourceDir}")
}
//1、默认以app为例创建build/app/intermediates/flutter目录
intermediateDir.mkdirs()
//2、计算flutter assemble的规则名称列表
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" }
}
//3、重点执行命令
project.exec {
logging.captureStandardError LogLevel.ERROR
//4、windows的话就是flutter SDK路径下 bin/flutter.bat文件,unix就是bin/flutter
executable flutterExecutable.absolutePath
//5、我们app的build.gradle中配置的flutter { source '../../' }闭包,路径,也就是项目根目录下
workingDir sourceDir
//6、使用本地自己编译的flutter engine才需要的参数
if (localEngine != null) {
args "--local-engine", localEngine
args "--local-engine-src-path", localEngineSrcPath
}
//7、类似标准gradle构建参数打印控制
if (verbose) {
args "--verbose"
} else {
args "--quiet"
}
//8、追加一堆编译参数
args "assemble"
args "--no-version-check"
args "--depfile", "${intermediateDir}/flutter_build.d"
//flutter 编译产物输出路径
args "--output", "${intermediateDir}"
if (performanceMeasurementFile != null) {
args "--performance-measurement-file=${performanceMeasurementFile}"
}
//Flutter dart程序入口,默认为lib/main.dart
if (!fastStart || buildMode != "debug") {
args "-dTargetFile=${targetPath}"
} else {
args "-dTargetFile=${Paths.get(flutterRoot.absolutePath, "examples", "splash", "lib", "main.dart")}"
}
args "-dTargetPlatform=android"
args "-dBuildMode=${buildMode}"
if (trackWidgetCreation != null) {
args "-dTrackWidgetCreation=${trackWidgetCreation}"
}
if (splitDebugInfo != null) {
args "-dSplitDebugInfo=${splitDebugInfo}"
}
if (treeShakeIcons == true) {
args "-dTreeShakeIcons=true"
}
if (dartObfuscation == true) {
args "-dDartObfuscation=true"
}
if (dartDefines != null) {
args "--DartDefines=${dartDefines}"
}
if (bundleSkSLPath != null) {
args "-iBundleSkSLPath=${bundleSkSLPath}"
}
if (codeSizeDirectory != null) {
args "-dCodeSizeDirectory=${codeSizeDirectory}"
}
if (extraGenSnapshotOptions != null) {
args "--ExtraGenSnapshotOptions=${extraGenSnapshotOptions}"
}
if (extraFrontEndOptions != null) {
args "--ExtraFrontEndOptions=${extraFrontEndOptions}"
}
args ruleNames
}
}
}
class FlutterTask extends BaseFlutterTask {
//默认以app为例则为build/app/intermediates/flutter目录。
@OutputDirectory
File getOutputDirectory() {
return intermediateDir
}
//默认以app为例则为build/app/intermediates/flutter/flutter_assets目录,前面我们已经截图展示过这个目录产物。
@Internal
String getAssetsDirectory() {
return "${outputDirectory}/flutter_assets"
}
//assets复制操作定义,intermediateDir就是getOutputDirectory路径
@Internal
CopySpec getAssets() {
return project.copySpec {
from "${intermediateDir}"
include "flutter_assets/**" // the working dir and its files
}
}
//dart编译的产物复制操作定义(注意:release和profile模式才是so产物),intermediateDir就是getOutputDirectory路径
@Internal
CopySpec getSnapshots() {
return project.copySpec {
from "${intermediateDir}"
if (buildMode == 'release' || buildMode == 'profile') {
targetPlatformValues.each {
include "${PLATFORM_ARCH_MAP[targetArch]}/app.so"
}
}
}
}
//依赖格式解析生成文件路径集合
FileCollection readDependencies(File dependenciesFile, Boolean inputs) {
if (dependenciesFile.exists()) {
// Dependencies file has Makefile syntax:
// <target> <files>: <source> <files> <separated> <by> <non-escaped space>
String depText = dependenciesFile.text
// So we split list of files by non-escaped(by backslash) space,
def matcher = depText.split(': ')[inputs ? 1 : 0] =~ /(\ |[^s]) /
// then we replace all escaped spaces with regular spaces
def depList = matcher.collect{it[0].replaceAll("\\ ", " ")}
return project.files(depList)
}
return project.files();
}
//输入源为所有依赖模块的pubspec.yaml文件集合
@InputFiles
FileCollection getSourceFiles() {
FileCollection sources = project.files()
for (File depfile in getDependenciesFiles()) {
sources = readDependencies(depfile, true)
}
return sources project.files('pubspec.yaml')
}
@OutputFiles
FileCollection getOutputFiles() {
FileCollection sources = project.files()
for (File depfile in getDependenciesFiles()) {
sources = readDependencies(depfile, false)
}
return sources
}
//重点实现!!!!!!!
@TaskAction
void build() {
buildBundle()
}
}
可以很直观的看到,整个构建编译的核心都是通过执行 Flutter SDK 中 bin 目录下的 flutter 脚本完成的,大段代码只是为了为执行这个脚本准备参数配置信息。也就是说 flutter 编译本质命令大致如下:
代码语言:txt复制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
这就走到了 SDK 里面的纯 flutter 命令脚本了。
Flutter SDK 下bin/flutter
编译命令分析
承接上面分析,上一小节最后的命令本质就是本小节的脚本,我们把目光转向 Flutter SDK 中 bin 目录下的 flutter 脚本,如下:
代码语言:txt复制#!/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 "$@"
很明显,我们需要将目光转向 Flutter SDKbin/internal/shared.sh
文件,且关注其内部的shared::execute
方法,如下:
#......
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 "