Skip to content

源代码集管理

源:Android 官方文档 - 配置 build 变体

源代码集(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 变体

  1. src/demoDebug/ - Build Variant 源代码集(最高优先级
  2. src/debug/ - Build Type 源代码集
  3. src/demo/ - Product Flavor 源代码集
  4. src/main/ - Main 源代码集(最低优先级

多维度变种的优先级

对于使用多个 flavor dimensions 的项目:

kotlin
flavorDimensions += listOf("api", "mode")

productFlavors {
    // "api" 维度
    create("minApi24") { dimension = "api" }
    create("minApi21") { dimension = "api" }
    
    // "mode" 维度
    create("demo") { dimension = "mode" }
    create("full") { dimension = "mode" }
}

构建 minApi24DemoDebug 变体时的优先级:

  1. src/minApi24DemoDebug/ - 完整变体组合
  2. src/demoDebug/ - Build type + mode flavor
  3. src/minApi24Debug/ - Build type + api flavor
  4. src/debug/ - Build type
  5. src/minApi24Demo/ - api flavor + mode flavor
  6. src/minApi24/ - api flavor(优先级高的维度)
  7. src/demo/ - mode flavor(优先级低的维度)
  8. 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.kt

AndroidManifest.xml

规则:所有清单文件会合并为一个最终清单。

  • 优先级高的源代码集的清单设置会覆盖优先级低的
  • 使用合并规则标记(tools:replacetools:merge 等)控制合并行为

详见 Manifest 合并机制

资源文件

values/ 目录

规则:XML 资源值会合并。

  • 如果同名资源在多个源代码集中定义,优先级高的覆盖优先级低的
  • 例如:两个 strings.xml 中都定义了 app_name,使用优先级高的值

res/ 和 assets/

规则:资源文件打包在一起。

  • 同名资源文件,优先级高的覆盖优先级低的
  • 例如:drawable/logo.png 在多个源代码集中存在时,使用优先级最高的版本

库模块资源

规则:库模块依赖项的资源和清单具有最低优先级。

创建源代码集

使用 Android Studio 创建

创建源代码集目录

  1. Project 窗格中选择 Project 视图
  2. 导航到 MyProject/app/src/
  3. 右键点击 src 目录 → NewDirectory
  4. Gradle Source Sets 下选择目标源代码集(如 debug/java
  5. Enter

为特定变体添加文件

  1. Project 窗格中右键点击 src 目录
  2. 选择 NewXMLValues XML File(或其他文件类型)
  3. 输入文件名
  4. Target Source Set 菜单选择目标源代码集(如 debug
  5. 点击 Finish

Android Studio 会自动创建必要的目录结构。

手动创建

直接在文件系统中创建目录:

bash
# 创建 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.kt

src/main/kotlin/com/example/Logger.kt(Release 默认):

kotlin
object Logger {
    fun log(message: String) {
        // Release 中不输出日志
    }
}

src/debug/kotlin/com/example/Logger.kt(Debug 覆盖):

kotlin
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

xml
<resources>
    <string name="app_name">MyApp Free</string>
</resources>

src/paid/res/values/strings.xml

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.kt

src/dev/kotlin/com/example/ApiConfig.kt

kotlin
object ApiConfig {
    const val BASE_URL = "https://dev-api.example.com"
    const val ENABLE_LOGGING = true
}

src/production/kotlin/com/example/ApiConfig.kt

kotlin
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 块重新配置:

kotlin
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")
        }
    }
}
groovy
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 任务查看所有源代码集的配置:

bash
./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 中查看

  1. 打开 Gradle 工具窗口
  2. 导航到 MyApplicationTasksandroid
  3. 双击 sourceSets 任务
  4. 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 测试文档

最佳实践

  1. 最大化共享代码:将通用代码放在 main/ 中,仅在变体源代码集中放置差异化代码
  2. 避免类重复:不要在多个源代码集中定义同名类
  3. 资源命名规范:为变体特定资源使用清晰的命名前缀
  4. 清单最小化:仅在变体源代码集的清单中定义必要的差异
  5. 测试覆盖:为关键变体编写专门的测试
  6. 文档化结构:在项目文档中说明源代码集的组织逻辑
  7. 定期清理:删除不再使用的源代码集目录

常见问题

如何解决"重复类"错误?

错误信息

Duplicate class com.example.Utility found in modules...

解决方法

  • 检查是否在多个源代码集中定义了同名类
  • 将共享类移至优先级最低的源代码集(通常是 main/
  • 或为每个变体创建不同的类实现,不在 main/ 中定义

如何查看某个变体使用了哪些源代码集?

运行 sourceSets 任务或查看构建日志中的源代码集合并信息。

能否为源代码集之间共享部分代码?

不能直接共享,但可以:

  1. 将共享代码移至 main/
  2. 使用 Gradle 的 sourceSets.getByName("flavor").java.srcDirs 引用其他目录
  3. 创建单独的库模块