一、引言
本文主要对Flutter工程编译时如何把Flutter生成的产物打包进入Android工程中进行分析。在Flutter工中打包过程中涉及到了local.properties、settings.gradle、build.gradle、flutter.gradle这几个脚本文件的参与,与传统的Android工程相比这几个脚本文件中的内容也不相同,接下来我们通过一层层解析,解开Flutter工程编译的面纱。 同时也建议大家看的时候搭配Flutter工程一起食用效果更佳。
二、工程结构分析
首先我们创建了一个最普通的Flutter工程flutter_new,创建后整个工程的目录结构如下:
Flutter工程下包括了Android和IOS两个目录,分别用于运行在Android和IOS平台上。其中android目录结果与Android工程的目录结构是一样的。Flutter工程中的android目录下包含了两个工程:第一个是android根工程,第二个是app子工程:
稍微有点不同的地方在于这两个工程的的输出目录搬到了Flutter工程根目录build下:
Flutter工程中android工程与传统的Android工程相比,都有.gradle、gradle、 setting.gradlew、gradlew等目录和文件,文件基本上都是一样的,在但是在local.properties、settings.gradle、build.gradle文件内容上又有所不同,接下来我们我们会一一做对比。
三、local.properties
在根工程下的local.properties文件中了多了Flutter SDK相关的配置信息,包括SDK的路径、版本名、版本号,这些信息在构建工程的过程自动从环境变量中获取的,我们无需手动配置。
四、根工程settings.gradle
如果你有配置Flutter工程根目录下.flutter-plugins这个文件,那么下面的操作就会把flutter插件用到的第三方工程include到当前的工程中,并为其配置工程的路径 projectDir:
include ':app'
//1、根工程的父目录,既Flutter工程目录
def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
//2、把Flutte工程下.flutter-plugins文件内容读取到内存中
def plugins = new Properties()
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
if (pluginsFile.exists()) {
pluginsFile.withReader('UTF-8') {
reader -> plugins.load(reader) }
}
//3、把.flutter-plugins文件中配置的flutter插件工程包含到当前工程中
plugins.each {
name, path ->
def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
include ":$name"
project(":$name").projectDir = pluginDirectory
}
然,我们新创建的Flutter工程默认是没有.flutter-plugins这个文件的,所以上面的代码基本不走。
五、根工程build.gradle
根工程中的build.grandle主要的工作是重新配置了工程的输出目录 和 工程间配置执行时的依赖关系:
buildscript {
//...}
allprojects {
//...}
//上面的代码是基本一样的
//第一点
rootProject.buildDir = '../build' //根工程输出路径
subprojects {
//所有子工程输出路径
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
//第二点
subprojects {
project.evaluationDependsOn(':app') //为所有子工程配置app的依赖
}
//第三点
task clean(type: Delete) {
delete rootProject.buildDir
}
第一点:配置了根工程 和 其所有子工程的输出路径。把所有的输出路径都搬到了Flutter根目录下的build目录中,如果是子工程则在build目录再建立属于自己名称的输出目录。可以看下面这张图:
第二点:所有子工程都配置app子工程的依赖,既让所有子工程运行配置阶段开始之前都要保证app工程的配置阶段都已经运行完毕。这样做的好处就是保证app工程的配置属性优先导入,防止其他子工程出现属性找不到的问题发生。
第三点: 为根工程添加clean任务用来删除build目录下所有文件。
关于Project#evaluationDependsOn方法
evaluationDependsOn用于配置Project对象之间的依赖关系,跟Task的dependsOn原理一样。
举个例子,比如有两个工程app工程和lib工程,其中app依赖lib工程。在lib工程的build.gradle 添加如下的属性:
rootProject.ext.producerMsg = "Hello"
在app工程的build.gradle 添加如下的代码,既app工程使用lib工程的动态属性:
def msg = rootProject.ext.producerMsg
如果在在配置阶段app 工程先运行,这样就会导致app会导致producerMsg属性没有找到!因为此lib工程还未运行。
所以要解决这个问题 就要在app project运行配置之前,先运行lib project的配置,那么就可以用evaluationDependsOn来解决依赖,在app的build.gradle中添加如下依赖即可:
evaluationDependsOn(':lib') //运行app配置之前,先运行lib依赖
那么添加依赖之后,每次在运行app配置阶段之前,都会保证lib配置阶段先被执行。
六、APP工程build.gradle
build.gradle的内容如下,与原工程一样的地方就省略了:
//第一点:读取local.properties文件中内容到内存
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') {
reader ->
localProperties.load(reader)
}
}
//第二点:获取flutter sdk路径、versionCode、VersionName
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'
//第三点:导入flutter gradle插件
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
//省略...基本一样
}
//第四点
flutter {
source '../..'
}
dependencies {
//可见App工程并没有依赖support包
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
第一点:读取根工程下local.properties的内容到内存中(Properties), 该内容就是上面所介绍的Flutter SDK相关信息。
第二点:获取flutter sdk路径、versionCode、VersionName等信息。
第三点:从Flutter SDK目录下导入flutter gradle插件到当前工程中运行。
第四点:配置flutter插件的source属性,该属性指定了Flutter工程的路径。
该build.gradle最主要的功能从local.properties文件中获取Flutter SDK路径,并把该路径下的Flutter Gradle插件导入到当前工程中运行,接下来我们要看看该插件到底做了哪些工作。
七、flutter.gradle
Flutter代码打包到Android工程中秘密其实就是发生在flutter.gradle脚本中。该gradle脚本位于Flutter SDK/packages/flutter_tools/gradle/flutter.gradle中,接下来我们就揭开它的神秘面纱:
flutter.gradle代码分为了有两大核心部分:FlutterPlugin和FlutterTask。
FlutterPlugin核心代码
apply方法
FlutterPlugin实现了Plugin接口,它是一个标准的gradle plugin。因此它的主入口在apply方法中,首先我们看看第一部分:
@Override
void apply(Project project) {
// Add custom build types
println "==== apply:" + project.getName() //app
//1、新增profile、dynamicProfile、dynamicRelease 三种构建类型
//在当前project下的android.buildTypes进行配置
project.android.buildTypes {
profile {
initWith debug //initWith:复制所有debug里面的属性
if (it.hasProperty('matchingFallbacks')) {
matchingFallbacks = ['debug', 'release']
}
}
dynamicProfile {
initWith debug
if (it.hasProperty('matchingFallbacks')) {
matchingFallbacks = ['debug', 'release']
}
}
dynamicRelease {
initWith debug
if (it.hasProperty('matchingFallbacks')) {
matchingFallbacks = ['debug', 'release']