Flutter Android 工程结构及应用层编译源码深入分析
背景
本文部分配图及源码最近基于 Flutter 2.2.3 版本进行了修正更新发布。目的是为了弄清 Flutter 在安卓端应用层的整个编译来龙去脉,以便编译过程中出任何问题都能做到心里有数,另一个目的是为了能够在应用层定制 Flutter 编译。全文比较长,图文并茂,由工程结构深入到源码解析。
Flutter 模块的几种形式
早期版本的 Flutter 是不支持创建 Flutter Module,只有其他三种类型,想要这种类型都是靠自己造轮子和脚本实现的,现在新版本 Flutter 对于原生与 Flutter 混合模式的支持方便许多,所以目前 Flutter 支持创建如下四种模块。
这四种模块对应的项目结构大致如下,其使用场景也各不相同,我们要依据自己需要创建适合自己的模块。
Flutter 模块依赖及产物概览
当我们在 yaml 文件中添加依赖后执行flutter pub get
命令就会自动从依赖配置的地方下载或复制。对于纯 Dart 依赖(Flutter Package)的下载位置在你 Flutter SDK 目录下的.pub-cache\hosted\pub.dartlang.org\dio-4.0.0
位置(mac 下在自己账号目录下的.pub-cache
中),以 https://pub.flutter-io.cn/packages/dio为例,这个目录下 lib 为项目主要依赖,如下:
对应在 Android Studio 中依赖展开的样子如下:
对于依赖 Flutter Plugin 下载位置在你 Flutter SDK 目录下的.pub-cache\hosted\pub.dartlang.org\webview_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目录下bin\internal\engine.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路径下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 其实就是新增了一种编译类型,对应项目中就是性能模式,如下:
步骤 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_$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 编译的重点,也是最复杂的部分,如下:
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("${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 这个方法剩下的部分:
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,我们可以详细来分析下:
/**
* 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,如下(比较长,但是比较直观简单):
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 编译本质命令大致如下:
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 脚本,如下:
#!/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 "📎"
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
命令,本质走到上面脚本最终执行的命令如下:
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()
方法。因此上面命令继续简化大致如下:
dart --disable-dart-dev --packages=xxx flutter_tools.dart build apk
也就是说,我们执行的任何 flutter 命令,本质都是把参数传递到了FLUTTER_SDK_DIR/packages/flutter_tools/bin/flutter_tools.dart
源码的 main 方法中,所以真正做事情的都在这部分源码里。这里由于篇幅问题不展开说明,后面专门写一篇解析,然后与本文关联阅读即可彻底搞懂。
Flutter Plugin 安卓编译流程
对于包含 android 代码的 flutter plugin 模块来说,其 android 部分就是一个标准的原生 android library,没有任何额外的干预脚本,所以就不分析了。这里只是提醒下,当我们新建一个 flutter plugin 时,其项目默认除过 plugin 会帮我们生成一个 example 的模块,目的只是为了方便我们独立开发 flutter plugin 时能脱离自己主项目进行 demo 验证,大致目录如下:
Flutter Module 安卓编译流程
对于原生现有工程集成 flutter 来说,flutter module 就是最好的隔离选择,这也就造就了其与 flutter app 在编译上的一些差异与共性。这部分我们重点分析 flutter module 与 上面分析的 app 编译流程差异,共性部分不再分析。
同样先从.android/settings.gradle
看起来:
// app 是测试 module,用来验证 flutter module 的,本质最后 flutter module 会生成可集成的 aar
include ':app'
//导入配置.android/include_flutter.groovy
rootProject.name = 'android_generated'
setBinding(new Binding([gradle: this]))
evaluate(new File(settingsDir, 'include_flutter.groovy'))
目光转向当前 flutter module 项目.android/include_flutter.groovy
文件,如下:
//1、以当前脚本为坐标找到当前项目根路径
def scriptFile = getClass().protectionDomain.codeSource.location.toURI()
def flutterProjectRoot = new File(scriptFile).parentFile.parentFile
//2、导入flutter module名称为相对当前目录的flutter
gradle.include ":flutter"
//3、flutter module android真正的实现位于.android/Flutter目录下
gradle.project(":flutter").projectDir = new File(flutterProjectRoot, ".android/Flutter")
//4、前面见过了,就是获取 flutter sdk 路径,然后导入脚本
def localPropertiesFile = new File(flutterProjectRoot, ".android/local.properties")
def properties = new Properties()
assert localPropertiesFile.exists(), "❗️The Flutter module doesn't have a `$localPropertiesFile` file." +
"\nYou must run `flutter pub get` in `$flutterProjectRoot`."
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
//5、类似之前,apply导入一个flutter sdk目录下的脚本
gradle.apply from: "$flutterSdkPath/packages/flutter_tools/gradle/module_plugin_loader.gradle"
目光转向 Flutter SDK 目录下packages/flutter_tools/gradle/module_plugin_loader.gradle
脚本文件,你会发现和前面 app 的settings.gradle
中 apply 的脚本很像,也是自动配置一些依赖模块啥的,所以不分析了。
接着看看.android/app/build.gradle
,你会发现他就是一个标准的 android app 脚本,dependencies 中只是多了上面settings.gradle
中的 flutter module,即implementation project(':flutter')
。
接着看看真正 flutter module android 相关的脚本,即.android/Flutter/build.gradle
,如下:
//......
apply plugin: 'com.android.library'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
//......
flutter {
source '../..'
}
咋说?不用我多解释了吧,本质回到了flutter.gradle
,我们前面已经分析过了,到此一切真相大白。
pubspec.yaml
及相关流程分析
先看一下其内部内容,大致如下:
# 项目名称和描述
name: f1
description: A new f1 project.
# 想要发布的位置,删除就是发布到pub.dev
publish_to: 'none'
# 版本号,修改这里后会自动修改安卓项目下local.properties文件中的versionName、versionCode
version: 1.0.1+10
# dart SDK 版本范围
environment:
sdk: ">=2.13.0 <3.0.0"
# 编译依赖
dependencies:
flutter:
sdk: flutter
dio: ^4.0.0 #来自pub.dev的纯dart依赖,即Flutter Package
webview_flutter: ^2.0.10 #来自pub.dev的插件依赖,即Flutter Plugin
f_package: #来自本地的纯dart依赖,即Flutter Package
path: ./../f_package
f_plugin: #来自本地的插件依赖,即Flutter Plugin
path: ./../f_plugin
# 开发模式依赖
dev_dependencies:
flutter_test:
sdk: flutter
# ......
pubspec.yaml
文件中version: 1.0.1+10
修改后会自动覆盖android/local.properties
中的flutter.versionName
和flutter.versionCode
。当我们追加依赖后一般都会执行flutter pub get
或者flutter pub upgrade
等命令来更新,这个命令背后的逻辑其是也是走进了我们上面 Flutter SDK 下bin/flutter
编译命令分析相关内容。
总结
到此,Flutter Android 应用层编译的全方位都分析到位了。由于篇幅问题,下一篇我们接续分析 Flutter SDK 下bin/flutter
编译命令的本质FLUTTER_SDK_DIR/packages/flutter_tools/flutter_tools.dart
源码,敬请期待。
版权声明: 本文为 InfoQ 作者【工匠若水】的原创文章。
原文链接:【http://xie.infoq.cn/article/e5dd99d978240cfd834eabd7e】。未经作者许可,禁止转载。
工匠若水
专注于移动互联网及嵌入式领域。 2018.05.02 加入
微信公众号:码农每日一题 个人微信号:codedeveloper 写博客不赚钱,就图交个朋友,欢迎来撩,微信更精彩。
评论