Flutter Android 工程結構及應用層編譯源碼深刻分析

背景

本文部分配圖及源碼最近基於 Flutter 2.2.3 版本進行了修正更新發布。目的是爲了弄清 Flutter 在安卓端應用層的整個編譯前因後果,以便編譯過程當中出任何問題都能作到內心有數,另外一個目的是爲了可以在應用層定製 Flutter 編譯。全文比較長,圖文並茂,由工程結構深刻到源碼解析。java

Flutter 模塊的幾種形式

早期版本的 Flutter 是不支持建立 Flutter Module,只有其餘三種類型,想要這種類型都是靠本身造輪子和腳本實現的,如今新版本 Flutter 對於原生與 Flutter 混合模式的支持方便許多,因此目前 Flutter 支持建立以下四種模塊。linux

在這裏插入圖片描述

這四種模塊對應的項目結構大體以下,其使用場景也各不相同,咱們要依據本身須要建立適合本身的模塊。android

在這裏插入圖片描述

Flutter 模塊依賴及產物概覽

當咱們在 yaml 文件中添加依賴後執行flutter pub get命令就會自動從依賴配置的地方下載或複製。對於純 Dart 依賴(Flutter Package)的下載位置在你 Flutter SDK 目錄下的.pub-cache\hosted\pub.dartlang.org\dio-4.0.0位置(mac 下在本身帳號目錄下的.pub-cache中),以 pub.flutter-io.cn/packages/di…爲例,這個目錄下 lib 爲項目主要依賴,以下:ios

在這裏插入圖片描述

對應在 Android Studio 中依賴展開的樣子以下:git

在這裏插入圖片描述

對於依賴 Flutter Plugin 下載位置在你 Flutter SDK 目錄下的.pub-cache\hosted\pub.dartlang.org\webview_flutter-2.0.10位置(mac 下在本身帳號目錄下的.pub-cache中),以 pub.flutter-io.cn/packages/we…爲例,這個目錄下 lib 及對應平臺目錄爲項目主要依賴,以下:web

在這裏插入圖片描述

對應在 Android Studio 中依賴展開的樣子以下:docker

在這裏插入圖片描述

對於一個 Flutter App 來講,其執行flutter build apk命令編譯後的產物宏觀以下:shell

在這裏插入圖片描述

請務必對上圖產物結構有個簡單的認識,由於下文源碼分析的重點都是圍繞怎麼編譯出這些東西來了。macos

Flutter App 安卓編譯源碼流程

下面咱們從純 Flutter 項目的 app 編譯安卓端 apk 流程提及。json

settings.gradle 源碼流程分析

既然是安卓的編譯流程,那就先從android/settings.gradle看起,以下:

// 當前 app module
include ':app'

