深度了解Flutter APP的构建流程
Posted Beason_H
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深度了解Flutter APP的构建流程相关的知识,希望对你有一定的参考价值。
android的构建流程不熟悉的,建议先看:Gradle构建流程-Android
首先下面我们在开看先Flutter APP构建流程:
Flutter APP 的构建流程,其实跟原生Android 的构建流程差不多,只是flutter app在构建的时候,导入了flutter.gradle文件,flutter.gradle在原生Android构建的task中添加了flutter编译相关的几个task,后面我们会详细介绍,下面我们一步步分析整个流程:
- settings.gradle 源码
- app_plugin_loader.gradle 源码
- 根目录下 build.gradle 源码
- App/build.gradle源码
- **核心:**flutter.gradle
1. setting.gradle的源码(大意看注释):
//引入app module
include ':app'
//读取local.properties文件,读取flutter配置信息
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"
//引入``$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle``文件
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
2. 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
}
//把安卓平台依赖的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
}
3. 根目录下 build.gradle 核心源码(大意看注释)
//将所有 android 依赖的构建产物挪到了根目录下的 build 中
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
//设置所有的其他子项目依赖项目“:app”
subprojects {
project.evaluationDependsOn(':app')
}
4. App/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路径
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.")
}
// 读取flutter.versionCode,该值读取:pubspec.yaml 文件中:version 值
// 比如:version: 1.0.0+11,versionCode为:11
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
// 读取flutter.versionName,该值读取:pubspec.yaml 文件中:version 值
// 比如:version: 1.0.0+11,versionName为:1.0.0
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
//声明为 application
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
//应用flutter.gradle flutter编译的核心问题,主要作用是在android构建流程的task中加入flutter编译相关的task
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 30
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.helloFlutter.hello_flutter"
minSdkVersion 16
targetSdkVersion 30
//赋值为上面从pubspec.yaml读取到的相关值
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
}
}
}
//一个拓展配置,指定source路径为当前的两级父级,也就是项目根目录
flutter {
source '../..'
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}
5. flutter.gradle 核心源码(大意看注释)
......省略部分.....
//应用 FLutterPlugin gradle插件
apply plugin: FlutterPlugin
//上面应用的FlutterPlugin插件实现源码
class FlutterPlugin implements Plugin<Project> {
......省略部分......
//核心方法!!!!!!!!!!
@Override
void apply(Project project) {
this.project = project
// 配置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
}
}
}
// 创建app模块中配置的flutter{ source: '../../'}闭包extensions
// source:用来配置当前Flutter工程的根路径,注意不是Android工程,如果没有配置抛出Must provide Flutter source directory异常
// target:用来指定Flutter代码的启动入口,如果没有配置默认为lib/main.dart
project.extensions.create("flutter", FlutterExtension)
//添加flutter构建相关的各种task
//文章最开始提到的构建流程最后执行阶段添加的几个flutter相关的task就是在这里添加:eg,
// app:compileFlutterBuildDebug
// app:packLibsflutterBuildDebug
// app:copyFlutterAssetsDebug
// 后面单独分析
this.addFlutterTasks(project)
// By default, assembling APKs generates fat APKs if multiple platforms are passed.
// Configuring split per ABI allows to generate separate APKs for each abi.
// This is a noop when building a bundle.
// 判断是否根据(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
}
}
}
}
//判断编译命令是否添加--target-platform=xxxABI参数,没有就用缺省,有就看这个ABI是否flutter支持的,支持就配置,否则抛出异常
getTargetPlatforms().each { targetArch ->
String abiValue = PLATFORM_ARCH_MAP[targetArch]
project.android {
if (shouldSplitPerAbi()) {
splits {
abi {
include abiValue
}
}
}
}
}
//通过属性配置获取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")
}
//获取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()
//依据平台获取对应flutter命令脚本,都位于SDK目录下bin\\中,名字为flutter
String flutterExecutableName = Os.isFamily(Os.FAMILY_WINDOWS) ? "flutter.bat" : "flutter"
flutterExecutable = Paths.get(flutterRoot.absolutePath, "bin", flutterExecutableName).toFile();
//获取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")
//新增profile构建类型,在当前project下的android.buildTypes中进行配置
project.android.buildTypes {
// Add profile build type.
profile {
initWith debug
if (it.hasProperty("matchingFallbacks")) {
matchingFallbacks = ["debug", "release"]
}
}
release {
// Enables code shrinking, obfuscation, and optimization for only
// your project's release build type.
minifyEnabled true
// Enables resource shrinking, which is performed by the
// Android Gradle plugin.
// NOTE: The resource shrinker can't be used for libraries.
shrinkResources isBuiltAsApp(project)
// Fallback to `android/app/proguard-rules.pro`.
// This way, custom Proguard rules can be configured as needed.
proguardFiles project.android.getDefaultProguardFile("proguard-android.txt"), flutterProguardRules, "proguard-rules.pro"
}
}
if (useLocalEngine()) {
// This is required to pass the local engine to flutter build aot.
String engineOutPath = project.property('local-engine-out')
File engineOut = project.file(engineOutPath)
if (!engineOut.isDirectory()) {
throw new GradleException('local-engine-out must point to a local engine build')
}
localEngine = engineOut.name
localEngineSrcPath = engineOut.parentFile.parent
}
//给所有buildTypes添加依赖,addFlutterDependencies
project.android.buildTypes.all this.&addFlutterDependencies
}
/**
* Adds the dependencies required by the Flutter project.
* This includes:
* 1. The embedding
* 2. libflutter.so
*/
void addFlutterDependencies(buildType) {
//获取flutter build类型,值为debug、profile、release
String flutterBuildMode = buildModeFor(buildType)
if (!supportsBuildMode(flutterBuildMode)) {
return
}
// The embedding is set as an API dependency in a Flutter plugin.
// Therefore, don't make the app project depend on the embedding if there are Flutter
// plugins.
// This prevents duplicated classes when using custom build types. That is, a custom build
// type like profile is used, and the plugin and app projects have API dependencies on the
// embedding.
// 如果插件不是applicationVariants类型,即android library,或者项目根目录下`.flutter-plugins`文件中安卓插件个数为空
if (!isFlutterAppProject() || getPluginList().size() == 0) {
// 给Flutter Plugin的android插件添加编译依赖
// 譬如io.flutter:flutter_embedding_debug:1.0.
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")
}
}
......省略部分.....
//接下来再单独分析核心部分:addFlutterTasks方法
6. 接下来我们再看看flutter.gradle的addFlutterTasks方法:
private void addFlutterTasks(Project project) {
if (project.state.failure) {
return
}
.....此处省略一堆属性获取与赋值操作......
// 定义 addFlutterDeps 函数,参数variant为标准构建对应的构建类型
def addFlutterDeps = { variant ->
if (shouldSplitPerAbi()) {
//常规操作:如果是构建多个变体apk模式就处理vc问题
variant.outputs.each { output ->
//确保每个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
}
}
}
//获取编译类型,variantBuildMode值为debug、profile、release之一
String variantBuildMode = buildModeFor(variant.buildType)
//依据参数生成一个task名字,譬如这里的compileFlutterBuildDebug、compileFlutterBuildProfile、compileFlutterBuildRelease
//参照文章开始最后执行阶段添加的flutter task名字就是在这里确认的
String taskName = toCammelCase(["compile", FLUTTER_BUILD_PREFIX, variant.name])
// 给当前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()
// 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
// 权限相关处理
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")
// 创建packLibsFlutterBuildProfile、packLibsFlutterBuildDebug、packLibsFlutterBuildRelease任务,主要是产物的复制挪位置操作,作用就是把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
// 依赖前面定义的compileTask,也就是说,这个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]
// 数据来源来自compileTask任务中间产物目录
// 即把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
})
// We build an AAR when this property is defined.
// 当构建有is-plugin属性时则编译aar
boolean isBuildingAar = project.hasProperty('is-plugin')
// In add to app scenarios, a Gradle project contains a `:flutter` and `:app` project.
// We know that `:flutter` is used as a subproject when these tasks exists and we aren't building an AAR.
// 当是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
// 新建copyFlutterAssetsDebug task,目的就是copy产物,也就是assets归档
// 常规merge中间产物类似,compileTask产物的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(Flutter Module Android的构建流程