Android 组件化学习项目(Kotlin + AGP8+)
一个适合学习 Android 组件化 的基础实战项目(Kotlin + AGP 8+)。
本仓库用两个业务组件对照讲解,而不是只堆模块数量:
仓库地址:点击进入
| 业务 | Gradle 模块 | 组件化方式 |
|---|---|---|
| 商城 | component-mall |
普通组件化(一个 Library 模块包打天下) |
| 用户 | user-api + user-impl |
API + Impl(合同与施工队分离) |
项目还包含:
common公共模块ServiceManager服务注册与获取UserRouter组件路由入口Application统一组件初始化- 组件通信(接口调用,而非直连 Activity)
适合:
- Android 组件化入门与进阶
- 理解「普通组件」与「api + impl」的差别
- 理解 Gradle 依赖与代码调用两条线
- 为企业多模块架构打基础
快速上手与构建说明见仓库根目录
READ.md。
零、先搞懂:什么叫「组件」?
把每个 Library 模块 想成公司里的一个业务部门:
- 对外只承诺一件事:例如「打开用户页」「打开商城页」
- 内部自己管页面、数据:
Activity、Repository不应被总部随意调用 - app 是「总部」:负责把各部门编进 APK,启动时统一「点名报到」(
init())
组件化要解决的核心问题只有一句:
txt
总部需要用人家的能力,但不应该绑死在人家内部的 Activity 类名上。
真正组件化的核心不是 拆了多少个 module,而是:
txt
让调用方(高层)不依赖具体实现(低层细节)。
一、项目结构(与仓库一致)
txt
ComponentDemo
│
├── app 主工程(壳、首页、组装依赖)
├── common 公共模块(ServiceManager、BaseActivity 等)
├── component-mall 商城组件(普通组件化)
├── user-api 用户组件 · 对外契约
├── user-impl 用户组件 · 具体实现
└── component-user 旧版用户组件(settings 中已注释,仅作对照)
settings.gradle.kts 当前包含:
kotlin
include(":app")
include(":component-mall")
include(":common")
include(":user-api")
include(":user-impl")
// include(":component-user") // 旧写法,已停用
二、模块说明与职能一览
| 模块 | 类型 | 一句话职能 | 是否业务组件 |
|---|---|---|---|
app |
application | 安装包入口;依赖并组装各模块;Application 里初始化组件 |
否(壳) |
common |
library | 公共基类、ServiceManager、工具;不写具体业务 |
否 |
component-mall |
library | 商城业务 + MallService 对外入口 |
是(Mall) |
user-api |
library | 用户对外「合同」:IUserService、UserRouter |
是(User 对外) |
user-impl |
library | 用户页面与实现;UserComponent.init() 注册服务 |
是(User 对内) |
component-user |
library(停用) | 旧版单模块用户组件,与 api+impl 对照 | 历史-普通组件写法 |
2.1 app ------ 主工程
职能:
- 生成可安装的 APK
MainActivity:演示如何打开用户页、商城页App.kt:调用UserComponent.init()、MallComponent.init()AppRouter:可集中封装商城跳转(MallService.start)
Gradle 依赖(实战配置):
kotlin
implementation(project(":common"))
implementation(project(":component-mall"))
implementation(project(":user-api"))
implementation(project(":user-impl"))
规范: 打开用户页应走 UserRouter,不要 Intent(this, UserActivity::class.java)。
2.2 common ------ 公共模块
职能: 所有业务组件都能依赖的「基础设施」,避免复制粘贴。
| 类 / 文件 | 作用(通俗说) |
|---|---|
ServiceManager |
服务台:登记、领取各组件实现的接口 |
BaseActivity |
业务 Activity 的公共父类 |
IService |
旧式组件服务标记(商城 MallService 使用) |
ToastUtils、RouterPath |
工具与常量 |
依赖规则: common 不依赖 任何 component-mall、user-api、user-impl。
2.3 component-mall ------ 商城组件(普通组件化)
职能: 商城相关能力全部放在一个 Library 里,对外只暴露 MallService。
目录结构(仓库实际):
txt
component-mall
├── api/MallService.kt 对外入口:start() 打开商城页
├── ui/MallActivity.kt 商城界面
├── repository/MallRepository.kt
├── data/Product.kt
└── MallComponent.kt 组件初始化
Gradle:
kotlin
implementation(project(":component-mall"))
调用:
kotlin
MallService.start(this)
// 或经 AppRouter:AppRouter.openMall(context)
2.4 user-api + user-impl ------ 用户组件(API + Impl)
user-api(合同)
| 文件 | 职能 |
|---|---|
IUserService |
定义对外能力(如 openUser) |
UserRouter |
统一入口:UserRouter.service() |
依赖:common。不依赖 user-impl。
user-impl(施工队)
| 文件 | 职能 |
|---|---|
UserServiceImpl |
实现 IUserService,内部跳转 UserActivity |
UserActivity |
用户界面 |
UserRepository、User |
数据层 |
UserComponent |
init() 时向 ServiceManager 注册实现 |
依赖:common、user-api。
调用(推荐):
kotlin
UserRouter.service().openUser(this)
二(补充)、两大业务组件对比
| 维度 | 商城 component-mall |
用户 user-api + user-impl |
|---|---|---|
| 模式名 | 普通 Library 组件化 | API + Impl 分离 |
| 模块个数 | 1 个业务模块 | 2 个(契约 + 实现) |
| 对外入口 | MallService 对象 |
IUserService + UserRouter |
| 思想 | 偏「面向实现」 | 偏「面向接口」 |
| app 依赖 | implementation(:component-mall) |
user-api + user-impl |
| 替换实现 | 较难(整包绑定) | 可换 impl 或自研后 register |
| 学习阶段 | 第一阶段 | 第二阶段(企业常用) |
| 本仓库角色 | 对照组:简单直观 | 主推:企业组件化第一阶段 |
记忆比喻:
- Mall :固定电话
MallService,拨号即用,简单。 - User :只认协议
IUserService;具体谁送货(UserServiceImpl)在启动时登记到服务台(ServiceManager),以后可换实现而不改总部代码。
二(补充)、引用关系(Gradle + 代码)
组件化要同时看清两条线:编译期谁依赖谁 、运行期谁调用谁。
Gradle 依赖(编译期)
txt
┌─────────┐
│ app │
└────┬────┘
┌────────────────┼────────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌─────────────────┐
│ common │ │ user-api │ │ component-mall │
└──────────┘ └────┬─────┘ └────────┬────────┘
│ │
▼ │
┌────────────┐ │
│ user-impl │───────────────┘
└────────────┘
(impl、mall 均依赖 common;
impl 只依赖 api,不依赖 mall)
依赖倒置(User):
txt
user-impl → user-api → common
app → user-api、user-impl、component-mall、common
注意:
txt
app 不应该在业务代码里直接调用 impl 内部实现(Activity、Repository)
Gradle 的 implementation 不会 禁止你在 app 里 import UserActivity,靠的是架构规范(下文第二十三、二十四节)。
代码调用(运行期)
用户组件
txt
App.onCreate()
→ UserComponent.init()
→ ServiceManager.register(IUserService, UserServiceImpl)
MainActivity 点击
→ UserRouter.service()
→ ServiceManager.get(IUserService)
→ openUser()
→ UserServiceImpl 内部 startActivity(UserActivity)
商城组件
txt
MainActivity 点击
→ AppRouter.openMall()
→ MallService.start()
→ MallActivity
商城路径更短,没有 ServiceManager,也更容易写成和内部类强耦合。
三、普通组件化(component-mall)
商城组件模块名是 component-mall(文档里简称 Mall 业务即可)。
属于:
txt
普通 Library 组件化
结构:
txt
component-mall
├── api MallService:对外唯一推荐入口
├── ui MallActivity
├── repository 数据获取
└── data Product 等模型
App 直接依赖整个模块:
kotlin
implementation(project(":component-mall"))
调用:
kotlin
MallService.start(this)
初始化(与 User 一样,在 Application 里):
kotlin
MallComponent.init()
四、普通组件化特点
优点:
- 简单
- 容易理解
- 开发速度快
- 适合中小项目、学习第一阶段
缺点:
- app 可以 访问组件内部类(如
MallActivity) - 耦合较高
- 不适合大型团队协作边界
- 不适合插件化、动态化
例如,技术上 App 可以直接:
kotlin
// 能编译,但不推荐
startActivity(Intent(this, MallActivity::class.java))
这属于:
txt
面向实现编程
五、api + impl 组件化(User)
User 组件拆成:
txt
user-api 对外合同
user-impl 对内实现
这是:
txt
企业组件化里非常核心的思想(本项目第一阶段)
六、目录结构(仓库实际)
txt
user-api
├── IUserService.kt
└── UserRouter.kt
user-impl
├── component/UserComponent.kt
├── service/UserServiceImpl.kt
├── ui/UserActivity.kt
├── repository/UserRepository.kt
└── data/User.kt
common
└── service/ServiceManager.kt
七、依赖关系(重点)
txt
app → user-api
app → user-impl (静态打包需要,见第十九~二十二节)
app → component-mall
app → common
user-impl → user-api
user-impl → common
user-api → common
component-mall → common
原则:
txt
业务代码:app 只通过 user-api(UserRouter / IUserService)使用用户能力
Gradle:app 为打完整 APK,仍需依赖 user-impl
八、为什么要 api + impl
核心思想:
txt
面向接口编程
而不是:
txt
面向实现编程
九、错误写法
kotlin
startActivity(
Intent(this, UserActivity::class.java)
)
问题:
- app 知道内部 Activity
- 高耦合
- impl 难以替换
- 后续难扩展、难做组件独立仓库
十、正确写法
kotlin
UserRouter
.service()
.openUser(this)
这样,App 在设计上只知道:
txt
IUserService
UserRouter
不应该依赖:
txt
UserActivity
UserRepository
UserServiceImpl
(这些应关在 user-impl 里。)
十一、IUserService
kotlin
interface IUserService {
fun openUser(context: Context)
}
作用:
txt
定义组件对外能力(合同上写清楚能干什么)
放在 user-api,任何调用方只需依赖 api 模块即可看到这份合同。
十二、UserServiceImpl
kotlin
class UserServiceImpl : IUserService {
override fun openUser(context: Context) {
context.startActivity(
Intent(context, UserActivity::class.java)
)
}
}
作用:
txt
真正实现组件逻辑(施工队干活)
跳转 UserActivity 的代码只应出现在 impl,不应散落在 app 的其他业务里。
十三、ServiceManager(重点)
kotlin
object ServiceManager
作用:
txt
管理所有组件服务(登记 + 领取)
类似:
txt
简化版 IoC 容器 / 公司前台服务台
位置:common ,各业务组件在 init() 时注册,调用方通过 api 里的 UserRouter 间接获取。
十四、注册服务
在 UserComponent.init() 中:
kotlin
ServiceManager.register(
IUserService::class.java,
UserServiceImpl()
)
含义:
txt
把「用户能力」的实现登记到服务台,键是 IUserService 类型
十五、获取服务
在 UserRouter 中:
kotlin
ServiceManager.get(
IUserService::class.java
)
对外暴露为:
kotlin
UserRouter.service() // 返回 IUserService
十六、为什么会出现 NPE
错误现象:
txt
IUserService.openUser()
on a null object reference
常见原因:
txt
IUserService 还没注册到 ServiceManager
也就是:
kotlin
UserComponent.init()
没执行,或 Application 根本没跑起来。
十七、正确初始化流程
App.kt:
kotlin
class App : Application() {
override fun onCreate() {
super.onCreate()
UserComponent.init()
MallComponent.init()
}
}
两个组件都要初始化: User 注册服务;Mall 目前可做日志、预加载等扩展。
十八、Manifest 必须配置 Application
xml
<application
android:name=".App"
/>
否则:
txt
Application.onCreate 不会执行
→ UserComponent.init() 不会执行
→ 调用 UserRouter 容易崩溃
十九、为什么 app 还需要依赖 user-impl
很多人会问:
txt
既然已经 api + impl
为什么 app 还需要:
implementation(project(":user-impl"))
原因:
txt
当前还是静态组件化
不是:
txt
动态组件化 / 插件化
二十、为什么 impl 必须参与编译
因为 Android 里:
txt
Activity
布局资源
AndroidManifest
都必须:
txt
在编译期打进 APK
否则:
txt
ActivityNotFoundException
UserActivity 声明在 user-impl 的 Manifest 中,所以主工程组装时必须带上 impl 模块。
二十一、当前属于什么组件化
当前属于:
txt
静态组件化
特点:
txt
编译期就把 app、common、component-mall、user-api、user-impl 打进同一个 APK
二十二、真正不依赖 impl 的方案
只有走向:
- 动态 Feature Module
- 插件化
- 动态加载 Dex
才有可能在依赖图上做到:
txt
app 只依赖 user-api(运行时再去加载 impl)
本学习项目刻意用静态方式,先把 api/impl 思想练熟。
二十三、为什么 app 仍能看到 impl 类
即使:
kotlin
implementation(project(":user-impl"))
App 仍然能 import:
kotlin
import com.example.user_impl.ui.UserActivity
原因:
txt
implementation 不是「源码级隔离」
它只是:
txt
控制依赖传递,不暴露给「依赖 app 的第三方模块」
同工程内的 app 模块仍能看到 impl 里所有 public 类。
二十四、真正企业如何限制
企业一般通过:
- lint 自定义规则
- detekt
- Code Review
- ArchUnit
- Kotlin
internal+ 模块可见性 - 禁止 app 模块 import
*.user_impl.*
来约束:
txt
app 业务代码禁止直接调用 impl
本仓库学习建议: 打开用户页只写 UserRouter.service().openUser(),当作纪律练习。
二十五、当前项目已经具备的企业思想
当前已经具备:
- api + impl 分层(User)
- 普通组件对照(Mall)
- Router(
UserRouter、AppRouter) - ServiceManager 注册发现
- 面向接口编程
- 模块边界与依赖倒置(impl → api)
- Application 统一组件初始化
已经属于:
txt
企业组件化第一阶段(静态 + 接口化)
二十六、当前还缺少什么
真正企业级通常还需要:
- 自动注册组件(KSP / ASM)
- 组件独立调试(单独 Run 某个 Library)
component-mall也升级为mall-api/mall-impl- 动态组件化、插件化
- 路由表自动生成(ARouter 等)
- Convention Plugin 统一 Gradle 配置
- 组件依赖治理与版本对齐
二十七、推荐学习路线(结合本仓库)
第一阶段:普通组件化
对照 component-mall:
- 看
MallService、MallActivity、MallComponent - 理解
implementation(project(":component-mall"))
第二阶段:api + impl
对照 user-api、user-impl:
- 看
IUserService、UserRouter - 看
UserServiceImpl、UserComponent
第三阶段:ServiceManager + 初始化
- 搞懂
register/get - 搞懂
App+ Manifest - 搞懂 NPE 与未注册的关系
第四阶段:自动注册组件
(本仓库尚未实现,可作为扩展作业。)
第五阶段:组件独立运行
给单个 Library 加独立 Application 与 Manifest,单独 Run。
第六~七阶段:动态组件化、插件化
理解第二十二节:为何那时 app 才可能「只依赖 api」。
二十八、这个学习项目的真正价值
它不是:
txt
简单 multi-module Demo
而是:
在一个 APK 里并排演示两种组件写法,真正学习:
- Android 组件化
- 模块职能与边界
- Gradle 引用 vs 代码调用
- 依赖倒置
- 面向接口与面向实现的差别
的起点。
二十九、当前项目适合谁
适合:
- Android 初学组件化
- Android 进阶,准备接触企业多模块
- 想在一套代码里对比 Mall 与 User
- 想理解 api + impl、Router、ServiceManager
三十、总结
Mall(component-mall):
txt
普通组件化
特点:
- 简单、开发快
- 一个模块包含 api + ui + data
- 耦合相对较高,适合入门与中小功能
User(user-api + user-impl):
txt
api + impl 组件化
特点:
- 调用方依赖 api,面向接口
- 实现可替换、边界更清晰
- 更适合大型项目、多团队、后续动态化演进
common:
txt
公共底座,不是业务组件
app:
txt
壳 + 组装 + 初始化,不写重业务
三十一、最后一句话(核心)
真正组件化核心:
不是:
txt
拆多少 module
而是:
txt
让高层不依赖具体实现
这才是:
txt
Android 企业组件化真正核心思想
本项目中:
- 点「打开用户组件」→ 走 接口 + Router + ServiceManager(推荐范式)
- 点「打开商城组件」→ 走 MallService(简单范式)
两条路都跑通之后,再决定团队新业务默认哪一套。