Flutter Android 工程结构及应用层编译源码深入分析,Android 面试题及答案 2020
//......省略常规操作,不解释
下面我们看看上面提到的重点 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_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("{AndroidProject.FD_INTERMEDIATES}/flutter/${variant.name}/")
extraFrontEndOptions extraFrontEndOptionsValue
extraGenSnapshotOptions extraGenSnapshotOptionsValue
splitDebugInfo splitDebugInfoValue
treeShakeIcons treeShakeIconsOptionsValue
dartObfuscation dartObfuscationValue
dartDefines dartDefinesValue
bundleSkSLPath bundleSkSLPathValue
performanceMeasurementFile performanceMeasurementFileValue
codeSizeDirectory codeSizeDirectoryValue
deferredComponents deferredComponentsValue
validateDeferredComponents validateDeferredComponentsValue
//最后做一波权限相关处理
doLast {
project.exec {
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
commandLine('cmd', '/c', "attrib -r ${assetsDirectory}/* /s")
} else {
commandLine('chmod', '-R', 'u+w', assetsDirectory)
}
}
}
}
//项目构建中间产物的文件,也就是根目录下 build/intermediates/flutter/debug/libs.jar 文件
File libJar = project.file("{AndroidProject.FD_INTERMEDIATES}/flutter/${variant.name}/libs.jar")
//6、创建 packLibsFlutterBuildProfile、packLibsFlutterBuildDebug、packLibsFlutterBuildRelease 任务,主要是产物的复制挪位置操作,Jar 类型的 task
//作用就是把 build/intermediates/flutter/debug/下依据 abi 生成的 app.so 通过 jar 命令打包成 build/intermediates/flutter/debug/libs.jar
Task packFlutterAppAotTask = project.tasks.create(name: "packLibs{variant.name.capitalize()}", type: Jar) {
//目标路径为 build/intermediates/flutter/debug 目录
destinationDir libJar.parentFile
//文件名为 libs.jar
archiveName libJar.name
//依赖前面步骤 5 定义的 compileFlutterBuildDebug,也就是说,这个 task 基本作用是产物处理
dependsOn compileTask
//targetPlatforms 取值为 android-arm、android-arm64、android-x86、android-x64
targetPlatforms.each { targetPlatform ->
//abi 取值为 armeabi-v7a、arm64-v8a、x86、x86_64
String abi = PLATFORM_ARCH_MAP[targetPlatform]
//数据来源来自步骤 5 的 compileFlutterBuildDebug 任务中间产物目录
//即把 build/intermediates/flutter/debug/下依据 abi 生成的 app.so 通过 jar 命令打包成一个 build/intermediates/flutter/debug/libs.jar 文件
from("{abi}") {
include "*.so"
// Move app.so
to lib/<abi>/libapp.so
rename { String filename ->
return "lib/{filename}"
}
}
}
}
//前面有介绍过 addApiDependencies 作用,把 packFlutterAppAotTask 产物加到依赖项里面参与编译
//类似 implementation files('libs.jar'),然后里面的 so 会在项目执行标准 mergeDebugNativeLibs task 时打包进标准 lib 目录
addApiDependencies(project, variant.name, project.files {
packFlutterAppAotTask
})
// 当构建有 is-plugin 属性时则编译 aar
boolean isBuildingAar = project.hasProperty('is-plugin')
//7、当是 Flutter Module 方式,即 Flutter 以 aar 作为已存在 native 安卓项目依赖时才有这些:flutter:模块依赖,否则没有这些 task
//可以参见新建的 FlutterModule 中.android/include_flutter.groovy 中 gradle.project(":flutter").projectDir 实现
Task packageAssets = project.tasks.findByPath(":flutter:package${variant.name.capitalize()}Assets")
Task cleanPackageAssets = project.tasks.findByPath(":flutter:cleanPackage${variant.name.capitalize()}Assets")
//判断是否为 FlutterModule 依赖
boolean isUsedAsSubproject = packageAssets && cleanPackageAssets && !isBuildingAar
//8、新建 copyFlutterAssetsDebug task,目的就是 copy 产物,也就是 assets 归档
//常规 merge 中间产物类似,不再过多解释,就是把步骤 5 task 产物的 assets 目录在 mergeAssets 时复制到主包中间产物目录
Task copyFlutterAssetsTask = project.tasks.create(
name: "copyFlutterAssets${variant.name.capitalize()}",
type: Copy,
) {
dependsOn compileTask
with compileTask.assets
if (isUsedAsSubproject) {
dependsOn packageAssets
dependsOn cleanPackageAssets
into packageAssets.outputDir
return
}
// variant.mergeAssets
will be removed at the end of 2019.
def mergeAssets = variant.hasProperty("mergeAssetsProvider") ?
variant.mergeAssetsProvider.get() : variant.mergeAssets
dependsOn mergeAssets
dependsOn "clean${mergeAssets.name.capitalize()}"
mergeAssets.mustRunAfter("clean${mergeAssets.name.capitalize()}")
into mergeAssets.outputDir
}
if (!isUsedAsSubproject) {
def variantOutput = variant.outputs.first()
def processResources = variantOutput.hasProperty("processResourcesProvider") ?
variantOutput.processResourcesProvider.get() : variantOutput.processResources
processResources.dependsOn(copyFlutterAssetsTask)
}
return copyFlutterAssetsTask
} // end def addFlutterDeps
......
}
上面这段比较直观,步骤 5 细节我们后面会分析这个 FlutterTask;对于步骤 6 其实也蛮直观,我们执行 flutter build apk 后看产物目录如下:
这个 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("{output.outputFileName}")
into new File("${project.buildDir}/outputs/flutter-apk");
rename {
return "${filename}.apk"
}
}
}
}
}
//3、小重点
configurePlugins()
return
}
//3、是不是模块源码依赖方式集成到现有项目,参见 https://flutter.cn/docs/development/add-to-app/android/project-setup
//是的话对模块也做类似一堆处理即可,不再重复分析了,也是 assets 合并
String hostAppProjectName = project.rootProject.hasProperty('flutter.hostAppProjectName') ? project.rootProject.property('flutter.hostAppProjectName') : "app"
Project appProject = project.rootProject.findProject(":${hostAppProjectName}")
assert appProject != null : "Project :${hostAppProjectName} doesn't exist. To custom the host app project name, set org.gradle.project.flutter.hostAppProjectName=<project-name>
in gradle.properties."
// Wait for the host app project configuration.
appProject.afterEvaluate {
assert appProject.android != null
project.android.libraryVariants.all { libraryVariant ->
Task copyFlutterAssetsTask
appProject.android.applicationVariants.all { appProjectVariant ->
Task appAssembleTask = getAssembleTask(appProjectVariant)
if (!shouldConfigureFlutterTask(appAssembleTask)) {
return
}
// Find a compatible application variant in the host app.
//
// For example, consider a host app that defines the following variants:
// | ----------------- | ----------------------------- |
// | Build Variant | Flutter Equivalent Variant |
// | ----------------- | ----------------------------- |
// | freeRelease | release |
// | freeDebug | debug |
// | freeDevelop | debug |
// | profile | profile |
// | ----------------- | ----------------------------- |
//
// This mapping is based on the following rules:
// 1. If the host app build variant name is profile
then the equivalent
// Flutter variant is profile
.
// 2. If the host app build variant is debuggable
// (e.g. buildType.debuggable = true
), then the equivalent Flutter
// variant is debug
.
// 3. Otherwise, the equivalent Flutter variant is release
.
String variantBuildMode = buildModeFor(libraryVariant.buildType)
if (buildModeFor(appProjectVariant.buildType) != variantBuildMode) {
return
}
if (copyFlutterAssetsTask == null) {
copyFlutterAssetsTask = addFlutterDeps(libraryVariant)
}
Task mergeAssets = project
.tasks
.findByPath(":{appProjectVariant.name.capitalize()}Assets")
assert mergeAssets
mergeAssets.dependsOn(copyFlutterAssetsTask)
}
}
}
configurePlugins()
}
上面这段代码分析中的步骤 2 本质就是对标准安卓构建产物进行一次重新按照格式归档,如果是 split api 模式就能很直观看出来效果,下面图示是直接运行 flutter build apk 的步骤 2 效果:
对于上面代码片段中的步骤 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 \
评论