Android多模块设计:如何集中管理配置且避免强依赖?
兼顾解耦与编译效率的Feature Flag/端点常量管理方案
针对你提到的两种策略的痛点,可以通过**「分层公共模块+自动化代码生成+运行时统一管理」**的组合方案来兼顾优势,具体做法如下:
1. 拆分公共模块为API层与实现层
把原来的单一common模块拆成两个独立模块:
common-api:仅存放无具体值的接口/常量定义,比如跨模块生效的Feature Flag的key名称、API端点的接口声明,不包含实际的常量值。业务模块仅依赖这个轻量模块。common-implementation:存放Feature Flag的实际值、API端点的具体URL,以及全局配置逻辑。这个模块仅在运行时或最终打包时被依赖,业务模块编译阶段不直接依赖它。
示例代码:common-api/Features.kt:
interface FeatureKeys { // 仅定义跨模块生效的Flag key,本地模块的Flag无需在这里定义 val MODULE_SHARED_PAYMENT_FEATURE: String val MODULE_SHARED_NOTIFICATION_FEATURE: String } interface ApiEndpointKeys { val BASE_USER_SERVICE: String }
common-implementation/DefaultFeatures.kt:
object DefaultFeatureKeys : FeatureKeys { override val MODULE_SHARED_PAYMENT_FEATURE = "payment_v2" override val MODULE_SHARED_NOTIFICATION_FEATURE = "push_notify_v3" } object DefaultApiEndpoints : ApiEndpointKeys { override val BASE_USER_SERVICE = "https://api.example.com/user" }
这样,新增或修改常量值时,只有common-implementation会重新编译,业务模块因为依赖的是接口,不会触发全量编译,解决了策略1的编译慢问题。
2. 自动化代码生成+全局校验
允许业务模块在本地定义专属的Feature Flag和API端点,但通过Gradle任务实现自动生成常量代码和全局重复校验:
- 要求每个业务模块在固定路径(如
src/main/resources/local-configs/)下用YAML/Properties文件定义本地常量,比如local-features.yaml:MODULE_A_NEW_FEATURE: "module_a_new_ui" MODULE_A_USER_LIST_ENDPOINT: "/api/v1/module-a/users" - 编写Gradle任务,在构建前完成以下操作:
- 扫描所有模块的本地配置文件,检查Feature Flag ID是否重复,重复则直接抛出构建错误。
- 自动将每个模块的本地配置转换成Kotlin/Java常量类(生成到模块的
generated目录),模块内直接使用生成的类,无需手动维护常量。 - 自动将跨模块生效的Flag同步到
common-api的接口中,确保全局一致性。
示例Gradle任务(Groovy):
task validateAndGenerateConstants { doLast { def allFeatureKeys = [] // 扫描所有子模块的本地配置 subprojects.each { project -> def featureFile = project.file("src/main/resources/local-configs/local-features.yaml") if (featureFile.exists()) { def localFeatures = new groovy.yaml.YamlSlurper().parse(featureFile) // 校验重复Key localFeatures.each { key, _ -> if (allFeatureKeys.contains(key)) { throw new GradleException("重复的Feature Flag ID: $key,存在于模块${project.name}") } allFeatureKeys.add(key) } // 生成模块本地的常量类 def outputDir = project.file("src/main/generated/kotlin/com/example/${project.name}") outputDir.mkdirs() def constantContent = """ object ${project.name.capitalize()}Constants { // 本地Feature Flags ${localFeatures.collect { "const val ${it.key} = \"${it.value}\"" }.join("\n ")} } """ new File(outputDir, "LocalConstants.kt").write(constantContent) } } // 更新common-api的跨模块Flag接口(仅包含需要全局共享的Key) def sharedKeys = allFeatureKeys.findAll { it.startsWith("MODULE_SHARED_") } def apiOutput = project(":common-api").file("src/main/kotlin/com/example/common/api/FeatureKeys.kt") def apiContent = """ interface FeatureKeys { ${sharedKeys.collect { "val $it: String" }.join("\n ")} } """ apiOutput.write(apiContent) } } // 让编译任务依赖该校验生成任务 subprojects.each { project -> project.tasks.compileKotlin.dependsOn(validateAndGenerateConstants) }
3. 运行时统一管理
通过全局的FeatureManager和EndpointManager封装所有常量的使用逻辑:
- 业务模块不直接使用常量值,而是通过Manager类判断Feature状态、获取API端点。
- 跨模块的Feature逻辑在Manager中统一处理,本地模块的Feature也可以通过Manager调用,保持调用方式一致。
示例代码:
class FeatureManager(private val featureKeys: FeatureKeys) { fun isFeatureEnabled(featureKey: String): Boolean { // 对接远程配置中心或本地缓存的逻辑 return RemoteConfig.getBoolean(featureKey, false) } // 跨模块Feature的快捷方法 fun isPaymentFeatureEnabled(): Boolean { return isFeatureEnabled(featureKeys.MODULE_SHARED_PAYMENT_FEATURE) } }
方案优势
- 解耦性:业务模块仅维护自身专属的常量,跨模块常量通过公共API接口定义,避免了单一模块的依赖臃肿。
- 编译效率:常量值的修改仅影响实现层模块,业务模块依赖的是稳定的接口,不会触发全量重新编译。
- 维护便捷:自动化校验避免了重复ID问题,代码生成减少了手动维护常量的工作量,同时跨模块常量自动同步到公共API。
内容的提问来源于stack exchange,提问作者htafoya
相关产品推荐
相关产品推荐

