Android 模块化、组件化、插件化架构完全指南

从基础概念到实战应用,系统梳理 Android 架构演进之路


一、基础概念

1.1 为什么需要架构优化?

随着业务迭代,单体 App 会遇到诸多问题:

问题 表现 影响
代码耦合严重 模块间直接 import,循环依赖 难以维护、编译慢
编译效率低 改一行代码全量编译 开发效率下降
团队协作冲突 多人修改同一工程 Git 冲突频繁
无法独立开发 强依赖主工程 无法并行开发、独立测试
复用困难 业务逻辑与 UI 混在一起 跨项目复用成本高

1.2 三种架构模式辨析

1
2
3
4
5
6
7
8
9
10
11
12
┌─────────────────────────────────────────────────────────────────┐
│ 架构演进路径 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 单体架构 ──► 模块化 ──► 组件化 ──► 插件化 │
│ │ │ │ │ │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ 按功能拆分 Gradle模块 路由解耦 运行时动态加载 │
│ 包结构划分 build独立 ARouter等 热更新/按需下载 │
│ │
└─────────────────────────────────────────────────────────────────┘

模块化(Modularization)

  • 定义:按业务功能将代码拆分为独立的 Gradle 模块,每个模块有清晰的 build.gradle 和边界
  • 特点:物理隔离、可独立编译,通常仍在同一 Git 仓库内
  • 粒度:中等,如「用户模块」「订单模块」「支付模块」

组件化(Componentization)

  • 定义:在模块化基础上,通过路由/服务发现实现模块间完全解耦,可独立开发、独立运行
  • 特点:依赖倒置、接口隔离、可单模块调试(Application 切换)
  • 粒度:较细,如「登录组件」「分享组件」「埋点组件」

插件化(Pluginization)

  • 定义:插件 APK/Dex 可动态加载、热更新,主 App 与插件解耦到运行时
  • 特点:运行时动态、按需下载、可热修复、减小包体积
  • 粒度:独立 APK/模块

二、模块化原理与实践

2.1 模块化的核心原则

  1. 单一职责:每个模块只负责一块业务
  2. 接口隔离:模块间通过接口/路由通信,不暴露实现细节
  3. 依赖倒置:依赖抽象(interface)而非具体实现

2.2 Gradle 多模块目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
MyApp/
├── app/ # 主工程壳
│ └── build.gradle
├── module_user/ # 用户模块
│ ├── build.gradle
│ └── src/main/
├── module_order/ # 订单模块
│ └── build.gradle
├── module_payment/ # 支付模块
│ └── build.gradle
├── common_base/ # 公共基础库
│ ├── network/
│ ├── utils/
│ └── base/
├── common_router/ # 路由层
│ └── build.gradle
├── build.gradle
└── settings.gradle

2.3 settings.gradle 与模块声明

1
2
3
4
5
6
7
8
// settings.gradle
rootProject.name = "MyApp"
include ':app'
include ':module_user'
include ':module_order'
include ':module_payment'
include ':common_base'
include ':common_router'

2.4 模块间依赖配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// app/build.gradle - 主工程依赖各业务模块
dependencies {
implementation project(':module_user')
implementation project(':module_order')
implementation project(':module_payment')
implementation project(':common_router')
implementation project(':common_base')
}

// module_user/build.gradle - 业务模块只依赖基础库和路由
dependencies {
implementation project(':common_base')
implementation project(':common_router')
// 不要依赖 module_order、module_payment!
}

// common_base/build.gradle - 基础库不依赖任何业务模块
dependencies {
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
}

2.5 模块间通信:接口 + 实现注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// common_router 中定义接口
interface IUserService {
fun getCurrentUser(): User?
fun logout()
}

// module_user 中实现接口
class UserServiceImpl : IUserService {
override fun getCurrentUser(): User? = UserManager.currentUser
override fun logout() { /* ... */ }
}

// 通过 ServiceLocator 或依赖注入框架注入
object ServiceLocator {
var userService: IUserService? = null
}

