源代码集管理
源代码集(Source Sets)定义了 Gradle 为特定构建变体收集源代码、资源和 AndroidManifest.xml 的目录。通过合理组织源代码集,可以精确控制不同构建变体使用的代码和资源。
核心概念
什么是源代码集
源代码集是一个目录结构,包含:
- Kotlin/Java 源代码
- Android 资源文件(res/)
- Assets 文件
- AndroidManifest.xml
- AIDL 文件
- RenderScript 文件
- JNI 库
每个模块都有一个 main/ 源代码集,包含所有构建变体共享的代码和资源。
默认源代码集结构
src/
├── main/ # 主源代码集(所有变体共享)
│ ├── kotlin/
│ ├── java/
│ ├── res/
│ ├── assets/
│ └── AndroidManifest.xml
├── debug/ # Debug build type 专用
│ ├── kotlin/
│ └── res/
├── release/ # Release build type 专用
│ └── res/
├── demo/ # Demo product flavor 专用
│ ├── kotlin/
│ └── res/
├── full/ # Full product flavor 专用
│ └── res/
├── demoDebug/ # Demo + Debug 组合的变体专用
│ └── res/
└── test/ # 单元测试
└── kotlin/
└── androidTest/ # Instrumentation 测试
└── kotlin/源代码集优先级
当构建特定变体时,Gradle 按以下优先级合并源代码集:
示例:构建 demoDebug 变体
src/demoDebug/- Build Variant 源代码集(最高优先级)src/debug/- Build Type 源代码集src/demo/- Product Flavor 源代码集src/main/- Main 源代码集(最低优先级)
多维度变种的优先级:
对于使用多个 flavor dimensions 的项目:
flavorDimensions += listOf("api", "mode")
productFlavors {
// "api" 维度
create("minApi24") { dimension = "api" }
create("minApi21") { dimension = "api" }
// "mode" 维度
create("demo") { dimension = "mode" }
create("full") { dimension = "mode" }
}构建 minApi24DemoDebug 变体时的优先级:
src/minApi24DemoDebug/- 完整变体组合src/demoDebug/- Build type + mode flavorsrc/minApi24Debug/- Build type + api flavorsrc/debug/- Build typesrc/minApi24Demo/- api flavor + mode flavorsrc/minApi24/- api flavor(优先级高的维度)src/demo/- mode flavor(优先级低的维度)src/main/- Main 源代码集
优先级规则:
flavorDimensions列表中越靠前的维度优先级越高- Build type 源代码集优先于 product flavor 源代码集
- 组合源代码集优先于单一源代码集
合并规则
Kotlin/Java 源代码
规则:所有源代码集的 kotlin/ 和 java/ 目录中的代码会一起编译。
⚠️ 重要限制:不能有重复的类定义!
❌ 错误示例:
src/main/kotlin/com/example/Utility.kt
src/debug/kotlin/com/example/Utility.kt // 构建错误!✅ 正确做法:
src/debug/kotlin/com/example/Utility.kt // 仅在 debug 中定义
src/release/kotlin/com/example/Utility.kt // 仅在 release 中定义
// main 中不定义 Utility.ktAndroidManifest.xml
规则:所有清单文件会合并为一个最终清单。
- 优先级高的源代码集的清单设置会覆盖优先级低的
- 使用合并规则标记(
tools:replace、tools:merge等)控制合并行为
详见 Manifest 合并机制。
资源文件
values/ 目录
规则:XML 资源值会合并。
- 如果同名资源在多个源代码集中定义,优先级高的覆盖优先级低的
- 例如:两个
strings.xml中都定义了app_name,使用优先级高的值
res/ 和 assets/
规则:资源文件打包在一起。
- 同名资源文件,优先级高的覆盖优先级低的
- 例如:
drawable/logo.png在多个源代码集中存在时,使用优先级最高的版本
库模块资源
规则:库模块依赖项的资源和清单具有最低优先级。
创建源代码集
使用 Android Studio 创建
创建源代码集目录
- 在 Project 窗格中选择 Project 视图
- 导航到
MyProject/app/src/ - 右键点击
src目录 → New → Directory - 从 Gradle Source Sets 下选择目标源代码集(如
debug/java) - 按 Enter
为特定变体添加文件
- 在 Project 窗格中右键点击
src目录 - 选择 New → XML → Values XML File(或其他文件类型)
- 输入文件名
- 从 Target Source Set 菜单选择目标源代码集(如
debug) - 点击 Finish
Android Studio 会自动创建必要的目录结构。
手动创建
直接在文件系统中创建目录:
# 创建 debug build type 源代码集
mkdir -p src/debug/kotlin
mkdir -p src/debug/res/values
# 创建 demo product flavor 源代码集
mkdir -p src/demo/kotlin
mkdir -p src/demo/res/drawable
# 创建 demo + debug 组合源代码集
mkdir -p src/demoDebug/res/values实践案例
案例一:不同构建类型的配置
场景:Debug 版本启用日志,Release 版本禁用。
目录结构:
src/
├── main/kotlin/com/example/
│ └── Logger.kt # Release 版本
├── debug/kotlin/com/example/
│ └── Logger.kt # Debug 版本(覆盖 main)
└── release/ # 使用 main 中的 Logger.ktsrc/main/kotlin/com/example/Logger.kt(Release 默认):
object Logger {
fun log(message: String) {
// Release 中不输出日志
}
}src/debug/kotlin/com/example/Logger.kt(Debug 覆盖):
import android.util.Log
object Logger {
fun log(message: String) {
Log.d("App", message) // Debug 中输出日志
}
}案例二:产品变种的资源差异
场景:Free 版和 Paid 版使用不同的图标和应用名称。
目录结构:
src/
├── main/res/
│ └── values/
│ └── strings.xml # 共享字符串
├── free/res/
│ ├── mipmap-*/
│ │ └── ic_launcher.png # Free 图标
│ └── values/
│ └── strings.xml # Free 应用名
└── paid/res/
├── mipmap-*/
│ └── ic_launcher.png # Paid 图标
└── values/
└── strings.xml # Paid 应用名src/free/res/values/strings.xml:
<resources>
<string name="app_name">MyApp Free</string>
</resources>src/paid/res/values/strings.xml:
<resources>
<string name="app_name">MyApp Pro</string>
</resources>案例三:环境特定配置
场景:Dev、Staging、Production 环境使用不同的 API 配置类。
目录结构:
src/
├── dev/kotlin/com/example/
│ └── ApiConfig.kt
├── staging/kotlin/com/example/
│ └── ApiConfig.kt
└── production/kotlin/com/example/
└── ApiConfig.ktsrc/dev/kotlin/com/example/ApiConfig.kt:
object ApiConfig {
const val BASE_URL = "https://dev-api.example.com"
const val ENABLE_LOGGING = true
}src/production/kotlin/com/example/ApiConfig.kt:
object ApiConfig {
const val BASE_URL = "https://api.example.com"
const val ENABLE_LOGGING = false
}案例四:特定变体资源
场景:Demo + Debug 组合需要特殊的调试资源。
src/
├── main/res/values/
│ └── config.xml
├── demo/res/values/
│ └── config.xml # Demo 特有配置
├── debug/res/values/
│ └── config.xml # Debug 特有配置
└── demoDebug/res/values/
└──config.xml # Demo + Debug 最高优先级配置源代码集路径
更改默认路径
如果源代码未按默认结构组织,可以使用 sourceSets 块重新配置:
android {
sourceSets {
// 配置 main 源代码集
getByName("main") {
// 更改 Java/Kotlin 源代码目录
java.setSrcDirs(listOf("src/main/kotlin", "src/main/java"))
// 配置资源目录(可以有多个)
res.setSrcDirs(listOf("src/main/res", "src/main/res_extra"))
// 配置清单文件
manifest.srcFile("src/main/AndroidManifest.xml")
// 配置 assets 目录
assets.setSrcDirs(listOf("src/main/assets"))
// 配置 AIDL 目录
aidl.setSrcDirs(listOf("src/main/aidl"))
// 配置 JNI 库目录
jniLibs.setSrcDirs(listOf("src/main/jniLibs"))
}
// 配置 test 源代码集
getByName("test") {
java.setSrcDirs(listOf("src/test/kotlin"))
}
// 使用 setRoot 设置源代码集根目录
getByName("androidTest") {
setRoot("src/androidTest")
}
}
}android {
sourceSets {
main {
java.srcDirs = ['src/main/kotlin', 'src/main/java']
res.srcDirs = ['src/main/res', 'src/main/res_extra']
manifest.srcFile 'src/main/AndroidManifest.xml'
assets.srcDirs = ['src/main/assets']
aidl.srcDirs = ['src/main/aidl']
jniLibs.srcDirs = ['src/main/jniLibs']
}
test {
java.srcDirs = ['src/test/kotlin']
}
androidTest {
setRoot 'src/androidTest'
}
}
}⚠️ 注意事项:
- 避免指定父子目录关系(如
['res', 'res/layouts']) - 同一资源类型的多个目录具有相同优先级,同名资源会导致合并错误
查看源代码集配置
使用 sourceSets Task
运行以下 Gradle 任务查看所有源代码集的配置:
./gradlew sourceSets输出示例:
------------------------------------------------------------
Project :app
------------------------------------------------------------
debug
----
Compile configuration: debugCompile
build.gradle name: android.sourceSets.debug
Java sources: [app/src/debug/java]
Kotlin sources: [app/src/debug/kotlin, app/src/debug/java]
Manifest file: app/src/debug/AndroidManifest.xml
Android resources: [app/src/debug/res]
Assets: [app/src/debug/assets]
AIDL sources: [app/src/debug/aidl]
RenderScript sources: [app/src/debug/rs]
JNI sources: [app/src/debug/jni]
JNI libraries: [app/src/debug/jniLibs]
Java-style resources: [app/src/debug/resources]在 Android Studio 中查看
- 打开 Gradle 工具窗口
- 导航到 MyApplication → Tasks → android
- 双击 sourceSets 任务
- 在 Run 窗口查看输出
测试源代码集
单元测试
路径:src/test/kotlin/
特点:
- 在 JVM 上运行,不需要 Android 设备
- 速度快,适合纯逻辑测试
Instrumentation 测试
路径:src/androidTest/kotlin/
特点:
- 在 Android 设备或模拟器上运行
- 可以访问 Android 框架 API
- 适合 UI 测试和集成测试
变体特定测试
可以为特定变体创建测试:
src/
├── test/ # 所有变体的单元测试
├── testDemo/ # Demo 变种的单元测试
├── testDebug/ # Debug 构建的单元测试
├── androidTest/ # 所有变体的 instrumentation 测试
├── androidTestDemo/ # Demo 变种的 instrumentation 测试
└── androidTestDemoDebug/ # Demo + Debug 的 instrumentation 测试详见 Android 测试文档。
最佳实践
- 最大化共享代码:将通用代码放在
main/中,仅在变体源代码集中放置差异化代码 - 避免类重复:不要在多个源代码集中定义同名类
- 资源命名规范:为变体特定资源使用清晰的命名前缀
- 清单最小化:仅在变体源代码集的清单中定义必要的差异
- 测试覆盖:为关键变体编写专门的测试
- 文档化结构:在项目文档中说明源代码集的组织逻辑
- 定期清理:删除不再使用的源代码集目录
常见问题
如何解决"重复类"错误?
错误信息:
Duplicate class com.example.Utility found in modules...解决方法:
- 检查是否在多个源代码集中定义了同名类
- 将共享类移至优先级最低的源代码集(通常是
main/) - 或为每个变体创建不同的类实现,不在
main/中定义
如何查看某个变体使用了哪些源代码集?
运行 sourceSets 任务或查看构建日志中的源代码集合并信息。
能否为源代码集之间共享部分代码?
不能直接共享,但可以:
- 将共享代码移至
main/ - 使用 Gradle 的
sourceSets.getByName("flavor").java.srcDirs引用其他目录 - 创建单独的库模块