Android 模块化架构与依赖边界

举报
shenlan9755 发表于 2026/09/11 09:06:20 2026/09/11
【摘要】 Android 模块化架构与依赖边界随着业务增长,单一模块会逐渐出现编译缓慢、代码相互引用、资源命名冲突和改动影响范围过大的问题。模块化可以隔离业务边界,但模块数量越多并不代表架构越好。真正有价值的模块化,应让依赖方向清晰、公共接口稳定,并降低团队并行开发时的冲突。 一、先识别模块边界常见模块可以按职责划分:应用壳模块:负责进程入口、全局装配和最终打包。业务模块:围绕账户、交易、资讯等完整...

Android 模块化架构与依赖边界

随着业务增长,单一模块会逐渐出现编译缓慢、代码相互引用、资源命名冲突和改动影响范围过大的问题。模块化可以隔离业务边界,但模块数量越多并不代表架构越好。真正有价值的模块化,应让依赖方向清晰、公共接口稳定,并降低团队并行开发时的冲突。

一、先识别模块边界

常见模块可以按职责划分:

  • 应用壳模块:负责进程入口、全局装配和最终打包。
  • 业务模块:围绕账户、交易、资讯等完整业务能力组织。
  • 基础能力模块:网络、数据库、日志、主题等通用能力。
  • 公共界面模块:项目统一控件、样式和资源。
  • 纯领域模块:业务模型与规则,不依赖 Android 界面。

边界应来自业务变化频率和团队协作方式,而不是按每个页面建立一个模块。过细拆分会增加构建配置和跨模块跳转成本。

二、让依赖方向保持单向

业务模块可以依赖稳定的基础接口,基础模块不应反向知道具体业务。两个业务模块如果相互依赖,通常说明公共契约或流程协调职责没有被正确提取。

例如,订单模块只依赖账户读取能力:

interface AccountSession {
    fun currentUserId(): String?
    fun isLoggedIn(): Boolean
}

class SubmitOrderUseCase(
    private val accountSession: AccountSession,
    private val orderRepository: OrderRepository
) {
    suspend operator fun invoke(order: PendingOrder): SubmitResult {
        val userId = accountSession.currentUserId()
            ?: return SubmitResult.LoginRequired
        return orderRepository.submit(userId, order)
    }
}

账户模块或应用装配层提供实现,订单模块只看到接口,因此不需要引用账户页面和内部存储细节。

三、区分 API 与 implementation

依赖如果通过模块公共接口暴露给下游,应使用公开依赖;仅在模块内部使用的库和实现应保持内部依赖。过度暴露会扩大编译影响范围,也会让下游代码意外依赖传递细节。

判断方式很直接:下游模块编译自己的公共代码时,是否必须知道这个类型。如果不需要,就不应暴露。

四、控制模块公共表面

模块公共类越多,后续重构越困难。业务模块可以只公开少量入口、路由参数和结果类型,其余页面、适配器、数据库实体保持模块内部可见。

data class OrderDetailArgs(
    val orderId: String,
    val source: OrderSource
)

interface OrderNavigator {
    fun openOrderDetail(args: OrderDetailArgs)
}

路由参数应使用稳定业务字段,不要直接传递整个可变实体。目标模块可以根据标识加载权威数据,也能减少跨模块序列化耦合。

五、避免建立万能公共模块

当任何不好归类的代码都被放进 common,这个模块最终会依赖所有基础能力,并被所有业务依赖,成为新的单体。公共模块只应包含语义稳定、确实被多个模块复用的内容。

复用一段代码并不总值得提取。如果两个业务当前实现相似,但未来变化原因不同,保留各自实现可能更合理。

六、管理资源与主题

多模块中的布局、颜色和图片容易重名。资源名称应遵循项目既有前缀规范,业务模块优先使用公共主题属性,而不是复制颜色值。

公共组件中的 TextView 宽高应优先使用 wrap_content,字号使用 dp,再通过约束控制可用空间,避免模块复用到不同屏幕时发生裁剪。固定尺寸只用于设计明确且经过适配验证的区域。

模块不应覆盖含义模糊的全局资源名,否则合并资源时可能产生难以察觉的样式变化。

七、使用依赖注入完成装配

业务模块声明所需接口,应用层决定使用哪个实现。无论项目采用手工工厂还是依赖注入框架,都应让装配集中可查。

class OrderFeatureFactory(
    private val accountSession: AccountSession,
    private val orderRepository: OrderRepository
) {
    fun createSubmitOrderUseCase(): SubmitOrderUseCase {
        return SubmitOrderUseCase(accountSession, orderRepository)
    }
}

不要在业务代码内部到处读取全局单例,这会隐藏真实依赖并增加测试替换难度。

八、控制构建配置重复

每个模块复制一份插件、编译版本和静态检查配置,会导致升级时遗漏。可以使用项目已有的构建约定统一公共配置,让模块脚本只表达自身差异。

新增构建能力前必须确认与项目最低兼容版本、当前构建工具版本相符,不能为了局部便利破坏整体构建环境。

九、逐步实施模块化

大型项目不适合一次性移动所有代码。可以从依赖相对独立、边界清楚的业务开始:

  1. 记录当前模块依赖和跨业务调用。
  2. 定义少量稳定公共契约。
  3. 把实现迁移到新模块,保持行为不变。
  4. 删除旧入口并运行完整验证。
  5. 观察编译时间与协作冲突是否改善。

迁移期间避免同时进行大规模业务改造,否则出现问题时很难区分是架构移动还是逻辑变化造成的。

总结

模块化的目标是建立边界,而不是追求模块数量。围绕业务能力拆分,保持单向依赖,缩小公共接口,并把实现装配集中到应用层,才能降低编译影响和修改风险。任何拆分都应通过构建速度、依赖复杂度和团队协作效果来验证价值。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。