Android 模块公开 API 的特征与变更管控方案
在 Android 开发中,模块(如 Library 模块、业务模块)的公开 API 是模块对外提供能力的核心入口,其设计和管控直接影响跨模块依赖稳定性。以下从「公开 API 的核心特征」「变更管控流程」「落地工具与最佳实践」三部分展开说明:
一、Android 模块公开 API 的核心特征
公开 API 是模块明确对外暴露、允许其他模块依赖调用的「稳定契约」,具备以下可识别特征(区分于内部实现代码):
1. 代码层面的显性特征
| 特征维度 | 具体表现 | 示例 |
|---|---|---|
| 访问修饰符 | 必须是 public(Kotlin 中默认 public,需显式声明为 internal 排除内部接口) |
Java: public class UserApi { ... } Kotlin: class UserApi { ... }(默认 public) |
无 internal/包级私有修饰 |
模块内部代码需用 internal(Kotlin)、包级私有(Java 无修饰符)或 private 隔离 |
Kotlin: internal fun internalUtils() { ... }(非公开) |
| 集中式暴露(推荐规范) | 公开 API 统一放在固定包名下(如 com.xxx.module.api),避免散落在业务包中 |
包结构:com.xxx.pay.api.PayApi、com.xxx.pay.api.PayCallback |
| 无「实现细节泄露」 | 公开 API 优先是接口(interface)、抽象类,或封装后的工具类,不暴露内部成员变量、实现类 |
反例:public class PayImpl { public String apiKey; ... }(暴露实现+敏感字段) |
| 注解标记(可选) | 用自定义注解标记公开 API,便于工具识别(如 @PublicApi) |
@PublicApi fun createOrder(): String |
2. 功能与契约层面的特征
- 稳定性承诺:公开 API 需承诺「向后兼容」(除非明确声明为 Beta 版本),内部接口可随时变更;
- 职责单一:公开 API 聚焦核心能力(如支付模块的「创建订单」「查询支付结果」),不包含模块内部的辅助逻辑;
- 输入输出明确:参数和返回值优先使用可序列化、稳定的类型(如基本类型、Parcelable 数据类、自定义 DTO),避免使用第三方框架依赖类型(如 Retrofit 的 Call);
- 异常可预期:明确声明可能抛出的checked异常(Java)或通过 Result 封装异常(Kotlin),不隐藏崩溃风险。
3. 工程层面的特征
- 模块的
build.gradle中,对外提供的依赖仅包含公开 API 所需的「最小依赖集」(避免传递无关依赖); - 公开 API 的文档(JavaDoc/KDoc)完整,包含参数说明、返回值含义、使用示例、兼容版本等;
- 非公开代码不会被其他模块通过「反射」「跨包访问」等方式间接依赖(需通过 lint 等工具禁止)。
二、公开 API 变更管控流程
API 变更的核心原则是:最小化影响范围,明确告知依赖方,确保向后兼容。管控流程分为「变更分类」「变更审批」「实施与发布」「兼容保障」四步:
1. 第一步:明确变更类型(决定管控严格程度)
根据对依赖方的影响,将变更分为 3 类,管控力度依次递增:
| 变更类型 | 定义 | 示例 | 管控要求 |
|---|---|---|---|
| 兼容变更(Safe) | 不影响现有调用,仅新增能力或优化实现 | 1. 新增公开方法/类;2. 给现有方法新增可选参数;3. 优化方法内部逻辑(不改变输入输出) | 无需审批,直接发布,更新文档 |
| 兼容警告变更(Compatible with Warning) | 现有调用仍可用,但不推荐(未来可能移除) | 1. 标记方法为 @Deprecated;2. 新增替代方法 |
需同步文档说明替代方案,预留至少 1 个版本的过渡期 |
| 不兼容变更(Breaking Change) | 现有调用会失效(编译报错、运行崩溃) | 1. 删除公开方法/类;2. 修改方法参数/返回值类型;3. 变更异常抛出规则;4. 降低访问修饰符(public → protected) | 需严格审批,仅允许在「主版本升级」(如 1.x → 2.x)时进行,且必须提前告知所有依赖方 |
2. 第二步:变更审批与准备
- 依赖影响评估:通过工具(如 Android Studio 的「Find Usages」、自定义 Gradle 插件)扫描所有依赖该模块的项目,明确受影响的代码范围;
- 变更方案评审:提交变更申请(含变更类型、影响范围、兼容措施、替代方案),由模块负责人、依赖方负责人共同评审;
- 文档准备:更新 API 文档(如 Dokka 生成的文档、Wiki),明确标注变更点、兼容版本、迁移指南;
- 测试准备:补充回归测试用例(覆盖旧 API 兼容场景、新 API 功能场景),确保无遗漏。
3. 第三步:变更实施与发布
(1)兼容变更实施
- 新增 API 时,遵循「接口优先」原则:优先定义接口(如
PayApi),再提供实现类(如PayApiImpl),便于后续扩展; - 新增参数时,必须设为「可选参数」(Kotlin:
fun createOrder(amount: Int, discount: Int = 0);Java:提供重载方法createOrder(int amount)和createOrder(int amount, int discount))。
(2)不兼容变更实施
- 采用「渐进式迁移」策略:
- 第一阶段:标记旧 API 为
@Deprecated,提供新 API 替代,旧 API 内部转发到新 API 实现(保证兼容); - 第二阶段:保留旧 API,但在文档中明确告知「下一个主版本将移除」,并在日志中打印警告;
- 第三阶段:主版本升级时,删除旧 API,同步更新所有依赖方代码。
- 第一阶段:标记旧 API 为
-
示例(Kotlin):
// 第一阶段:新增新 API,旧 API 标记 Deprecated 并转发 @PublicApi class PayApi { // 旧 API(兼容中) @Deprecated("请使用 createOrder(OrderParam),v2.0 将移除", ReplaceWith("createOrder(OrderParam(amount))")) fun createOrder(amount: Int): String { return createOrder(OrderParam(amount)) // 内部转发到新 API } // 新 API(推荐使用) @PublicApi fun createOrder(param: OrderParam): String { // 新实现 } } // 第三阶段(v2.0):删除旧 API,仅保留新 API
(3)发布规范
- 版本号遵循 Semantic Versioning(语义化版本):
主版本号.次版本号.修订号- 兼容变更:更新「修订号」(如 1.0.0 → 1.0.1);
- 新增 API(兼容):更新「次版本号」(如 1.0.1 → 1.1.0);
- 不兼容变更:更新「主版本号」(如 1.1.0 → 2.0.0);
- 发布时附带「变更日志(Changelog)」,明确标注 API 变更类型、影响范围、迁移指南。
4. 第四步:兼容保障与问题兜底
- 灰度发布:先向部分依赖方灰度发布变更后的模块,收集反馈,避免全量发布导致大规模问题;
- 回滚机制:若发现变更引入严重兼容问题,立即回滚到上一个稳定版本,暂停变更;
- 技术兜底:对于无法避免的不兼容变更,提供「适配层」(如适配类、静态代理),帮助依赖方快速迁移(示例如下):
// 适配层:兼容旧 API 调用,内部转换为新 API object PayApiAdapter { // 旧 API 签名,供未迁移的依赖方调用 fun createOrder(amount: Int): String { return PayApi().createOrder(OrderParam(amount)) } }
三、落地工具与最佳实践
1. 工具选型:自动化管控 API 变更
| 工具用途 | 推荐工具 | 核心能力 |
|---|---|---|
| API 边界定义 | 自定义 @PublicApi 注解 + Lint 规则 |
强制公开 API 必须加注解,禁止内部代码被外部依赖 |
| API 变更检测 | 1. Dokka(生成 API 文档,对比版本差异);2. Gradle API Check 插件;3. Binary Compatibility Validator(Kotlin 官方工具,检测二进制兼容性) | 自动识别不兼容变更(如方法删除、参数变更),构建时报错 |
| 依赖影响分析 | 1. Android Studio「Find Usages」;2. JDepend;3. 自定义 Gradle 插件扫描项目依赖 | 快速定位依赖该 API 的所有模块/代码 |
| 文档生成 | Dokka(Kotlin)、JavaDoc(Java) | 自动生成结构化 API 文档,包含变更历史、使用示例 |
2. 最佳实践:避免 API 变更风险
(1)API 设计阶段:从源头减少变更
-
优先使用「接口 + 工厂模式」暴露 API,避免直接暴露实现类(便于后续替换实现,不影响调用方):
// 推荐:暴露接口 + 工厂方法 @PublicApi interface PayApi { fun createOrder(param: OrderParam): String companion object Factory { fun getInstance(): PayApi = PayApiImpl() // 隐藏实现类 } } // 不推荐:直接暴露实现类 @PublicApi class PayApiImpl { ... } // 后续修改类名/结构会导致不兼容 -
用「数据类/DTO」封装复杂参数(避免后续新增参数时修改方法签名):
// 推荐:用 DTO 封装参数 @PublicApi data class OrderParam( val amount: Int, val discount: Int = 0, // 新增参数时设为可选,兼容旧调用 val remark: String? = null ) // 不推荐:参数直接写在方法上(新增参数需改签名) fun createOrder(amount: Int, discount: Int, remark: String) { ... } -
避免暴露「可变状态」:公开 API 的返回值优先用不可变类型(如 Kotlin
val、Javafinal),禁止返回内部集合的引用(避免外部修改导致数据错乱):// 推荐:返回不可变集合 fun getOrderList(): List= orderList.toList() // 拷贝一份,避免外部修改 // 不推荐:返回可变集合 fun getOrderList(): MutableList = orderList // 外部可修改内部状态
(2)工程规范:强制 API 管控
- 模块包结构强制分离「公开 API」和「内部实现」:
com.xxx.module ├── api/ // 公开 API(仅放 interface、DTO、工具类) │ ├── PayApi.kt │ └── OrderParam.kt ├── internal/ // 内部实现(禁止外部依赖) │ ├── PayApiImpl.kt │ └── utils/ └── BuildConfig.kt - 用 Lint 规则禁止「内部代码被公开依赖」:自定义 Lint 检查,若其他模块引用了
internal包下的类,直接编译报错; - 定期审计 API:每季度review公开 API,清理废弃接口(Deprecated 超过 2 个版本的),避免 API 膨胀。
(3)沟通机制:确保依赖方同步
- 建立 API 变更通知机制:通过团队群、Wiki 公告、版本日志等方式,提前告知依赖方变更计划;
- 提供迁移支持:针对不兼容变更,编写迁移文档、示例代码,甚至提供临时适配层,降低依赖方迁移成本。
总结
Android 模块公开 API 的核心特征是「公开访问、稳定契约、集中暴露」,其变更管控的核心是「区分变更类型、最小化影响、保障向后兼容」。通过「规范 API 设计 + 自动化工具检测 + 渐进式变更 + 充分沟通」,可有效降低 API 变更带来的风险,确保跨模块依赖的稳定性。