Android组件化工程循环依赖解决方案

930 阅读4分钟

在 Android 组件化工程中,避免循环依赖的核心思路是 明确模块职责、建立单向依赖关系、合理分层,以下是具体规划和实践方案:


一、分层架构设计(核心原则)

将工程划分为 清晰的层级,确保依赖方向始终向下或平级,禁止反向依赖

  1. 基础层(Base Layer)

    • 职责:提供全局基础能力,如网络库、图片加载、工具类、通用UI组件等。
    • 依赖规则不依赖任何其他层,完全独立。
    • 示例模块basecommonnetworkutils
  2. 业务通用层(Business Common Layer)

    • 职责:提供跨业务复用的能力,如登录服务、支付服务、埋点统计等。
    • 依赖规则:只能依赖基础层,不依赖具体业务模块。
    • 示例模块account(账号服务)、router(路由)、analytics(统计)。
  3. 业务层(Business Layer)

    • 职责:独立业务功能模块,如首页、商品详情、购物车。
    • 依赖规则:可依赖基础层业务通用层,但禁止直接依赖其他业务层模块
    • 示例模块module_homemodule_productmodule_cart
  4. 应用层(App Layer)

    • 职责:主App模块,负责整合所有模块,初始化全局配置。
    • 依赖规则:可依赖所有层,但仅用于组装和启动,不包含业务逻辑。
    • 示例模块app

二、依赖关系规范

  1. 严格单向依赖

    • 高层模块可依赖低层模块,反之禁止(如 module_home 可依赖 base,但 base 不可依赖 module_home)。
    • 使用 Gradle 的 apiimplementation 控制依赖传递性,避免意外泄漏依赖。
  2. 使用接口隔离(关键技巧)

    • 定义接口模块:在业务通用层创建 module_interface,存放各业务模块的接口(如 IProductService)。
    • 依赖接口而非实现:业务模块通过接口调用其他模块能力,具体实现在实现模块中通过依赖注入(如 Dagger/Hilt)或服务发现(如 ARouter)提供。
    • 示例
      // 在 module_interface 中定义接口
      interface IProductService {
          fun openProductDetail(productId: String)
      }
      
      // 在 module_product 中实现接口
      class ProductServiceImpl : IProductService {
          override fun openProductDetail(productId: String) {
              // 具体实现
          }
      }
      
      // 在 module_cart 中通过接口调用(无需直接依赖 module_product)
      val productService = ServiceLoader.load(IProductService::class.java)
      productService.openProductDetail("123")
      
  3. 使用路由框架解耦页面跳转

    • 通过 ARouter、Navigator 等路由框架,避免页面间的直接依赖。
    • 示例:module_home 跳转到 module_product 的详情页时,通过路由协议(如 router://product/detail?id=123)而非直接引用Activity类。

三、循环依赖检测与解决工具

  1. Gradle 依赖分析
    运行命令查看依赖树,定位循环依赖:

    ./gradlew :app:dependencies --configuration releaseRuntimeClasspath
    
  2. Android Studio 插件
    使用 Dependency Analysis 插件可视化分析模块依赖关系。

  3. 架构守护工具
    通过 ArchUnit 编写规则,自动化检查分层是否被破坏:

    // 示例:禁止 base 模块依赖业务模块
    val layers = layeredArchitecture()
        .layer("Base").definedBy("com.example.base..")
        .layer("Business").definedBy("com.example.module..")
        .whereLayer("Business").mayNotBeAccessedByAnyLayer()
    

四、常见场景解决方案

场景1:模块A和模块B需要互相调用

  • 问题module_a 依赖 module_b,同时 module_b 依赖 module_a
  • 解决
    1. 将公共逻辑抽离到业务通用层(如 module_common)。
    2. 通过接口隔离:module_amodule_b 都依赖 module_interface,在接口模块中定义交互协议。

场景2:基础模块需要回调业务模块

  • 问题base 模块的通用弹窗需要回调 module_user 的登录逻辑。
  • 解决
    1. module_interface 中定义 ILoginCallback 接口。
    2. base 模块依赖 module_interface,通过接口回调。
    3. module_user 实现接口并注册到基础模块(如通过 ServiceLoader 或 Dagger)。

五、组件化依赖示例

project
├── base           // 基础层
├── common         // 业务通用层
├── module_interface // 接口定义
├── module_home    // 业务层-首页
├── module_product // 业务层-商品
├── module_cart    // 业务层-购物车
└── app           // 应用层

build.gradle 示例

// module_product 的依赖声明
dependencies {
    implementation project(':base')       // 基础层
    implementation project(':common')     // 业务通用层
    implementation project(':module_interface') // 接口
}

六、总结

  • 核心原则:分层设计 + 单向依赖 + 接口隔离。
  • 关键技巧:通过路由、接口、依赖注入解耦模块。
  • 工具辅助:利用 Gradle 和 ArchUnit 自动化检测。

通过以上规划,可彻底避免循环依赖,同时提升代码复用性和可维护性。