写点什么

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

用户头像
工匠若水
关注
发布于: 2 小时前
Flutter Android 工程结构及应用层编译源码深入分析

背景

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

Flutter 模块的几种形式

早期版本的 Flutter 是不支持创建 Flutter Module,只有其他三种类型,想要这种类型都是靠自己造轮子和脚本实现的,现在新版本 Flutter 对于原生与 Flutter 混合模式的支持方便许多,所以目前 Flutter 支持创建如下四种模块。



这四种模块对应的项目结构大致如下,其使用场景也各不相同,我们要依据自己需要创建适合自己的模块。


Flutter 模块依赖及产物概览

当我们在 yaml 文件中添加依赖后执行flutter pub get命令就会自动从依赖配置的地方下载或复制。对于纯 Dart 依赖(Flutter Package)的下载位置在你 Flutter SDK 目录下的.pub-cache\hosted\pub.dartlang.org\dio-4.0.0位置(mac 下在自己账号目录下的.pub-cache中),以 https://pub.flutter-io.cn/packages/dio为例,这个目录下 lib 为项目主要依赖,如下:



对应在 Android Studio 中依赖展开的样子如下:



对于依赖 Flutter Plugin 下载位置在你 Flutter SDK 目录下的.pub-cache\hosted\pub.dartlang.org\webview_flutter-2.0.10位置(mac 下在自己账号目录下的.pub-cache中),以 https://pub.flutter-io.cn/packages/webview_flutter为例,这个目录下 lib 及对应平台目录为项目主要依赖,如下:



对应在 Android Studio 中依赖展开的样子如下:



对于一个 Flutter App 来说,其执行flutter build apk命令编译后的产物宏观如下:



请务必对上图产物结构有个简单的认识,因为下文源码分析的重点都是围绕怎么编译出这些东西来了。

Flutter App 安卓编译源码流程

下面我们从纯 Flutter 项目的 app 编译安卓端 apk 流程说起。

settings.gradle 源码流程分析

既然是安卓的编译流程,那就先从android/settings.gradle看起,如下:


// 当前 app moduleinclude ':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 Mapassert object.plugins instanceof Mapassert 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 = pluginDirectoryinclude ":f_plugin"project(":f_plugin").projectDir = new File("E:\\\\f_plugin\\\\", 'android')
include ":webview_flutter"project(":webview_flutter").projectDir = new File("D:\\\\software\\\\flutter\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\webview_flutter-2.0.10\\\\", 'android')
复制代码


咋说!是不是一下就恍然大悟了,其实就是“约定大于配置”的软件工程原则,你只管按照规则摆放,本质最后都是我们平时标准 Android 项目那样。

build.gradle源码流程分析

先看项目 android 下根目录的build.gradle,如下:


//......省略无关紧要的常见配置// 看到了吧,他将所有 android 依赖的构建产物挪到了根目录下的 build 中,所以产物都在那儿rootProject.buildDir = '../build'subprojects {    project.buildDir = "${rootProject.buildDir}/${project.name}"    project.evaluationDependsOn(':app') //运行其他配置之前,先运行app依赖}
复制代码


接着我们看看 app 模块下的build.gradle,如下:


/** * 1、读取local.properties配置信息。 * 2、获取flutter.sdk路径。 * 3、获取flutter.versionCode值,此值在编译时自动从pubspec.yaml中读取赋值,所以修改版本号请修改yaml。 * 4、获取flutter.versionName值,此值在编译时自动从pubspec.yaml中读取赋值,所以修改版本号请修改yaml。 */def localProperties = new Properties()def localPropertiesFile = rootProject.file('local.properties')if (localPropertiesFile.exists()) {    localPropertiesFile.withReader('UTF-8') { reader ->        localProperties.load(reader)    }}
def flutterRoot = localProperties.getProperty('flutter.sdk')if (flutterRoot == null) { throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')if (flutterVersionCode == null) { flutterVersionCode = '1'}
def flutterVersionName = localProperties.getProperty('flutter.versionName')if (flutterVersionName == null) { flutterVersionName = '1.0'}//常规操作,不解释apply plugin: 'com.android.application'apply plugin: 'kotlin-android'//重点1:apply 了 flutter SDK 下面的packages/flutter_tools/gradle/flutter.gradle脚本文件apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android { compileSdkVersion 30
sourceSets { main.java.srcDirs += 'src/main/kotlin' }
defaultConfig { applicationId "cn.yan.f1" minSdkVersion 21 targetSdkVersion 30 versionCode flutterVersionCode.toInteger() //赋值为yaml中读取的值 versionName flutterVersionName //赋值为yaml中读取的值 } //......省略常规操作,不解释}//重点2:一个拓展配置,指定source路径为当前的两级父级,也就是项目根目录flutter { source '../..'}
//......省略常规操作,不解释
复制代码


下面我们看看上面提到的重点 1,也就是 Flutter SDK 中的packages/flutter_tools/gradle/flutter.gradle,我们按照脚本运行时宏观到细节的方式来分析,如下:


//......省略一堆import头文件/** * 常规脚本配置:脚本依赖仓库及依赖的 AGP 版本 * 如果你自己没有全局配国内maven镜像,修改这里repositories也可以。 * 如果你项目对于AGP这个版本不兼容,自己修改这里然后兼容也可以。 */buildscript {    repositories {        google()        jcenter()    }    dependencies {        classpath 'com.android.tools.build:gradle:4.1.0'    }}//java8编译配置android {    compileOptions {        sourceCompatibility 1.8        targetCompatibility 1.8    }}//又 apply 了一个插件,只是这个插件源码直接定义在下方apply plugin: FlutterPlugin
//FlutterPlugin插件实现源码,参考标准插件写法一样,基本语法不解释,这里重点看逻辑。class FlutterPlugin implements Plugin<Project> { //...... //重点入口!!!!!! @Override void apply(Project project) { this.project = project
//1、配置maven仓库地址,环境变量有配置FLUTTER_STORAGE_BASE_URL就优先用,没就缺省 String hostedRepository = System.env.FLUTTER_STORAGE_BASE_URL ?: DEFAULT_MAVEN_HOST String repository = useLocalEngine() ? project.property('local-engine-repo') : "$hostedRepository/download.flutter.io" project.rootProject.allprojects { repositories { maven { url repository } } } //2、创建app模块中配置的flutter{ source: '../../'}闭包extensions project.extensions.create("flutter", FlutterExtension) //3、添加flutter构建相关的各种task this.addFlutterTasks(project)
//4、判断编译命令flutter build apk --split-per-abi是否添加--split-per-abi参数,有的话就拆分成多个abi包。 if (shouldSplitPerAbi()) { project.android { splits { abi { // Enables building multiple APKs per ABI. enable true // Resets the list of ABIs that Gradle should create APKs for to none. reset() // Specifies that we do not want to also generate a universal APK that includes all ABIs. universalApk false } } } } //5、判断编译命令是否添加deferred-component-names参数,有就配置android dynamicFeatures bundle特性。 if (project.hasProperty('deferred-component-names')) { String[] componentNames = project.property('deferred-component-names').split(',').collect {":${it}"} project.android { dynamicFeatures = componentNames } } //6、判断编译命令是否添加--target-platform=xxxABI参数,没有就用缺省,有就看这个ABI是否flutter支持的,支持就配置,否则抛出异常。 getTargetPlatforms().each { targetArch -> String abiValue = PLATFORM_ARCH_MAP[targetArch] project.android { if (shouldSplitPerAbi()) { splits { abi { include abiValue } } } } } //7、通过属性配置获取flutter.sdk,或者通过环境变量FLUTTER_ROOT获取,都没有就抛出环境异常。 String flutterRootPath = resolveProperty("flutter.sdk", System.env.FLUTTER_ROOT) if (flutterRootPath == null) { throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file or with a FLUTTER_ROOT environment variable.") } flutterRoot = project.file(flutterRootPath) if (!flutterRoot.isDirectory()) { throw new GradleException("flutter.sdk must point to the Flutter SDK directory") } //8、获取Flutter Engine的版本号,如果通过local-engine-repo参数使用本地自己编译的Engine则版本为+,否则读取SDK目录下bin\internal\engine.version文件值,一串类似MD5的值。 engineVersion = useLocalEngine() ? "+" // Match any version since there's only one. : "1.0.0-" + Paths.get(flutterRoot.absolutePath, "bin", "internal", "engine.version").toFile().text.trim() //9、依据平台获取对应flutter命令脚本,都位于SDK目录下bin\中,名字为flutter String flutterExecutableName = Os.isFamily(Os.FAMILY_WINDOWS) ? "flutter.bat" : "flutter" flutterExecutable = Paths.get(flutterRoot.absolutePath, "bin", flutterExecutableName).toFile(); //10、获取flutter混淆配置清单,位于SDK路径下packages\flutter_tools\gradle\flutter_proguard_rules.pro。 //里面配置只有 -dontwarn io.flutter.plugin.** 和 -dontwarn android.** String flutterProguardRules = Paths.get(flutterRoot.absolutePath, "packages", "flutter_tools", "gradle", "flutter_proguard_rules.pro") project.android.buildTypes { //11、新增profile构建类型,在当前project下的android.buildTypes中进行配置 profile { initWith debug //initWith操作复制所有debug里面的属性 if (it.hasProperty("matchingFallbacks")) { matchingFallbacks = ["debug", "release"] } } //...... } //...... //12、给所有buildTypes添加依赖,addFlutterDependencies project.android.buildTypes.all this.&addFlutterDependencies } //......}//flutter{}闭包Extension定义class FlutterExtension { String source String target}//......
复制代码


可以看到,上面脚本的本质是一个标准插件,其内部主要就是基于我们传递的参数进行一些配置。上面的步骤 4 的表现看产物,这里不再演示。步骤 11 其实就是新增了一种编译类型,对应项目中就是性能模式,如下:



步骤 12 对应追加依赖的脚本如下:


/** * 给每个buildType添加Flutter项目的dependencies依赖,主要包括embedding和libflutter.so */void addFlutterDependencies(buildType) {  //获取build类型,值为debug、profile、release    String flutterBuildMode = buildModeFor(buildType)    //对使用本地Engine容错,官方Engine忽略这个条件即可,继续往下    if (!supportsBuildMode(flutterBuildMode)) {        return    }    //如果插件不是applicationVariants类型,即android library,或者项目根目录下`.flutter-plugins`文件中安卓插件个数为空。    if (!isFlutterAppProject() || getPluginList().size() == 0) {      //简单理解就是给Flutter Plugin的android插件添加编译依赖      //譬如io.flutter:flutter_embedding_debug:1.0.0,来自maven仓库        addApiDependencies(project, buildType.name,                "io.flutter:flutter_embedding_$flutterBuildMode:$engineVersion")    }    //给project添加对应编译依赖    //譬如io.flutter:arm64_v8a_debug:1.0.0,来自maven仓库    List<String> platforms = getTargetPlatforms().collect()    // Debug mode includes x86 and x64, which are commonly used in emulators.    if (flutterBuildMode == "debug" && !useLocalEngine()) {        platforms.add("android-x86")        platforms.add("android-x64")    }    platforms.each { platform ->        String arch = PLATFORM_ARCH_MAP[platform].replace("-", "_")        // Add the `libflutter.so` dependency.        addApiDependencies(project, buildType.name,                "io.flutter:${arch}_$flutterBuildMode:$engineVersion")    }}
private static void addApiDependencies(Project project, String variantName, Object dependency, Closure config = null) { String configuration; // `compile` dependencies are now `api` dependencies. if (project.getConfigurations().findByName("api")) { configuration = "${variantName}Api"; } else { configuration = "${variantName}Compile"; } project.dependencies.add(configuration, dependency, config)}
复制代码


上面这段脚本的本质就是给 Flutter 项目自动添加编译依赖,这个依赖本质也是 maven 仓库的,很像我们自己编写 gradle 中添加的 okhttp 等依赖,没啥区别。譬如我们创建的 demo 项目导入 Android Studio 后自动 sync 的 dependencies 依赖如下:



接下来我们把重心放回步骤 3(addFlutterTasks),这才是我们整个 Flutter app 编译的重点,也是最复杂的部分,如下:


private void addFlutterTasks(Project project) {  //gradle项目配置评估失败则返回,常规操作,忽略    if (project.state.failure) {        return    }    //1、一堆属性获取与赋值操作    String[] fileSystemRootsValue = null    if (project.hasProperty('filesystem-roots')) {        fileSystemRootsValue = project.property('filesystem-roots').split('\\|')    }    String fileSystemSchemeValue = null    if (project.hasProperty('filesystem-scheme')) {        fileSystemSchemeValue = project.property('filesystem-scheme')    }    Boolean trackWidgetCreationValue = true    if (project.hasProperty('track-widget-creation')) {        trackWidgetCreationValue = project.property('track-widget-creation').toBoolean()    }    String extraFrontEndOptionsValue = null    if (project.hasProperty('extra-front-end-options')) {        extraFrontEndOptionsValue = project.property('extra-front-end-options')    }    String extraGenSnapshotOptionsValue = null    if (project.hasProperty('extra-gen-snapshot-options')) {        extraGenSnapshotOptionsValue = project.property('extra-gen-snapshot-options')    }    String splitDebugInfoValue = null    if (project.hasProperty('split-debug-info')) {        splitDebugInfoValue = project.property('split-debug-info')    }    Boolean dartObfuscationValue = false    if (project.hasProperty('dart-obfuscation')) {        dartObfuscationValue = project.property('dart-obfuscation').toBoolean();    }    Boolean treeShakeIconsOptionsValue = false    if (project.hasProperty('tree-shake-icons')) {        treeShakeIconsOptionsValue = project.property('tree-shake-icons').toBoolean()    }    String dartDefinesValue = null    if (project.hasProperty('dart-defines')) {        dartDefinesValue = project.property('dart-defines')    }    String bundleSkSLPathValue;    if (project.hasProperty('bundle-sksl-path')) {        bundleSkSLPathValue = project.property('bundle-sksl-path')    }    String performanceMeasurementFileValue;    if (project.hasProperty('performance-measurement-file')) {        performanceMeasurementFileValue = project.property('performance-measurement-file')    }    String codeSizeDirectoryValue;    if (project.hasProperty('code-size-directory')) {        codeSizeDirectoryValue = project.property('code-size-directory')    }    Boolean deferredComponentsValue = false    if (project.hasProperty('deferred-components')) {        deferredComponentsValue = project.property('deferred-components').toBoolean()    }    Boolean validateDeferredComponentsValue = true    if (project.hasProperty('validate-deferred-components')) {        validateDeferredComponentsValue = project.property('validate-deferred-components').toBoolean()    }    def targetPlatforms = getTargetPlatforms()    ......}
复制代码


可以看到,addFlutterTasks 方法的第一部分比较简单,基本都是从 Project 中读取各自配置属性供后续步骤使用。所以我们接着继续看 addFlutterTasks 这个方法步骤 1 之后的部分:


private void addFlutterTasks(Project project) {    //一堆属性获取与赋值操作    //......    //1、定义 addFlutterDeps 箭头函数,参数variant为标准构建对应的构建类型    def addFlutterDeps = { variant ->        if (shouldSplitPerAbi()) {          //2、常规操作:如果是构建多个变体apk模式就处理vc问题            variant.outputs.each { output ->                //由于GP商店不允许同一个应用的多个APK全都具有相同的版本信息,因此在上传到Play商店之前,您需要确保每个APK都有自己唯一的versionCode,这里就是做这个事情的。                //具体可以看官方文档 https://developer.android.com/studio/build/configure-apk-splits                def abiVersionCode = ABI_VERSION.get(output.getFilter(OutputFile.ABI))                if (abiVersionCode != null) {                    output.versionCodeOverride =                        abiVersionCode * 1000 + variant.versionCode                }            }        }        //3、获取编译类型,variantBuildMode值为debug、profile、release之一        String variantBuildMode = buildModeFor(variant.buildType)        //4、依据参数生成一个task名字,譬如这里的compileFlutterBuildDebug、compileFlutterBuildProfile、compileFlutterBuildRelease        String taskName = toCammelCase(["compile", FLUTTER_BUILD_PREFIX, variant.name])        //5、给当前project创建compileFlutterBuildDebug、compileFlutterBuildProfile、compileFlutterBuildRelease Task        //实现为FlutterTask,主要用来编译Flutter代码,这个task稍后单独分析        FlutterTask compileTask = project.tasks.create(name: taskName, type: FlutterTask) {          //各种task属性赋值操作,基本都来自上面的属性获取或者匹配分析            flutterRoot this.flutterRoot            flutterExecutable this.flutterExecutable            buildMode variantBuildMode            localEngine this.localEngine            localEngineSrcPath this.localEngineSrcPath            //默认dart入口lib/main.dart、可以通过target属性自定义指向            targetPath getFlutterTarget()            verbose isVerbose()            fastStart isFastStart()            fileSystemRoots fileSystemRootsValue            fileSystemScheme fileSystemSchemeValue            trackWidgetCreation trackWidgetCreationValue            targetPlatformValues = targetPlatforms            sourceDir getFlutterSourceDirectory()            //学到一个小技能,原来中间API是AndroidProject.FD_INTERMEDIATES,这也是flutter中间产物目录            intermediateDir project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter/${variant.name}/")            extraFrontEndOptions extraFrontEndOptionsValue            extraGenSnapshotOptions extraGenSnapshotOptionsValue            splitDebugInfo splitDebugInfoValue            treeShakeIcons treeShakeIconsOptionsValue            dartObfuscation dartObfuscationValue            dartDefines dartDefinesValue            bundleSkSLPath bundleSkSLPathValue            performanceMeasurementFile performanceMeasurementFileValue            codeSizeDirectory codeSizeDirectoryValue            deferredComponents deferredComponentsValue            validateDeferredComponents validateDeferredComponentsValue            //最后做一波权限相关处理            doLast {                project.exec {                    if (Os.isFamily(Os.FAMILY_WINDOWS)) {                        commandLine('cmd', '/c', "attrib -r ${assetsDirectory}/* /s")                    } else {                        commandLine('chmod', '-R', 'u+w', assetsDirectory)                    }                }            }        }        //项目构建中间产物的文件,也就是根目录下build/intermediates/flutter/debug/libs.jar文件        File libJar = project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter/${variant.name}/libs.jar")        //6、创建packLibsFlutterBuildProfile、packLibsFlutterBuildDebug、packLibsFlutterBuildRelease任务,主要是产物的复制挪位置操作,Jar 类型的 task        //作用就是把build/intermediates/flutter/debug/下依据abi生成的app.so通过jar命令打包成build/intermediates/flutter/debug/libs.jar        Task packFlutterAppAotTask = project.tasks.create(name: "packLibs${FLUTTER_BUILD_PREFIX}${variant.name.capitalize()}", type: Jar) {          //目标路径为build/intermediates/flutter/debug目录            destinationDir libJar.parentFile            //文件名为libs.jar            archiveName libJar.name            //依赖前面步骤5定义的compileFlutterBuildDebug,也就是说,这个task基本作用是产物处理            dependsOn compileTask            //targetPlatforms取值为android-arm、android-arm64、android-x86、android-x64            targetPlatforms.each { targetPlatform ->              //abi取值为armeabi-v7a、arm64-v8a、x86、x86_64                String abi = PLATFORM_ARCH_MAP[targetPlatform]                //数据来源来自步骤5的compileFlutterBuildDebug任务中间产物目录                //即把build/intermediates/flutter/debug/下依据abi生成的app.so通过jar命令打包成一个build/intermediates/flutter/debug/libs.jar文件                from("${compileTask.intermediateDir}/${abi}") {                    include "*.so"                    // Move `app.so` to `lib/<abi>/libapp.so`                    rename { String filename ->                        return "lib/${abi}/lib${filename}"                    }                }            }        }        //前面有介绍过addApiDependencies作用,把 packFlutterAppAotTask 产物加到依赖项里面参与编译        //类似implementation files('libs.jar'),然后里面的so会在项目执行标准mergeDebugNativeLibs task时打包进标准lib目录        addApiDependencies(project, variant.name, project.files {            packFlutterAppAotTask        })        // 当构建有is-plugin属性时则编译aar        boolean isBuildingAar = project.hasProperty('is-plugin')        //7、当是Flutter Module方式,即Flutter以aar作为已存在native安卓项目依赖时才有这些:flutter:模块依赖,否则没有这些task        //可以参见新建的FlutterModule中.android/include_flutter.groovy中gradle.project(":flutter").projectDir实现        Task packageAssets = project.tasks.findByPath(":flutter:package${variant.name.capitalize()}Assets")        Task cleanPackageAssets = project.tasks.findByPath(":flutter:cleanPackage${variant.name.capitalize()}Assets")        //判断是否为FlutterModule依赖        boolean isUsedAsSubproject = packageAssets && cleanPackageAssets && !isBuildingAar        //8、新建copyFlutterAssetsDebug task,目的就是copy产物,也就是assets归档        //常规merge中间产物类似,不再过多解释,就是把步骤5 task产物的assets目录在mergeAssets时复制到主包中间产物目录        Task copyFlutterAssetsTask = project.tasks.create(            name: "copyFlutterAssets${variant.name.capitalize()}",            type: Copy,        ) {            dependsOn compileTask            with compileTask.assets            if (isUsedAsSubproject) {                dependsOn packageAssets                dependsOn cleanPackageAssets                into packageAssets.outputDir                return            }            // `variant.mergeAssets` will be removed at the end of 2019.            def mergeAssets = variant.hasProperty("mergeAssetsProvider") ?                variant.mergeAssetsProvider.get() : variant.mergeAssets            dependsOn mergeAssets            dependsOn "clean${mergeAssets.name.capitalize()}"            mergeAssets.mustRunAfter("clean${mergeAssets.name.capitalize()}")            into mergeAssets.outputDir        }        if (!isUsedAsSubproject) {            def variantOutput = variant.outputs.first()            def processResources = variantOutput.hasProperty("processResourcesProvider") ?                variantOutput.processResourcesProvider.get() : variantOutput.processResources            processResources.dependsOn(copyFlutterAssetsTask)        }        return copyFlutterAssetsTask    } // end def addFlutterDeps  ......}
复制代码


上面这段比较直观,步骤 5 细节我们后面会分析这个 FlutterTask;对于步骤 6 其实也蛮直观,我们执行 flutter build apk 后看产物目录如下:



这个 jar 也是重点,它里面其实不是 class,而是上图中的 abi 对应 app.so,也就是 dart app 编译的 so。所以 libs.jar 解压如下:



这货会被类似 implementation files('libs.jar') 添加进我们 project 的编译依赖项中,然后里面的 so 会在项目执行标准 mergeDebugNativeLibs task 时打包进标准 lib 目录,所以最终 apk 中 app.so 位于 lib 目录下(好奇反思:官方这里为什么不直接弄成 aar,而是把 so 打进 jar,感觉回到了 eclipse 时代,没整明白为什么)。


对于步骤 8 来说,assets 合并复制操作在 app 主包的中间产物中效果如下:



因此,步骤 6、步骤 8 的产物最终编译后就是 apk 中对应的东西,对应 apk 解压如下:



上面步骤 5 中的 FlutterTask 我们先放一放,让我们先继续看 addFlutterTasks 这个方法剩下的部分:


private void addFlutterTasks(Project project) {    //......上面已分析,下面接续分析    //1、如果是applicationVariants就走进去,也就是说project是app module    if (isFlutterAppProject()) {        project.android.applicationVariants.all { variant ->          //也就是assemble task咯            Task assembleTask = getAssembleTask(variant)            //正常容错,不用关心            if (!shouldConfigureFlutterTask(assembleTask)) {              return            }            //把前面定义的addFlutterDeps函数调用返回的copyFlutterAssetsTask任务拿到作为依赖项            //这货的作用和产物前面已经图示贴了产物            Task copyFlutterAssetsTask = addFlutterDeps(variant)            def variantOutput = variant.outputs.first()            def processResources = variantOutput.hasProperty("processResourcesProvider") ?                variantOutput.processResourcesProvider.get() : variantOutput.processResources            processResources.dependsOn(copyFlutterAssetsTask)
//2、执行flutter run或者flutter build apk的产物apk归档处理 //不多解释,下面会图解说明 variant.outputs.all { output -> assembleTask.doLast { // `packageApplication` became `packageApplicationProvider` in AGP 3.3.0. def outputDirectory = variant.hasProperty("packageApplicationProvider") ? variant.packageApplicationProvider.get().outputDirectory : variant.packageApplication.outputDirectory // `outputDirectory` is a `DirectoryProperty` in AGP 4.1. String outputDirectoryStr = outputDirectory.metaClass.respondsTo(outputDirectory, "get") ? outputDirectory.get() : outputDirectory String filename = "app" String abi = output.getFilter(OutputFile.ABI) if (abi != null && !abi.isEmpty()) { filename += "-${abi}" } if (variant.flavorName != null && !variant.flavorName.isEmpty()) { filename += "-${variant.flavorName.toLowerCase()}" } filename += "-${buildModeFor(variant.buildType)}" project.copy { from new File("$outputDirectoryStr/${output.outputFileName}") into new File("${project.buildDir}/outputs/flutter-apk"); rename { return "${filename}.apk" } } } } } //3、小重点 configurePlugins() return } //3、是不是模块源码依赖方式集成到现有项目,参见 https://flutter.cn/docs/development/add-to-app/android/project-setup //是的话对模块也做类似一堆处理即可,不再重复分析了,也是 assets 合并 String hostAppProjectName = project.rootProject.hasProperty('flutter.hostAppProjectName') ? project.rootProject.property('flutter.hostAppProjectName') : "app" Project appProject = project.rootProject.findProject(":${hostAppProjectName}") assert appProject != null : "Project :${hostAppProjectName} doesn't exist. To custom the host app project name, set `org.gradle.project.flutter.hostAppProjectName=<project-name>` in gradle.properties." // Wait for the host app project configuration. appProject.afterEvaluate { assert appProject.android != null project.android.libraryVariants.all { libraryVariant -> Task copyFlutterAssetsTask appProject.android.applicationVariants.all { appProjectVariant -> Task appAssembleTask = getAssembleTask(appProjectVariant) if (!shouldConfigureFlutterTask(appAssembleTask)) { return } // Find a compatible application variant in the host app. // // For example, consider a host app that defines the following variants: // | ----------------- | ----------------------------- | // | Build Variant | Flutter Equivalent Variant | // | ----------------- | ----------------------------- | // | freeRelease | release | // | freeDebug | debug | // | freeDevelop | debug | // | profile | profile | // | ----------------- | ----------------------------- | // // This mapping is based on the following rules: // 1. If the host app build variant name is `profile` then the equivalent // Flutter variant is `profile`. // 2. If the host app build variant is debuggable // (e.g. `buildType.debuggable = true`), then the equivalent Flutter // variant is `debug`. // 3. Otherwise, the equivalent Flutter variant is `release`. String variantBuildMode = buildModeFor(libraryVariant.buildType) if (buildModeFor(appProjectVariant.buildType) != variantBuildMode) { return } if (copyFlutterAssetsTask == null) { copyFlutterAssetsTask = addFlutterDeps(libraryVariant) } Task mergeAssets = project .tasks .findByPath(":${hostAppProjectName}:merge${appProjectVariant.name.capitalize()}Assets") assert mergeAssets mergeAssets.dependsOn(copyFlutterAssetsTask) } } } configurePlugins()}
复制代码


上面这段代码分析中的步骤 2 本质就是对标准安卓构建产物进行一次重新按照格式归档,如果是 split api 模式就能很直观看出来效果,下面图示是直接运行 flutter build apk 的步骤 2 效果:



对于上面代码片段中的步骤 3,我们可以详细来分析下:


/** * flutter的依赖都添加在pubspec.yaml中 * 接着都会执行flutter pub get,然后工具会生成跟目录下.flutter-plugins等文件 * 这里做的事情就是帮忙给module自动添加上这些插件dependencies依赖模块 */private void configurePlugins() {    if (!buildPluginAsAar()) {      //项目根目录下的.flutter-plugins文件        getPluginList().each this.&configurePluginProject        //项目根目录下的.flutter-plugins-dependencies文件        getPluginDependencies().each this.&configurePluginDependencies        return    }    project.repositories {        maven {            url "${getPluginBuildDir()}/outputs/repo"        }    }    getPluginList().each { pluginName, pluginPath ->        configurePluginAar(pluginName, pluginPath, project)    }}
复制代码


到此整个 addFlutterTasks 核心方法我们就分析完毕。接下来让我们把目光转向 FlutterTask 的实现,Task 机制不懂就自己去补习 gradle 基础吧,重点入口就是 @TaskAction,如下(比较长,但是比较直观简单):


abstract class BaseFlutterTask extends DefaultTask {    //......一堆task属性声明,忽略
@OutputFiles FileCollection getDependenciesFiles() { FileCollection depfiles = project.files()
// Includes all sources used in the flutter compilation. depfiles += project.files("${intermediateDir}/flutter_build.d") return depfiles } //重点!!!!!!!!!!!!!!!!!!!!! //整个flutter android编译的核心实现在此!!!! void buildBundle() { if (!sourceDir.isDirectory()) { throw new GradleException("Invalid Flutter source directory: ${sourceDir}") } //1、默认以app为例创建build/app/intermediates/flutter目录 intermediateDir.mkdirs()
//2、计算flutter assemble的规则名称列表 String[] ruleNames; if (buildMode == "debug") { ruleNames = ["debug_android_application"] } else if (deferredComponents) { ruleNames = targetPlatformValues.collect { "android_aot_deferred_components_bundle_${buildMode}_$it" } } else { ruleNames = targetPlatformValues.collect { "android_aot_bundle_${buildMode}_$it" } } //3、重点执行命令 project.exec { logging.captureStandardError LogLevel.ERROR //4、windows的话就是flutter SDK路径下 bin/flutter.bat文件,unix就是bin/flutter executable flutterExecutable.absolutePath //5、我们app的build.gradle中配置的flutter { source '../../' }闭包,路径,也就是项目根目录下 workingDir sourceDir //6、使用本地自己编译的flutter engine才需要的参数 if (localEngine != null) { args "--local-engine", localEngine args "--local-engine-src-path", localEngineSrcPath } //7、类似标准gradle构建参数打印控制 if (verbose) { args "--verbose" } else { args "--quiet" } //8、追加一堆编译参数 args "assemble" args "--no-version-check" args "--depfile", "${intermediateDir}/flutter_build.d" //flutter 编译产物输出路径 args "--output", "${intermediateDir}" if (performanceMeasurementFile != null) { args "--performance-measurement-file=${performanceMeasurementFile}" } //Flutter dart程序入口,默认为lib/main.dart if (!fastStart || buildMode != "debug") { args "-dTargetFile=${targetPath}" } else { args "-dTargetFile=${Paths.get(flutterRoot.absolutePath, "examples", "splash", "lib", "main.dart")}" } args "-dTargetPlatform=android" args "-dBuildMode=${buildMode}" if (trackWidgetCreation != null) { args "-dTrackWidgetCreation=${trackWidgetCreation}" } if (splitDebugInfo != null) { args "-dSplitDebugInfo=${splitDebugInfo}" } if (treeShakeIcons == true) { args "-dTreeShakeIcons=true" } if (dartObfuscation == true) { args "-dDartObfuscation=true" } if (dartDefines != null) { args "--DartDefines=${dartDefines}" } if (bundleSkSLPath != null) { args "-iBundleSkSLPath=${bundleSkSLPath}" } if (codeSizeDirectory != null) { args "-dCodeSizeDirectory=${codeSizeDirectory}" } if (extraGenSnapshotOptions != null) { args "--ExtraGenSnapshotOptions=${extraGenSnapshotOptions}" } if (extraFrontEndOptions != null) { args "--ExtraFrontEndOptions=${extraFrontEndOptions}" } args ruleNames } }}
class FlutterTask extends BaseFlutterTask { //默认以app为例则为build/app/intermediates/flutter目录。 @OutputDirectory File getOutputDirectory() { return intermediateDir } //默认以app为例则为build/app/intermediates/flutter/flutter_assets目录,前面我们已经截图展示过这个目录产物。 @Internal String getAssetsDirectory() { return "${outputDirectory}/flutter_assets" } //assets复制操作定义,intermediateDir就是getOutputDirectory路径 @Internal CopySpec getAssets() { return project.copySpec { from "${intermediateDir}" include "flutter_assets/**" // the working dir and its files } } //dart编译的产物复制操作定义(注意:release和profile模式才是so产物),intermediateDir就是getOutputDirectory路径 @Internal CopySpec getSnapshots() { return project.copySpec { from "${intermediateDir}"
if (buildMode == 'release' || buildMode == 'profile') { targetPlatformValues.each { include "${PLATFORM_ARCH_MAP[targetArch]}/app.so" } } } } //依赖格式解析生成文件路径集合 FileCollection readDependencies(File dependenciesFile, Boolean inputs) { if (dependenciesFile.exists()) { // Dependencies file has Makefile syntax: // <target> <files>: <source> <files> <separated> <by> <non-escaped space> String depText = dependenciesFile.text // So we split list of files by non-escaped(by backslash) space, def matcher = depText.split(': ')[inputs ? 1 : 0] =~ /(\\ |[^\s])+/ // then we replace all escaped spaces with regular spaces def depList = matcher.collect{it[0].replaceAll("\\\\ ", " ")} return project.files(depList) } return project.files(); } //输入源为所有依赖模块的pubspec.yaml文件集合 @InputFiles FileCollection getSourceFiles() { FileCollection sources = project.files() for (File depfile in getDependenciesFiles()) { sources += readDependencies(depfile, true) } return sources + project.files('pubspec.yaml') }
@OutputFiles FileCollection getOutputFiles() { FileCollection sources = project.files() for (File depfile in getDependenciesFiles()) { sources += readDependencies(depfile, false) } return sources } //重点实现!!!!!!! @TaskAction void build() { buildBundle() }}
复制代码


可以很直观的看到,整个构建编译的核心都是通过执行 Flutter SDK 中 bin 目录下的 flutter 脚本完成的,大段代码只是为了为执行这个脚本准备参数配置信息。也就是说 flutter 编译本质命令大致如下:


flutter assemble --no-version-check \--depfile build/app/intermediates/flutter/release/flutter_build.d \--output build/app/intermediates/flutter/release/ \-dTargetFile=lib/main.dart \-dTargetPlatform=android \-dBuildMode=release \-dDartObfuscation=true \android_aot_bundle_release_android-arm \android_aot_bundle_release_android-arm64 \android_aot_bundle_release_android-x86 \android_aot_bundle_release_android-x64
复制代码


这就走到了 SDK 里面的纯 flutter 命令脚本了。

Flutter SDK 下bin/flutter编译命令分析

承接上面分析,上一小节最后的命令本质就是本小节的脚本,我们把目光转向 Flutter SDK 中 bin 目录下的 flutter 脚本,如下:


#!/usr/bin/env bash#1、该命令之后出现的代码,一旦出现了返回值非零,整个脚本就会立即退出,那么就可以避免一些脚本的危险操作。set -e#2、清空CDPATH变量值unset CDPATH
# 在Mac上,readlink -f不起作用,因此follow_links一次遍历一个链接的路径,然后遍历cd进入链接目的地并找出它。# 返回的文件系统路径必须是Dart的URI解析器可用的格式,因为Dart命令行工具将其参数视为文件URI,而不是文件名。# 例如,多个连续的斜杠应该减少为一个斜杠,因为双斜杠表示URI的authority。function follow_links() ( cd -P "$(dirname -- "$1")" file="$PWD/$(basename -- "$1")" while [[ -h "$file" ]]; do cd -P "$(dirname -- "$file")" file="$(readlink -- "$file")" cd -P "$(dirname -- "$file")" file="$PWD/$(basename -- "$file")" done echo "$file")# 这个变量的值就是Flutter SDK根目录下的bin/flutterPROG_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 Module 安卓编译流程

对于原生现有工程集成 flutter 来说,flutter module 就是最好的隔离选择,这也就造就了其与 flutter app 在编译上的一些差异与共性。这部分我们重点分析 flutter module 与 上面分析的 app 编译流程差异,共性部分不再分析。


同样先从.android/settings.gradle看起来:


// app 是测试 module,用来验证 flutter module 的,本质最后 flutter module 会生成可集成的 aarinclude ':app'//导入配置.android/include_flutter.groovyrootProject.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名称为相对当前目录的fluttergradle.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: f1description: A new f1 project.# 想要发布的位置,删除就是发布到pub.devpublish_to: 'none'# 版本号,修改这里后会自动修改安卓项目下local.properties文件中的versionName、versionCodeversion: 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源码,敬请期待。

发布于: 2 小时前阅读数: 5
用户头像

工匠若水

关注

专注于移动互联网及嵌入式领域。 2018.05.02 加入

微信公众号:码农每日一题 个人微信号:codedeveloper 写博客不赚钱,就图交个朋友,欢迎来撩,微信更精彩。

评论

发布
暂无评论
Flutter Android 工程结构及应用层编译源码深入分析