3、Android项目结构:Project与Module、build.gradle与settings.gradle、gradle.properties、local.properties、版本目录libs.versions.toml

说实话,很多刚接触Android的同学,一打开项目看到那一堆文件和文件夹就懵了。我当年也是这样,对着Android Studio自动生成的结构发呆——这玩意儿到底谁是谁?

今天咱们就把这个项目结构彻底捋清楚。你想想看,Gradle脚本说白了就是一堆配置文件,它们各司其职,共同决定了你的App怎么编译、怎么打包、依赖哪些库。

核心一句话:Android项目 = 1个Project + N个Module。Project是容器,Module是具体功能单元。

Android项目结构核心要素 Project(根项目) settings.gradle.kts 定义Module列表 build.gradle.kts (Project) 全局配置、仓库、插件 gradle.properties JVM参数、全局属性 local.properties SDK/NDK路径(本地) libs.versions.toml 版本目录(统一依赖) Module(模块) 每个Module有自己的 build.gradle.kts,定义该模块的构建逻辑

3.1 Project与Module:谁是谁的爸爸?

Android Studio里你打开一个项目,最外层那个文件夹就是Project。它本身不写代码,就是个管家。真正的代码都在Module里。

我习惯把Project比作一栋楼,Module就是楼里的各个房间。有的房间是App(主模块),有的房间是Library(库模块),还有的是Android Library(比如你封装的网络请求库)。

常见的Module类型:

  • app模块——你的主应用,最终生成APK
  • library模块——纯Java/Kotlin库,生成JAR
  • android-library模块——包含Android资源的库,生成AAR
  • dynamic-feature模块——动态功能模块,按需下载

我的经验:项目初期别急着拆Module。我见过有人一上来就拆了十几个Module,结果业务没跑通,光改依赖就改了一天。等业务稳定了再拆分,不迟。

3.2 build.gradle与settings.gradle:构建脚本的双核

这两个文件是Gradle构建的入口。咱们一个一个说。

settings.gradle.kts

这个文件在项目根目录,它的作用就一句话:告诉Gradle哪些Module参与构建

// settings.gradle.kts
pluginManagement {
    repositories {
        google()
        mavenCentral()
        gradlePluginPortal()
    }
}

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
    }
}

rootProject.name = "MyApp"
include(":app")
include(":library:network")
include(":library:common")

注意看include那一行,每个Module对应一个路径。冒号是分隔符,:app表示根目录下的app文件夹,:library:network表示library/network文件夹。

我曾经踩过的坑:有一次我改了Module名字,但忘了更新settings.gradle里的include,结果Gradle报错说找不到模块。嗯,从那以后我每次重命名Module都会先检查这里。

build.gradle.kts(Project级别)

这个文件配置的是全局的东西——仓库地址、插件声明、所有Module共享的配置。

// 项目根目录的 build.gradle.kts
plugins {
    id("com.android.application") version "8.2.0" apply false
    id("com.android.library") version "8.2.0" apply false
    id("org.jetbrains.kotlin.android") version "1.9.20" apply false
    id("com.google.devtools.ksp") version "1.9.20-1.0.14" apply false
}

看到apply false了吗?这表示只声明插件,但不应用到当前Project。真正应用是在各个Module的build.gradle里。

build.gradle.kts(Module级别)

每个Module都有自己的build.gradle,这里才是真正干活的地方——配置SDK版本、依赖库、构建类型、混淆规则等等。

// app/build.gradle.kts
plugins {
    id("com.android.application")
    id("org.jetbrains.kotlin.android")
    id("com.google.devtools.ksp")
}

