揭开Flutter工程编译的面纱(Android篇)

深入剖析Flutter工程如何混编进入Android工程,从脚本文件解析到编译产物打包全过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、引言

本文主要对Flutter工程编译时如何把Flutter生成的产物打包进入Android工程中进行分析。在Flutter工中打包过程中涉及到了local.properties、settings.gradle、build.gradle、flutter.gradle这几个脚本文件的参与,与传统的Android工程相比这几个脚本文件中的内容也不相同,接下来我们通过一层层解析,解开Flutter工程编译的面纱。 同时也建议大家看的时候搭配Flutter工程一起食用效果更佳。

二、工程结构分析

首先我们创建了一个最普通的Flutter工程flutter_new,创建后整个工程的目录结构如下:
在这里插入图片描述

Flutter工程下包括了Android和IOS两个目录,分别用于运行在Android和IOS平台上。其中android目录结果与Android工程的目录结构是一样的。Flutter工程中的android目录下包含了两个工程:第一个是android根工程,第二个是app子工程
在这里插入图片描述
稍微有点不同的地方在于这两个工程的的输出目录搬到了Flutter工程根目录build下:
在这里插入图片描述
Flutter工程中android工程与传统的Android工程相比,都有.gradle、gradle、 setting.gradlew、gradlew等目录和文件,文件基本上都是一样的,在但是在local.properties、settings.gradle、build.gradle文件内容上又有所不同,接下来我们我们会一一做对比。

三、local.properties

在这里插入图片描述
在根工程下的local.properties文件中了多了Flutter SDK相关的配置信息,包括SDK的路径、版本名、版本号,这些信息在构建工程的过程自动从环境变量中获取的,我们无需手动配置。
在这里插入图片描述

四、根工程settings.gradle

如果你有配置Flutter工程根目录下.flutter-plugins这个文件,那么下面的操作就会把flutter插件用到的第三方工程include到当前的工程中,并为其配置工程的路径 projectDir:

include ':app'

//1、根工程的父目录,既Flutter工程目录
def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()

//2、把Flutte工程下.flutter-plugins文件内容读取到内存中
def plugins = new Properties()
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
if (pluginsFile.exists()) {
   
   
    pluginsFile.withReader('UTF-8') {
   
    reader -> plugins.load(reader) }
}
//3、把.flutter-plugins文件中配置的flutter插件工程包含到当前工程中
plugins.each {
   
    name, path ->
    def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
    include ":$name"
    project(":$name").projectDir = pluginDirectory
}

然,我们新创建的Flutter工程默认是没有.flutter-plugins这个文件的,所以上面的代码基本不走。

五、根工程build.gradle

根工程中的build.grandle主要的工作是重新配置了工程的输出目录 和 工程间配置执行时的依赖关系:

buildscript {
   
   //...}

allprojects {
   
   //...}
 
//上面的代码是基本一样的

//第一点
rootProject.buildDir = '../build'  //根工程输出路径
subprojects {
   
     //所有子工程输出路径
    project.buildDir = "${rootProject.buildDir}/${project.name}"
}
//第二点
subprojects {
   
   
    project.evaluationDependsOn(':app')  //为所有子工程配置app的依赖
}

//第三点
task clean(type: Delete) {
   
   
    delete rootProject.buildDir
}

第一点:配置了根工程 和 其所有子工程的输出路径。把所有的输出路径都搬到了Flutter根目录下的build目录中,如果是子工程则在build目录再建立属于自己名称的输出目录。可以看下面这张图:
在这里插入图片描述

第二点:所有子工程都配置app子工程的依赖,既让所有子工程运行配置阶段开始之前都要保证app工程的配置阶段都已经运行完毕。这样做的好处就是保证app工程的配置属性优先导入,防止其他子工程出现属性找不到的问题发生

第三点: 为根工程添加clean任务用来删除build目录下所有文件。

关于Project#evaluationDependsOn方法

evaluationDependsOn用于配置Project对象之间的依赖关系,跟Task的dependsOn原理一样。

举个例子,比如有两个工程app工程和lib工程,其中app依赖lib工程。在lib工程的build.gradle 添加如下的属性:

rootProject.ext.producerMsg = "Hello"

在app工程的build.gradle 添加如下的代码,既app工程使用lib工程的动态属性:

def msg = rootProject.ext.producerMsg

如果在在配置阶段app 工程先运行,这样就会导致app会导致producerMsg属性没有找到!因为此lib工程还未运行。

所以要解决这个问题 就要在app project运行配置之前,先运行lib project的配置,那么就可以用evaluationDependsOn来解决依赖,在app的build.gradle中添加如下依赖即可:

evaluationDependsOn(':lib') //运行app配置之前,先运行lib依赖

那么添加依赖之后,每次在运行app配置阶段之前,都会保证lib配置阶段先被执行。

六、APP工程build.gradle

build.gradle的内容如下,与原工程一样的地方就省略了:

//第一点:读取local.properties文件中内容到内存
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
   
   
    localPropertiesFile.withReader('UTF-8') {
   
    reader ->
        localProperties.load(reader)
    }
}

//第二点:获取flutter sdk路径、versionCode、VersionName
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'

//第三点:导入flutter  gradle插件
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"

android {
   
   
  //省略...基本一样
}

//第四点
flutter {
   
   
    source '../..'
}

dependencies {
   
   
    //可见App工程并没有依赖support包
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}

第一点:读取根工程下local.properties的内容到内存中(Properties), 该内容就是上面所介绍的Flutter SDK相关信息。
第二点:获取flutter sdk路径、versionCode、VersionName等信息。
第三点:从Flutter SDK目录下导入flutter gradle插件到当前工程中运行。
第四点:配置flutter插件的source属性,该属性指定了Flutter工程的路径。

该build.gradle最主要的功能从local.properties文件中获取Flutter SDK路径,并把该路径下的Flutter Gradle插件导入到当前工程中运行,接下来我们要看看该插件到底做了哪些工作。

七、flutter.gradle

Flutter代码打包到Android工程中秘密其实就是发生在flutter.gradle脚本中。该gradle脚本位于Flutter SDK/packages/flutter_tools/gradle/flutter.gradle中,接下来我们就揭开它的神秘面纱:
在这里插入图片描述
flutter.gradle代码分为了有两大核心部分:FlutterPluginFlutterTask

FlutterPlugin核心代码

apply方法

FlutterPlugin实现了Plugin接口,它是一个标准的gradle plugin。因此它的主入口在apply方法中,首先我们看看第一部分:

  @Override
    void apply(Project project) {
   
   
        // Add custom build types

        println "==== apply:" + project.getName() //app

        //1、新增profile、dynamicProfile、dynamicRelease 三种构建类型
        //在当前project下的android.buildTypes进行配置
        project.android.buildTypes {
   
   
            profile {
   
   
                initWith debug  //initWith:复制所有debug里面的属性
                if (it.hasProperty('matchingFallbacks')) {
   
   
                    matchingFallbacks = ['debug', 'release']
                }
            }
            dynamicProfile {
   
   
                initWith debug
                if (it.hasProperty('matchingFallbacks')) {
   
   
                    matchingFallbacks = ['debug', 'release']
                }
            }
            dynamicRelease {
   
   
                initWith debug
                if (it.hasProperty('matchingFallbacks')) {
   
   
                    matchingFallbacks = ['debug', 'release']
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值