1. 背景与目标
在 Android 多工程协作中,宿主应用(项目 A)与 SDK 工程(lib_homeworkcommon)通常通过 Maven 坐标(com.eebbk.study:homeworkcommon-*)解耦。但日常联调时,每改一行 SDK 代码都要先发布 Maven 版本再同步宿主,链路长、反馈慢。
本文介绍一种基于 Gradle 复合构建(includeBuild + dependencySubstitution)的源码依赖方案:在项目 A 中新建 homework-sdk-source.gradle,由 settings.gradle 引用,把宿主声明的 Maven 坐标整体替换为 SDK 工程内的源码模块。修改 SDK 代码后宿主直接增量编译,无需再发布 Maven 版本。
2. 方案原理
Gradle 的 includeBuild 可以把另一个 Gradle 工程纳入当前构建,配合 dependencySubstitution 将指定 Maven 坐标替换为被包含工程中的 project 路径。这样宿主在编译时不再去仓库拉取 Maven 产物,而是直接编译 SDK 源码模块。
核心映射关系如下:
| Maven 坐标 | SDK 源码模块 |
|---|---|
| com.eebbk.study:homeworkcommon-stream | :module_stream_result |
| com.eebbk.study:homeworkcommon-base | :lib_homework_base |
| com.eebbk.study:homeworkcommon-correct | :module_correct_answer |
| com.eebbk.study:homeworkcommon-analysis | :module_question_analysis |
3. 新建 homework-sdk-source.gradle
在项目 A 根目录新建 homework-sdk-source.gradle,内容如下:
groovy
// 作业检查 SDK(lib_homeworkcommon)源码依赖开关,由 settings.gradle 引用。
// 启用后通过 Gradle 复合构建(includeBuild)把宿主声明的
// com.eebbk.study:homeworkcommon-* Maven 坐标整体替换为 SDK 工程内的源码模块,
// 修改 SDK 代码后宿主直接增量编译,无需再发布 Maven 版本。
// SDK 源码工程目录(按优先级从高到低):
// 1. 命令行 -PhomeworkSdkSourceDir=<dir>
// 2. local.properties / gradle.properties 中 homework.sdk.source.dir=<dir>
// 3. 默认与本工程同级的 lib_homeworkcommon
// 是否启用源码模式(按优先级从高到低):
// 1. 命令行 -PhomeworkSdkSourceEnabled=true|false
// 2. local.properties / gradle.properties 中 homework.sdk.source.enabled=true|false
// 3. 默认:找到 SDK 源码工程时自动启用,找不到则继续使用 Maven 依赖
// 典型用法:
// 日常联调:local.properties 配置 homework.sdk.source.dir,直接 Sync 即走源码;
// 验证 Maven 产物:local.properties 加 homework.sdk.source.enabled=false 后 Sync;
// Jenkins/其他同事机器:不配置即为 Maven 依赖模式,行为与改造前一致。
// 注意:源码模式下 Gradle 会配置整个 SDK 工程(含 app demo),因此需要能访问
// gitlab001 上的共享配置脚本;另外不要同时在另一个 Android Studio 窗口构建
// lib_homeworkcommon,两个构建会争用 SDK 工程的 .gradle/build 目录锁。
def homeworkSdkModuleSubstitutions = [
'com.eebbk.study:homeworkcommon-stream' : ':module_stream_result',
'com.eebbk.study:homeworkcommon-base' : ':lib_homework_base',
'com.eebbk.study:homeworkcommon-correct' : ':module_correct_answer',
'com.eebbk.study:homeworkcommon-analysis': ':module_question_analysis']
// local.properties 是本机文件(已在 .gitignore 中),优先用于存放本机 SDK 路径。
def homeworkSdkLocalProperties = new Properties()
def homeworkSdkLocalPropertiesFile = new File(settingsDir, 'local.properties')
if (homeworkSdkLocalPropertiesFile.exists()) {
homeworkSdkLocalPropertiesFile.withInputStream { homeworkSdkLocalProperties.load(it) }
}
// 取值优先级:命令行 -P > local.properties(本机)> gradle.properties(随工程提交)。
def homeworkSdkSetting = { String key ->
gradle.startParameter.projectProperties[key]
?: homeworkSdkLocalProperties.getProperty(key)
?: gradle.properties[key]
}
def homeworkSdkEnabledSetting = homeworkSdkSetting('homeworkSdkSourceEnabled')
?: homeworkSdkSetting('homework.sdk.source.enabled')
def homeworkSdkEnabled = homeworkSdkEnabledSetting == null
? null
: homeworkSdkEnabledSetting.toString().toBoolean()
def homeworkSdkConfiguredDir = homeworkSdkSetting('homeworkSdkSourceDir')
?: homeworkSdkSetting('homework.sdk.source.dir')
def homeworkSdkCandidates = []
if (homeworkSdkConfiguredDir) {
def configuredDir = new File(homeworkSdkConfiguredDir.toString())
homeworkSdkCandidates << (configuredDir.isAbsolute()
? configuredDir
: new File(settingsDir, homeworkSdkConfiguredDir.toString()))
}
homeworkSdkCandidates << new File(settingsDir.parentFile, 'lib_homeworkcommon')
// 以 module_stream_result 作为 SDK 工程的有效性标记,避免误把普通目录当源码工程。
def homeworkSdkDir = homeworkSdkCandidates.find { dir ->
dir.isDirectory() && new File(dir, 'module_stream_result/build.gradle').isFile()
}
def homeworkSdkUseSource = homeworkSdkEnabled == null
? homeworkSdkDir != null
: (homeworkSdkEnabled && homeworkSdkDir != null)
// 暴露给宿主 build.gradle 使用(例如判断是否还需要预取 Maven 源码包)。
gradle.ext.homeworkSdkSourceEnabled = homeworkSdkUseSource
gradle.ext.homeworkSdkSourceDir = homeworkSdkUseSource ? homeworkSdkDir : null
if (homeworkSdkUseSource) {
logger.lifecycle("[HomeworkSdkSource] 源码依赖模式已启用,SDK 工程:${homeworkSdkDir}")
includeBuild(homeworkSdkDir) {
dependencySubstitution {
homeworkSdkModuleSubstitutions.each { coordinate, projectPath ->
substitute module(coordinate) using project(projectPath)
}
}
}
} else {
if (homeworkSdkEnabled) {
logger.warn("[HomeworkSdkSource] 已开启源码依赖模式,但未找到 SDK 源码工程,已回落到 Maven 依赖。候选目录:" +
homeworkSdkCandidates.collect { it.absolutePath }.join(' | '))
} else {
logger.lifecycle("[HomeworkSdkSource] Maven 依赖模式,SDK 坐标:com.eebbk.study:homeworkcommon-*")
}
}
4. 在 settings.gradle 中引用
在项目 A 的 settings.gradle 中加入一行引用即可:
groovy
apply from: 'homework-sdk-source.gradle'
建议放在 settings.gradle 中插件声明之后、模块 include 之前,确保依赖替换在构建配置阶段尽早生效。
5. 配置项与优先级
SDK 源码工程目录的取值优先级从高到低为:
- 命令行参数:-PhomeworkSdkSourceDir=<dir>
- local.properties 或 gradle.properties 中的 homework.sdk.source.dir=<dir>
- 默认:与本工程同级的 lib_homeworkcommon 目录
是否启用源码模式的优先级从高到低为:
- 命令行参数:-PhomeworkSdkSourceEnabled=true|false
- local.properties 或 gradle.properties 中的 homework.sdk.source.enabled=true|false
- 默认:找到 SDK 源码工程时自动启用,找不到则继续使用 Maven 依赖
6. 典型使用场景
- 日常联调:在 local.properties 配置 homework.sdk.source.dir 指向本机 SDK 工程路径,直接 Sync 即走源码模式,改完 SDK 代码宿主增量编译。
- 验证 Maven 产物:在 local.properties 加 homework.sdk.source.enabled=false 后 Sync,强制走 Maven 依赖,验证发布产物是否符合预期。
- Jenkins 或其他同事机器:不配置任何开关,默认找不到 SDK 源码工程时自动回落 Maven 依赖模式,行为与改造前完全一致。
7. 注意事项
- 源码模式下 Gradle 会配置整个 SDK 工程(含 app demo),因此需要能访问 gitlab001 上的共享配置脚本。
- 不要同时在另一个 Android Studio 窗口构建 lib_homeworkcommon,两个构建会争用 SDK 工程的 .gradle/build 目录锁。
- SDK 工程的有效性以 module_stream_result/build.gradle 是否存在为标记,避免误把普通目录当源码工程。
- local.properties 已在 .gitignore 中,适合存放本机 SDK 路径,不会污染提交到仓库的配置。
8. 总结
通过 homework-sdk-source.gradle 与 settings.gradle 的组合,宿主项目可以在源码依赖与 Maven 依赖之间灵活切换,既保留日常联调的开发效率,又保证 CI 环境的稳定一致。整个方案对现有构建逻辑侵入小,回退成本低,适合多工程协作场景下推广使用。