/** * 一、讀取android/local.properties文件內容 * 二、獲取flutter.sdk的值,也就是你本地flutter SDK安裝目錄 * 三、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
}
/** * 一、經過groovy的JsonSlurper解析json文件內容。 * 二、簡單校驗json內容字段的類型合法性。 * 三、把安卓平臺依賴的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,以下:

/** * 一、讀取local.properties配置信息。 * 二、獲取flutter.sdk路徑。 * 三、獲取flutter.versionCode值,此值在編譯時自動從pubspec.yaml中讀取賦值,因此修改版本號請修改yaml。 * 四、獲取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

        //一、配置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
        project.extensions.create("flutter", FlutterExtension)
        //三、添加flutter構建相關的各類task
        this.addFlutterTasks(project)

        //四、判斷編譯命令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
                    }
                }
            }
        }
		//五、判斷編譯命令是否添加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
            }
        }
        //六、判斷編譯命令是否添加--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")
        project.android.buildTypes {
            //十一、新增profile構建類型,在當前project下的android.buildTypes中進行配置
            profile {
                initWith debug //initWith操做複製全部debug裏面的屬性
                if (it.hasProperty("matchingFallbacks")) {
                    matchingFallbacks = ["debug", "release"]
                }
            }
            //......
        }
        //......
        //十二、給全部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
    }
    //一、一堆屬性獲取與賦值操做
    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) {
    //一堆屬性獲取與賦值操做
    //......
    //一、定義 addFlutterDeps 箭頭函數,參數variant爲標準構建對應的構建類型
    def addFlutterDeps = { variant ->
        if (shouldSplitPerAbi()) {
        	//二、常規操做:若是是構建多個變體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
                }
            }
        }
        //三、獲取編譯類型,variantBuildMode值爲debug、profile、release之一
        String variantBuildMode = buildModeFor(variant.buildType)
        //四、依據參數生成一個task名字,譬如這裏的compileFlutterBuildDebug、compileFlutterBuildProfile、compileFlutterBuildRelease
        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()
            //學到一個小技能,原來中間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")
        //六、建立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-arm6四、android-x8六、android-x64
            targetPlatforms.each { targetPlatform ->
            	//abi取值爲armeabi-v7a、arm64-v8a、x8六、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')
        //七、當是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中間產物相似,再也不過多解釋,就是把步驟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 主包的中間產物中效果以下:

在這裏插入圖片描述

所以,步驟 六、步驟 8 的產物最終編譯後就是 apk 中對應的東西,對應 apk 解壓以下:

在這裏插入圖片描述

上面步驟5中的 FlutterTask 咱們先放一放,讓咱們先繼續看 addFlutterTasks 這個方法剩下的部分:

private void addFlutterTasks(Project project) {
    //......上面已分析,下面接續分析
    //一、若是是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)

            //二、執行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"
                        }
                    }
                }
            }
        }
        //三、小重點
        configurePlugins()
        return
    }
    //三、是否是模塊源碼依賴方式集成到現有項目,參見 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}")
        }
		//一、默認以app爲例建立build/app/intermediates/flutter目錄
        intermediateDir.mkdirs()

        //二、計算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" }
        }
        //三、重點執行命令
        project.exec {
            logging.captureStandardError LogLevel.ERROR
            //四、windows的話就是flutter SDK路徑下 bin/flutter.bat文件,unix就是bin/flutter
            executable flutterExecutable.absolutePath
            //五、咱們app的build.gradle中配置的flutter { source '../../' }閉包,路徑,也就是項目根目錄下
            workingDir sourceDir
            //六、使用本地本身編譯的flutter engine才須要的參數
            if (localEngine != null) {
                args "--local-engine", localEngine
                args "--local-engine-src-path", localEngineSrcPath
            }
            //七、相似標準gradle構建參數打印控制
            if (verbose) {
                args "--verbose"
            } else {
                args "--quiet"
            }
            //八、追加一堆編譯參數
            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
#一、該命令以後出現的代碼,一旦出現了返回值非零,整個腳本就會當即退出,那麼就能夠避免一些腳本的危險操做。
set -e
#二、清空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

#三、source導入這個shell腳本後執行其內部的shared::execute方法
source "$BIN_DIR/internal/shared.sh"
shared::execute "$@"
複製代碼

很明顯,咱們須要將目光轉向 Flutter SDKbin/internal/shared.sh文件,且關注其內部的shared::execute方法,以下:

#......
function shared::execute() {
  #一、默認FLUTTER_ROOT值爲FlutterSDK根路徑
  export FLUTTER_ROOT="$(cd "${BIN_DIR}/.." ; pwd -P)"
  #二、若是存在就先執行bootstrap腳本,默認SDK下面是沒有這個文件的,我猜是預留給咱們自定義初始化掛載用的。
  BOOTSTRAP_PATH="$FLUTTER_ROOT/bin/internal/bootstrap.sh"
  if [ -f "$BOOTSTRAP_PATH" ]; then
    source "$BOOTSTRAP_PATH"
  fi
  #三、一堆基於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"

  #四、路徑文件平臺兼容,常規操做,忽略
  case "$(uname -s)" in
    MINGW*)
      DART="$DART.exe"
      PUB="$PUB.bat"
      ;;
  esac
  #五、測試運行腳本的帳號是否爲超級帳號,是的話警告提示,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

  #六、測試git命令行環境配置是否正常,不正常就拋出錯誤。
  if ! hash git 2>/dev/null; then
    >&2 echo "Error: Unable to find git in your PATH."
    exit 1
  fi
  #七、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"
  #七、平常編譯遇到命令lock文件鎖住問題就是他,本質該方法就是建立/bin/cache目錄並維持鎖狀態等事情,不是咱們關心的重點。
  upgrade_flutter 7< "$PROG_NAME"
  #八、相關參數值,別問我怎麼知道的,問就是本身在源碼對應位置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.dartmain()方法。所以上面命令繼續簡化大體以下:

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文件,以下:

//一、以當前腳本爲座標找到當前項目根路徑
def scriptFile = getClass().protectionDomain.codeSource.location.toURI()
def flutterProjectRoot = new File(scriptFile).parentFile.parentFile
//二、導入flutter module名稱爲相對當前目錄的flutter
gradle.include ":flutter"
//三、flutter module android真正的實現位於.android/Flutter目錄下
gradle.project(":flutter").projectDir = new File(flutterProjectRoot, ".android/Flutter")
//四、前面見過了,就是獲取 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"
//五、相似以前,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.versionNameflutter.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源碼,敬請期待。

相關文章
相關標籤/搜索