Flutter Android 工程结构及应用层编译源码深入分析

背景

本文部分配图及源码最近基于 Flutter 2.2.3 版本进行了修正更新发布。目的是为了弄清 Flutter 在安卓端应用层的整个编译来龙去脉,以便编译过程中出任何问题都能做到心里有数,另一个目的是为了能够在应用层定制 Flutter 编译。全文比较长,图文并茂,由工程结构深入到源码解析。

Flutter 模块的几种形式

早期版本的 Flutter 是不支持创建 Flutter Module,只有其他三种类型,想要这种类型都是靠自己造轮子和脚本实现的,现在新版本 Flutter 对于原生与 Flutter 混合模式的支持方便许多,所以目前 Flutter 支持创建如下四种模块。
Flutter Android 工程结构及应用层编译源码深入分析_第1张图片
这四种模块对应的项目结构大致如下,其使用场景也各不相同,我们要依据自己需要创建适合自己的模块。
Flutter Android 工程结构及应用层编译源码深入分析_第2张图片

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 为项目主要依赖,如下:
Flutter Android 工程结构及应用层编译源码深入分析_第3张图片
对应在 Android Studio 中依赖展开的样子如下:
Flutter Android 工程结构及应用层编译源码深入分析_第4张图片
对于依赖 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 及对应平台目录为项目主要依赖,如下:
Flutter Android 工程结构及应用层编译源码深入分析_第5张图片
对应在 Android Studio 中依赖展开的样子如下:
Flutter Android 工程结构及应用层编译源码深入分析_第6张图片
对于一个 Flutter App 来说,其执行flutter build apk命令编译后的产物宏观如下:
Flutter Android 工程结构及应用层编译源码深入分析_第7张图片
请务必对上图产物结构有个简单的认识,因为下文源码分析的重点都是围绕怎么编译出这些东西来了。

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 {
    //......
    //重点入口!!!!!!
    @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 其实就是新增了一种编译类型,对应项目中就是性能模式,如下:
Flutter Android 工程结构及应用层编译源码深入分析_第8张图片
步骤 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 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 依赖如下:
Flutter Android 工程结构及应用层编译源码深入分析_第9张图片
接下来我们把重心放回步骤 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//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 后看产物目录如下:
Flutter Android 工程结构及应用层编译源码深入分析_第10张图片
这个 jar 也是重点,它里面其实不是 class,而是上图中的 abi 对应 app.so,也就是 dart app 编译的 so。所以 libs.jar 解压如下:
Flutter Android 工程结构及应用层编译源码深入分析_第11张图片
这货会被类似 implementation files('libs.jar') 添加进我们 project 的编译依赖项中,然后里面的 so 会在项目执行标准 mergeDebugNativeLibs task 时打包进标准 lib 目录,所以最终 apk 中 app.so 位于 lib 目录下(好奇反思:官方这里为什么不直接弄成 aar,而是把 so 打进 jar,感觉回到了 eclipse 时代,没整明白为什么)。

对于步骤 8 来说,assets 合并复制操作在 app 主包的中间产物中效果如下:
Flutter Android 工程结构及应用层编译源码深入分析_第12张图片
因此,步骤 6、步骤 8 的产物最终编译后就是 apk 中对应的东西,对应 apk 解压如下:
Flutter Android 工程结构及应用层编译源码深入分析_第13张图片
上面步骤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=` 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 效果:
Flutter Android 工程结构及应用层编译源码深入分析_第14张图片
对于上面代码片段中的步骤 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:
        //    :     
        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.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 Android 工程结构及应用层编译源码深入分析_第15张图片

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.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源码,敬请期待。

你可能感兴趣的:(Flutter Android 工程结构及应用层编译源码深入分析)