android {
    namespace = "com.example.myapp"
    compileSdk = 34

    defaultConfig {
        applicationId = "com.example.myapp"
        minSdk = 24
        targetSdk = 34
        versionCode = 1
        versionName = "1.0"
    }

    buildTypes {
        release {
            isMinifyEnabled = true
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
}

dependencies {
    implementation(project(":library:network"))
    implementation("androidx.core:core-ktx:1.12.0")
    implementation("androidx.appcompat:appcompat:1.6.1")
}

你想想看,每个Module独立配置的好处是什么?——解耦。app模块可以依赖library模块,但library模块不需要知道app的存在。

3.3 gradle.properties:全局属性的保险箱

这个文件在项目根目录,用来设置Gradle构建的JVM参数和全局属性。说白了,就是一些你不想硬编码在build.gradle里的东西。

# gradle.properties
# JVM参数
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8

# 并行构建
org.gradle.parallel=true

# 启用构建缓存
org.gradle.caching=true

# Android专用
android.useAndroidX=true
android.nonTransitiveRClass=true

# 自定义属性
MY_APP_VERSION=2.0.0

我个人习惯把一些环境相关的配置放这里,比如是否开启日志、API地址的开关等。这样改配置不用动代码,方便很多。

小技巧:org.gradle.jvmargs里的-Xmx2048m表示给Gradle分配2GB内存。如果你的项目比较大,可以改成-Xmx4096m。我见过有些团队项目编译慢,调大这个参数后速度快了不少。

3.4 local.properties:你的本地秘密

这个文件不应该提交到Git。它记录的是你本机的SDK和NDK路径。

# local.properties
sdk.dir=/Users/yourname/Library/Android/sdk
ndk.dir=/Users/yourname/Library/Android/sdk/ndk/25.2.9519653

为什么不能提交?因为每个人的电脑路径不一样。你提交了,同事拉下来还得改。Android Studio会自动生成这个文件,你基本不用手动碰它。

注意:如果你换了电脑或者重装了系统,记得检查这个文件。我有个同事重装后忘了配SDK路径,结果Gradle一直报错,折腾了半天才发现是local.properties的问题。

3.5 版本目录libs.versions.toml:依赖管理的现代化方案

这个文件是Gradle 7.0之后引入的,位置在gradle/libs.versions.toml。它的作用就一个:统一管理所有依赖的版本号

以前我们是怎么做的?每个Module的build.gradle里写死版本号:

implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.appcompat:appcompat:1.6.1")

这样有什么问题?版本号散落在各个文件里,升级依赖的时候得一个一个改,烦不烦?

有了版本目录,我们这样写:

# gradle/libs.versions.toml
[versions]
core-ktx = "1.12.0"
appcompat = "1.6.1"
compose-bom = "2024.01.00"

[libraries]
core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "core-ktx" }
appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" }

[plugins]
android-application = { id = "com.android.application", version = "8.2.0" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version = "1.9.20" }

然后在build.gradle里引用:

dependencies {
    implementation(libs.core.ktx)
    implementation(libs.appcompat)
    implementation(platform(libs.compose.bom))
}

看到libs.core.ktx了吗?这是Gradle自动生成的访问器,点号对应TOML文件里的层级。升级版本?只需要改libs.versions.toml一个地方就行。

我的建议:新项目直接用版本目录。老项目迁移也不难,花半小时把依赖整理到TOML文件里,以后升级依赖能省不少事。我去年把一个老项目迁移过来,现在升级AGP版本只需要改一行,爽得很。

3.6 这些文件的关系,一张表说清楚

文件 位置 核心作用 是否提交Git
settings.gradle.kts 项目根目录 定义Module列表、仓库配置
build.gradle.kts (Project) 项目根目录 全局插件声明、仓库配置
build.gradle.kts (Module) 每个Module目录 该Module的构建配置、依赖
gradle.properties 项目根目录 JVM参数、全局属性
local.properties 项目根目录 本地SDK/NDK路径
libs.versions.toml gradle/目录 统一管理依赖版本

说白了,这些文件各管一摊:settings.gradle管"有哪些模块",build.gradle管"怎么构建",gradle.properties管"全局参数",local.properties管"本地环境",libs.versions.toml管"依赖版本"。各司其职,不乱。

你刚开始可能觉得文件多,但用熟了就会发现——每个文件都有它存在的道理。就像我常说的,Gradle脚本不是写一次就完事的,它是你和构建系统之间的沟通语言。把这些文件搞懂了,以后遇到构建问题,你就能快速定位是哪个文件出了问题。


公众号:蓝海资料掘金营,微信deep3321