背景
本文部分配圖及原始碼最近基于 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原始碼,敬請期待,
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/289330.html
標籤:其他
