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.PayApicom.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. 第二步:变更审批与准备

  1. 依赖影响评估:通过工具(如 Android Studio 的「Find Usages」、自定义 Gradle 插件)扫描所有依赖该模块的项目,明确受影响的代码范围;
  2. 变更方案评审:提交变更申请(含变更类型、影响范围、兼容措施、替代方案),由模块负责人、依赖方负责人共同评审;
  3. 文档准备:更新 API 文档(如 Dokka 生成的文档、Wiki),明确标注变更点、兼容版本、迁移指南;
  4. 测试准备:补充回归测试用例(覆盖旧 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)不兼容变更实施

  • 采用「渐进式迁移」策略:
    1. 第一阶段:标记旧 API 为 @Deprecated,提供新 API 替代,旧 API 内部转发到新 API 实现(保证兼容);
    2. 第二阶段:保留旧 API,但在文档中明确告知「下一个主版本将移除」,并在日志中打印警告;
    3. 第三阶段:主版本升级时,删除旧 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、Java final),禁止返回内部集合的引用(避免外部修改导致数据错乱):

    // 推荐:返回不可变集合
    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 变更带来的风险,确保跨模块依赖的稳定性。