// module_order 中调用
class OrderActivity : AppCompatActivity() {
private val userService: IUserService? by lazy { ServiceLocator.userService }

fun showUserInfo() {
userService?.getCurrentUser()?.let { user ->
// 显示用户信息
}
}
}

三、组件化原理与实现

3.1 组件化架构图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
                ┌──────────────────┐
│ app (壳) │
│ 主工程/组装 │
└────────┬─────────┘

┌──────────────┼──────────────┐
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ module_user │ │ module_order│ │module_payment│
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
└───────────────┼───────────────┘

┌──────▼──────┐
│ ARouter │
│ 路由中间层 │
└──────┬──────┘

┌──────▼──────┐
│ common_base │
│ 基础组件库 │
└─────────────┘

3.2 路由方案对比

方案 原理 优点 缺点
ARouter 注解 + 编译期生成路由表 阿里出品、生态成熟、支持拦截器 需引入注解处理器
WMRouter 美团方案,分组路由 按需加载、包体积优化 学习成本略高
CC (Component Call) 组件调用框架 同步/异步调用、跨进程 概念较多
手写路由表 Map 映射 简单、无依赖 维护成本高

3.3 单模块独立运行(Application 切换)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// module_user/build.gradle
android {
defaultConfig {
// 作为 Application 运行时使用
if (project.hasProperty('runAsApp') && runAsApp) {
applicationId "com.example.user"
}
}
sourceSets {
main {
manifest.srcFile 'src/main/AndroidManifest.xml'
if (project.hasProperty('runAsApp') && runAsApp) {
manifest.srcFile 'src/main/debug/AndroidManifest.xml'
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- module_user/src/main/debug/AndroidManifest.xml - 独立运行时的 Manifest -->
<manifest>
<application
android:name=".UserDebugApplication"
android:label="User Module Debug">
<activity android:name=".UserActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
1
2
# 独立运行用户模块
./gradlew :module_user:installDebug -PrunAsApp=true

3.4 ARouter 使用与原理

3.4.1 添加依赖

1
2
3
4
5
// 各模块 build.gradle
dependencies {
implementation 'com.alibaba:arouter-api:1.5.2'
kapt 'com.alibaba:arouter-compiler:1.5.2'
}

3.4.2 路由配置与跳转

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// ========== 组件端:Order 模块 ==========
// 在 OrderListActivity 上添加路由注解
@Route(path = "/order/list")
class OrderListActivity : AppCompatActivity() {
@Autowired
lateinit var userId: String

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ARouter.getInstance().inject(this)
// userId 已自动注入
}
}

// ========== 调用端:任意模块 ==========
ARouter.getInstance()
.build("/order/list")
.withString("userId", "12345")
.navigation()

3.4.3 服务发现(跨模块获取实现)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 定义接口(可在 common_base 或单独 api 模块)
interface IOrderService {
fun getOrderCount(userId: String): Int
}

// Order 模块实现并注册
@Route(path = "/service/order")
class OrderServiceImpl : IOrderService {
override fun getOrderCount(userId: String): Int = /* ... */
}

// 调用端获取
val orderService = ARouter.getInstance()
.navigation(IOrderService::class.java)
orderService?.getOrderCount("12345")

3.4.5 ARouter 核心源码解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 简化版 ARouter 核心逻辑
// 1. 初始化时扫描 Dex,加载路由表(通过 Gradle 插件编译期生成)
class Warehouse {
// 路由表:path -> RouteMeta
static Map<String, RouteMeta> routes = ConcurrentHashMap()
// 分组缓存
static Map<String, Class<? extends IRouteGroup>> groupsIndex = ConcurrentHashMap()
}

// 2. navigation 流程
fun navigation(path: String): Postcard {
val meta = Warehouse.routes[path] // 从路由表获取
return Postcard(path, meta)
}

fun Postcard.navigation(): Any? {
return _ARouter.navigation(context, this, requestCode, navigationCallback)
}

// 3. 实际跳转
fun _ARouter.navigation(...) {
when (meta.type) {
RouteType.ACTIVITY -> {
val intent = Intent(context, meta.destination)
// 注入参数
context.startActivity(intent)
}
RouteType.ISERVICE -> {
return meta.destination.newInstance() // 服务实现类
}
}
}

// 4. 编译期注解处理器生成路由表
// 生成类似:ARouter$$Group$$order、ARouter$$Providers$$order
// 在 init() 时通过反射加载到 Warehouse

3.5 依赖关系设计原则

1
2
3
4
5
6
7
8
9
业务模块 (user/order/payment)

├── 只依赖 common_base、common_router (arouter-api)

└── 业务模块之间 不直接依赖

app 壳工程

└── 依赖所有业务模块,负责组装和 Application 初始化

四、插件化原理与实现

4.1 插件化的应用场景

  • 热更新:修复线上 Bug 无需发版
  • 按需加载:减小包体积,冷启动只加载核心
  • 动态能力:运营活动插件、A/B 测试模块
  • 多端复用:同一套插件可被主 App、Split APK 加载

4.2 Android 插件化技术选型

方案 说明 适用场景
Dynamic Feature Modules Google 官方,AGP 支持 按需下载、免安装功能模块
VirtualAPK 滴滴开源 完整的插件化框架
RePlugin 360 出品 插件即独立 APK,稳定成熟
Shadow 腾讯开源 支持 Android 10+,零 Hook
Atlas 阿里 大型 App 动态化

4.3 Dynamic Feature Modules(官方方案)

4.3.1 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// settings.gradle
include ':app'
include ':feature:order' // 动态功能模块

// app/build.gradle
dependencies {
implementation project(':common_base')
dynamicFeatures = [':feature:order']
}

// feature/order/build.gradle
apply plugin: 'com.android.dynamic-feature'
android {
defaultConfig {
minSdk 24
}
}
dependencies {
implementation project(':app')
}

4.3.2 按需下载与安装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 使用 Play Core Library 下载动态模块
val splitInstallManager = SplitInstallManagerFactory.create(context)
val request = SplitInstallRequest.newBuilder()
.addModule("order")
.build()

splitInstallManager.startInstall(request)
.addOnSuccessListener {
// 安装成功,可跳转
startActivity(Intent(this, OrderActivity::class.java))
}
.addOnFailureListener {
// 处理失败
}

4.4 RePlugin 核心原理简述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
┌──────────────────────────────────────────────────────┐
│ Host App │
│ ┌────────────────────────────────────────────────┐ │
│ │ RePluginHostConfig / PluginManager │ │
│ │ - 加载插件 APK │ │
│ │ - 替换 ClassLoader │ │
│ │ - Hook Activity/Service 等组件 │ │
│ └────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────┘

┌───────────────┼───────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Plugin1 │ │ Plugin2 │ │ Plugin3 │
│ .apk │ │ .apk │ │ .apk │
└──────────┘ └──────────┘ └──────────┘

核心流程

  1. Dex 加载:将插件 APK 的 classes.dex 加入宿主的 DexPathList
  2. 资源加载:创建 Resources,合并插件的 AssetManager
  3. 组件占位:通过 Hook Instrumentation/IActivityManager,用占位 Activity 代理插件 Activity

4.5 手写简易插件加载(Dex 加载)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 简化示例:加载插件 APK 中的类
class PluginLoader(private val context: Context) {

private val pluginClassLoaders = mutableMapOf<String, DexClassLoader>()

fun loadPlugin(apkPath: String): Boolean {
val optimizedDir = File(context.filesDir, "plugin_opt").apply { mkdirs() }
val libDir = File(context.filesDir, "plugin_lib").apply { mkdirs() }

val classLoader = DexClassLoader(
apkPath,
optimizedDir.absolutePath,
null,
context.classLoader
)
pluginClassLoaders[apkPath] = classLoader
return true
}

fun loadClass(apkPath: String, className: String): Class<*>? {
val loader = pluginClassLoaders[apkPath] ?: return null
return try {
loader.loadClass(className)
} catch (e: ClassNotFoundException) {
null
}
}
}

// 加载插件中的 Activity(实际还需 Hook 组件启动流程)
val loader = PluginLoader(context)
loader.loadPlugin("/sdcard/plugin.apk")
val clazz = loader.loadClass("/sdcard/plugin.apk", "com.plugin.MainActivity")
val activity = clazz.newInstance()

4.6 插件化资源加载

1
2
3
4
5
6
7
8
9
10
11
12
// 合并插件资源到宿主
fun createPluginResources(hostResources: Resources, apkPath: String): Resources {
val assets = AssetManager::class.java.newInstance()
val addAssetPath = AssetManager::class.java.getMethod("addAssetPath", String::class.java)
addAssetPath.invoke(assets, apkPath)

return Resources(
assets,
hostResources.displayMetrics,
hostResources.configuration
)
}

五、ARouter 框架源码深度解析

5.1 整体架构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
┌────────────────────────────────────────────────────────┐
│ ARouter │
│ - 对外 API:build、navigation、inject │
└────────────────────────────────────────────────────────┘

┌─────────────────────────┴─────────────────────────────┐
│ _ARouter (内部实现) │
│ - LogisticsCenter:路由表、分组加载 │
│ - Warehouse:路由/服务/拦截器 存储 │
└────────────────────────────────────────────────────────┘

┌─────────────────┼─────────────────┐
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ 注解处理器 │ │ 拦截器链 │ │ 降级策略 │
│ 编译期生成 │ │ 可扩展 │ │ DegradeService│
└──────────────┘ └──────────────┘ └──────────────┘

5.2 LogisticsCenter 路由表加载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 初始化时通过插件扫描 Dex 中的特定类
public static void init(Context context) {
try {
// 获取所有 ARouter 生成的类:ARouter$$Root$$xxx
Set<String> routerMap = ClassUtils.getFileNameByPackageName(
context, ROUTE_ROOT_PACKAGE);
for (String className : routerMap) {
if (className.startsWith(ROUTE_ROOT_PACKAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
((IRouteRoot) Class.forName(className).getConstructor().newInstance())
.loadInto(Warehouse.groupsIndex);
}
}
// 分组按需加载,避免启动时加载全部
} catch (Exception e) {
throw new HandlerException("ARouter init logistics center exception");
}
}

5.3 拦截器机制

1
2
3
4
5
6
7
8
9
10
11
12
@Interceptor(priority = 8)
class LoginInterceptor : IInterceptor {
override fun process(postcard: Postcard, callback: InterceptorCallback) {
if (postcard.extra == NEED_LOGIN && !UserManager.isLoggedIn()) {
// 未登录,拦截并跳转登录
ARouter.getInstance().build("/user/login").navigation()
callback.onInterrupt(null)
} else {
callback.onContinue(postcard)
}
}
}

六、实际项目应用案例

6.1 某电商 App 组件化拆分

1
2
3
4
5
6
7
8
9
10
11
app (壳工程)
├── module_home # 首页
├── module_product # 商品详情
├── module_cart # 购物车
├── module_order # 订单
├── module_user # 用户中心
├── module_payment # 支付
├── module_share # 分享(可复用)
├── module_analytics # 埋点(可复用)
├── common_router # ARouter 封装
└── common_base # 网络/缓存/UI 基础库

收益

  • 编译时间:全量 6min → 单模块 1min
  • 4 个业务线可并行开发,Git 冲突减少 60%
  • ShareModule、AnalyticsModule 复用到多个 App

6.2 路由表设计实战

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 统一路由 Path 常量
object RouterPath {
const val ORDER_LIST = "/order/list"
const val ORDER_DETAIL = "/order/detail"
const val USER_LOGIN = "/user/login"
const val USER_PROFILE = "/user/profile"
}

// 封装便捷方法
object Router {
fun toOrderList(userId: String) {
ARouter.getInstance()
.build(RouterPath.ORDER_LIST)
.withString("userId", userId)
.navigation()
}

fun toOrderDetail(orderId: String) {
ARouter.getInstance()
.build(RouterPath.ORDER_DETAIL)
.withString("orderId", orderId)
.navigation()
}
}

6.3 解耦实践:避免循环依赖

错误示例

1
2
module_order -> module_user (获取用户信息)
module_user -> module_order (跳转订单列表)

形成循环依赖,Gradle 编译失败。

正确做法

1
2
module_order -> common_router
module_user -> common_router

需要「用户信息」时,Order 通过 ARouter.getInstance().navigation(IUserService::class.java) 获取;需要「跳转订单」时,User 通过 ARouter.getInstance().build("/order/list").navigation() 跳转。

6.4 模块化 + 插件化组合:Dynamic Feature 实践

某资讯 App 将「小说」「漫画」等非核心功能做成 Dynamic Feature,用户首次进入时提示下载:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 检查模块是否已安装
fun isModuleInstalled(moduleName: String): Boolean {
return SplitInstallManagerFactory.create(context)
.installedModules.contains(moduleName)
}

// 进入小说模块前检查
fun openNovelModule() {
if (isModuleInstalled("novel")) {
startActivity(Intent(this, NovelActivity::class.java))
} else {
showDownloadDialog {
installDynamicModule("novel")
}
}
}

七、最佳实践与注意事项

7.1 模块拆分原则

  • 高内聚低耦合:模块内聚度高,模块间依赖少
  • 按业务边界拆分:参考 DDD 的 Bounded Context
  • 基础组件下沉:网络、缓存、日志等抽成 common_base
  • 渐进式演进:先模块化再组件化,避免一步到位导致成本过高

7.2 常见坑与规避

问题 原因 规避
编译顺序错误 模块间隐式依赖 严格检查 build.gradle,用 ./gradlew :module_x:dependencies 检查
路由表膨胀 所有页面都注册路由 仅对外暴露的页面注册,内部页面用 startActivity
启动变慢 初始化时加载全部路由 使用分组按需加载(ARouter 已支持)
R 文件冲突 多模块资源 ID 冲突 在 build.gradle 中设置 resourcePrefix "module_user_"
插件化兼容性 不同厂商 ROM 限制 优先使用 Dynamic Feature,第三方框架需充分测试

7.3 R 文件与资源隔离

1
2
3
4
// 各业务模块 build.gradle 中
android {
resourcePrefix "user_" // 资源必须以 user_ 开头
}

7.4 架构演进路线图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Phase 1: 模块化
├── 拆分为 Gradle 多模块
├── 建立 common_base、common_router
└── 模块间通过接口通信

Phase 2: 组件化
├── 引入 ARouter 路由
├── 服务发现替代直接依赖
└── 支持单模块独立运行调试

Phase 3: 插件化(可选)
├── 非核心功能改为 Dynamic Feature
├── 或引入 RePlugin/Shadow 做完整插件化
└── 按需下载、热更新

八、总结

架构 适用场景 核心手段
模块化 中小型 App、团队 < 10 人 Gradle 多模块、接口、依赖配置
组件化 中大型 App、多业务线并行 ARouter、服务发现、单模块调试
插件化 需要动态化、热更新、包体积优化 Dynamic Feature、RePlugin、Shadow

架构没有银弹,需结合团队规模、业务复杂度、迭代节奏选择合适方案。建议从模块化起步,随着复杂度提升再逐步演进到组件化,插件化则按实际需求谨慎引入。


参考资源

Android 模块化、组件化、插件化架构完全指南

https://bitgarden.cn/2021/02/26/Android/Android-模块化组件化插件化架构/

Author

Felix Tao

Posted on

2021-02-26

Updated on

2021-02-26

Licensed under