Gradle 复合构建:宿主项目直接依赖 SDK 源码模块的配置实践

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 源码工程目录的取值优先级从高到低为:

  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 依赖

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 环境的稳定一致。整个方案对现有构建逻辑侵入小,回退成本低,适合多工程协作场景下推广使用。

相关推荐
小师兄吃牛肉3 小时前
Android Studio 2025 专业介绍:新特性、性能与开发体验全面解析
android·ide·android studio
美摄科技3 小时前
安卓离线类美颜特效SDK
android
fengci.5 小时前
BUUCTF(困难部分)
android·开发语言·前端·学习·php
传奇开心果6 小时前
【Compose Multiplatform跨端声明式UI实用技术】第9课:跨平台 ViewModel 与状态架构
android·kotlin
用户69371750013847 小时前
当你向领导申请 AI 编程工具预算时,如果领导追问“能提效多少,有量化数据吗”,你该怎么回答?
android·后端·ios
hunterandroid8 小时前
WorkManager 后台任务调度实战:从一次性任务到周期执行
android
事圆则缓8 小时前
Android 屏幕适配的前世今生
android
andyweike8 小时前
kotlin dsl library工程怎么配置
android·开发语言·kotlin
啵啵啵鱼8 小时前
九、JDBC 操作 MySQL 数据库
android·数据库·mysql