在 Android 组件化工程中,避免循环依赖的核心思路是 明确模块职责、建立单向依赖关系、合理分层,以下是具体规划和实践方案:
一、分层架构设计(核心原则)
将工程划分为 清晰的层级,确保依赖方向始终向下或平级,禁止反向依赖:
-
基础层(Base Layer)
- 职责:提供全局基础能力,如网络库、图片加载、工具类、通用UI组件等。
- 依赖规则:不依赖任何其他层,完全独立。
- 示例模块:
base、common、network、utils。
-
业务通用层(Business Common Layer)
- 职责:提供跨业务复用的能力,如登录服务、支付服务、埋点统计等。
- 依赖规则:只能依赖基础层,不依赖具体业务模块。
- 示例模块:
account(账号服务)、router(路由)、analytics(统计)。
-
业务层(Business Layer)
- 职责:独立业务功能模块,如首页、商品详情、购物车。
- 依赖规则:可依赖基础层和业务通用层,但禁止直接依赖其他业务层模块。
- 示例模块:
module_home、module_product、module_cart。
-
应用层(App Layer)
- 职责:主App模块,负责整合所有模块,初始化全局配置。
- 依赖规则:可依赖所有层,但仅用于组装和启动,不包含业务逻辑。
- 示例模块:
app。
二、依赖关系规范
-
严格单向依赖
- 高层模块可依赖低层模块,反之禁止(如
module_home可依赖base,但base不可依赖module_home)。 - 使用 Gradle 的
api和implementation控制依赖传递性,避免意外泄漏依赖。
- 高层模块可依赖低层模块,反之禁止(如
-
使用接口隔离(关键技巧)
- 定义接口模块:在业务通用层创建
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")
- 定义接口模块:在业务通用层创建
-
使用路由框架解耦页面跳转
- 通过 ARouter、Navigator 等路由框架,避免页面间的直接依赖。
- 示例:
module_home跳转到module_product的详情页时,通过路由协议(如router://product/detail?id=123)而非直接引用Activity类。
三、循环依赖检测与解决工具
-
Gradle 依赖分析
运行命令查看依赖树,定位循环依赖:./gradlew :app:dependencies --configuration releaseRuntimeClasspath -
Android Studio 插件
使用 Dependency Analysis 插件可视化分析模块依赖关系。 -
架构守护工具
通过 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。 - 解决:
- 将公共逻辑抽离到业务通用层(如
module_common)。 - 通过接口隔离:
module_a和module_b都依赖module_interface,在接口模块中定义交互协议。
- 将公共逻辑抽离到业务通用层(如
场景2:基础模块需要回调业务模块
- 问题:
base模块的通用弹窗需要回调module_user的登录逻辑。 - 解决:
- 在
module_interface中定义ILoginCallback接口。 base模块依赖module_interface,通过接口回调。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 自动化检测。
通过以上规划,可彻底避免循环依赖,同时提升代码复用性和可维护性。