Android KMP(Kotlin Multiplatform)

由浅入深,从基本概念到源码原理,再到实战示例与生产级应用案例,系统梳理 Kotlin 跨平台开发之道


一、基础概念

1.1 什么是 Kotlin Multiplatform(KMP)?

Kotlin Multiplatform(又称 KMP 或 KMM)是 JetBrains 推出的跨平台代码共享方案,其核心理念是:共享业务逻辑,保留原生 UI

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
┌─────────────────────────────────────────────────────────────────────────┐
│ KMP 架构:共享逻辑,原生 UI │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Android │ │ iOS │ │ Web │ │
│ │ (Kotlin) │ │ (Swift) │ │ (JS/WasM) │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │
│ │ 原生 UI 层 │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ 共享模块 (shared / commonMain) │ │
│ │ 业务逻辑 · 网络请求 · 数据模型 · 存储 · ViewModel │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘

与 Flutter、React Native 等「一套 UI 到处跑」的方案不同,KMP 让你:

  • Kotlin 写共享的业务逻辑、网络、数据层
  • Jetpack Compose 写 Android UI,用 SwiftUI 写 iOS UI
  • 得到的是 原生体验,而非 WebView 或自绘引擎

1.2 为什么需要 KMP?

痛点 KMP 的解决方式
Android / iOS 重复实现业务逻辑 共享 ViewModel、UseCase、Repository 等
双端行为不一致(如计算逻辑) 同一套 Kotlin 代码,编译到各平台
双端各自维护一套网络/序列化代码 共享 Ktor + kotlinx.serialization
希望跨平台但不牺牲原生体验 保留原生 UI 框架
团队已有 Kotlin 基础 复用技能栈,降低学习成本

1.3 KMP 与主流跨平台方案对比

维度 KMP Flutter React Native
UI 方案 原生 UI(Compose / SwiftUI) 自绘引擎(Skia) 原生组件 + Bridge
共享范围 业务逻辑、网络、数据 UI + 逻辑全部共享 UI + 逻辑共享
性能 接近原生 接近原生 依赖 JS Bridge
包体积 共享库较小 需携带 Flutter 引擎 需携带 RN 运行时
生态 Kotlin 生态、官方支持 Dart/Flutter 生态 JS/React 生态
学习曲线 Kotlin 开发者友好 需学 Dart 需学 React/JS

适用场景:KMP 更适合「业务逻辑复杂、希望 UI 保持原生」的应用,如金融、电商、工具类 App。


二、核心原理

2.1 编译模型

KMP 将 Kotlin 代码编译成各平台的原生产物,而不是通过虚拟机或解释器运行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
                    ┌──────────────────┐
│ commonMain │
│ (共享 Kotlin) │
└────────┬─────────┘

┌───────────────────┼───────────────────┐
▼ ▼ ▼
┌────────────────┐ ┌────────────────┐ ┌────────────────┐
│ JVM / Android │ │ Kotlin/Native │ │ Kotlin/JS │
│ (.class/DEX) │ │ (LLVM → .a) │ │ (JS/Wasm) │
└────────────────┘ └────────────────┘ └────────────────┘
│ │ │
▼ ▼ ▼
Android APK iOS Framework Web Bundle
  • Android:Kotlin → JVM 字节码 → DEX → APK
  • iOS:Kotlin → Kotlin/Native(LLVM)→ 静态库/框架
  • Web:Kotlin → JavaScript 或 WebAssembly

因此,共享代码在运行时就是本地代码,无额外解释层。

2.2 expect / actual 机制

当共享代码需要调用平台特有 API 时(如文件系统、UUID、日期格式化),KMP 使用 expect / actual 做编译期抽象:

在 commonMain 中声明 expect(契约):

1
2
3
4
5
// commonMain/kotlin/platform/Platform.kt
package platform

expect fun currentTimeMillis(): Long
expect fun randomUUID(): String

在各平台提供 actual 实现:

1
2
3
4
5
6
7
// androidMain/kotlin/platform/Platform.android.kt
package platform

import java.util.UUID

actual fun currentTimeMillis(): Long = System.currentTimeMillis()
actual fun randomUUID(): String = UUID.randomUUID().toString()
1
2
3
4
5
6
7
8
// iosMain/kotlin/platform/Platform.ios.kt
package platform

import platform.Foundation.NSDate
import platform.Foundation.NSUUID

actual fun currentTimeMillis(): Long = (NSDate().timeIntervalSince1970 * 1000).toLong()
actual fun randomUUID(): String = NSUUID().UUIDString()

原理要点

  • 编译时,编译器将 expect 与对应平台的 actual 匹配
  • 每个目标平台都必须有且仅有一个 actual 实现
  • 保证 common 代码只能依赖抽象,无法引用平台专属 API

2.3 Source Sets(源集)与层级结构

KMP 用 Source Set 组织代码,每个源集对应一组目标平台:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
┌─────────────────────────────────────────────────────────────────┐
│ Source Sets 层级 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ commonMain ◄── 编译到所有目标,只可写平台无关代码 │
│ │ │
│ ├── androidMain ◄── 仅 Android │
│ │ │
│ ├── iosMain (中间源集) ◄── 所有 iOS 目标共享 │
│ │ ├── iosArm64Main (真机) │
│ │ └── iosSimulatorArm64Main (模拟器) │
│ │ │
│ └── appleMain (中间源集) ◄── iOS + macOS + watchOS + tvOS │
│ │
└─────────────────────────────────────────────────────────────────┘

依赖方向androidMaincommonMainiosMaincommonMaincommonMain 不能依赖平台源集

典型目录结构

1
2
3
4
5
6
7
8
9
10
11
12
shared/
├── src/
│ ├── commonMain/
│ │ └── kotlin/
│ │ ├── domain/
│ │ ├── data/
│ │ └── di/
│ ├── androidMain/
│ │ └── kotlin/
│ └── iosMain/
│ └── kotlin/
└── build.gradle.kts

三、源码与实现原理

3.1 expect / actual 的编译期处理

expect/actual 并非运行时多态,而是编译期替换

  1. 在 common 编译时,expect 仅作为「占位声明」参与类型检查
  2. 在平台编译时,编译器用对应平台的 actual 替换 expect
  3. 最终产物中只存在 actual 实现,无额外抽象开销

这保证了共享代码在目标平台上等价于「直接调用平台 API」。

3.2 中间源集(Hierarchical Source Sets)

当多个平台共享同一套「非 common」逻辑时,可引入中间源集,避免重复:

1
2
3
4
5
6
7
// 声明目标
kotlin {
android()
iosArm64()
iosSimulatorArm64()
macosArm64()
}

Kotlin 插件会自动生成:

  • appleMain:所有 Apple 平台共享(iOS + macOS + watchOS + tvOS)
  • iosMain:iOS 真机 + 模拟器共享

appleMain 中可以使用 Apple 专属 API(如 platform.Foundation.NSUUID),而无需在 iosArm64MainiosSimulatorArm64Main 里各写一遍。

3.3 依赖解析规则

1
2
3
4
5
6
7
8
9
10
11
12
13
commonMain 只能依赖:
- Kotlin 标准库(多平台版本)
- kotlinx-* 多平台库(coroutines, serialization, datetime 等)
- 其他 commonMain 或「包含当前目标」的中间源集

androidMain 可额外依赖:
- Android SDK
- Jetpack 库
- 纯 JVM 库

iosMain 可额外依赖:
- Kotlin/Native 平台库
- CocoaPods 依赖

四、实战示例

4.1 项目搭建

根 build.gradle.kts:

1
2
3
4
5
plugins {
kotlin("multiplatform") version "2.0.0" apply false
kotlin("plugin.serialization") version "2.0.0" apply false
id("com.android.application") version "8.2.0" apply false
}

shared/build.gradle.kts(共享模块):

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
plugins {
kotlin("multiplatform")
kotlin("plugin.serialization")
}

kotlin {
androidTarget()
listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64()
).forEach { iosTarget ->
iosTarget.binaries.framework {
baseName = "shared"
isStatic = true
}
}

sourceSets {
commonMain.dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
implementation("io.ktor:ktor-client-core:2.3.6")
implementation("io.ktor:ktor-client-content-negotiation:2.3.6")
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.6")
}
androidMain.dependencies {
implementation("io.ktor:ktor-client-okhttp:2.3.6")
}
iosMain.dependencies {
implementation("io.ktor:ktor-client-darwin:2.3.6")
}
}
}

4.2 网络层:Ktor + kotlinx.serialization

commonMain - 数据模型与 API:

1
2
3
4
5
6
7
8
9
10
11
12
// commonMain/kotlin/data/model/User.kt
import kotlinx.serialization.Serializable

@Serializable
data class User(
val id: Long,
val name: String,
val email: String
)

@Serializable
data class ApiResponse<T>(val data: T)
1
2
3
4
5
6
7
8
9
10
11
12
13
// commonMain/kotlin/data/remote/UserApi.kt
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.request.*
import io.ktor.http.*

class UserApi(private val client: HttpClient) {
suspend fun getUser(id: Long): User {
return client.get("https://api.example.com/users/$id") {
contentType(ContentType.Application.Json)
}.body()
}
}

commonMain - Ktor 客户端工厂:

1
2
3
4
5
6
7
8
9
10
11
12
// commonMain/kotlin/di/NetworkModule.kt
import io.ktor.client.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.serialization.json.Json

expect fun createHttpClient(): HttpClient

fun createJson(): Json = Json {
ignoreUnknownKeys = true
isLenient = true
}

androidMain - OkHttp 引擎:

1
2
3
4
5
6
7
8
9
10
// androidMain/kotlin/di/NetworkModule.android.kt
import io.ktor.client.*
import io.ktor.client.engine.okhttp.*
import io.ktor.client.plugins.contentnegotiation.*

actual fun createHttpClient(): HttpClient = HttpClient(OkHttp) {
install(ContentNegotiation) {
json(createJson())
}
}

iosMain - Darwin 引擎:

1
2
3
4
5
6
7
8
9
10
// iosMain/kotlin/di/NetworkModule.ios.kt
import io.ktor.client.*
import io.ktor.client.engine.darwin.*
import io.ktor.client.plugins.contentnegotiation.*

actual fun createHttpClient(): HttpClient = HttpClient(Darwin) {
install(ContentNegotiation) {
json(createJson())
}
}

4.3 ViewModel 共享

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// commonMain/kotlin/presentation/UserViewModel.kt
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.*

class UserViewModel(
private val userApi: UserApi,
private val scope: CoroutineScope
) {
private val _user = MutableStateFlow<User?>(null)
val user: StateFlow<User?> = _user.asStateFlow()

fun loadUser(id: Long) {
scope.launch {
_user.value = userApi.getUser(id)
}
}
}
  • Android:注入 viewModelScope,通过 viewModel() 获取
  • iOS:注入 MainScope()ViewModelScope,通过 KMP 的 StateFlow 订阅

两端共享同一套 ViewModel 逻辑,仅 CoroutineScope 由各平台注入。

4.4 expect/actual 实用示例:日期格式化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// commonMain
expect fun formatDate(timestamp: Long): String

// androidMain
actual fun formatDate(timestamp: Long): String {
val sdf = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
return sdf.format(Date(timestamp))
}

// iosMain
actual fun formatDate(timestamp: Long): String {
val formatter = NSDateFormatter().apply {
dateFormat = "yyyy-MM-dd"
}
return formatter.stringFromDate(NSDate(timestamp = timestamp / 1000.0))
}

五、实际项目应用案例

5.1 Cash App(Block 旗下金融应用)

项目规模 50+ 移动工程师,约 3000 万月活
策略 业务共享、UI 原生
时间线 自 2018 年起试验 KMP,逐步通过 Feature Flag 落地
效果 移除有问题的 JavaScript 共享代码;双端维护单一业务逻辑库;服务器团队(Kotlin)可直接参与共享库开发
使用库 SQLDelight、Wire、CrashKiOS

启示:大型金融场景下,KMP 在「持久化」和「纯函数业务逻辑」上收益最大,且便于服务端参与共享代码。

5.2 Netflix

  • 在移动工作室 App 中共享逻辑,减少重复开发
  • 在影视制作的高节奏迭代下,提升交付效率与稳定性

5.3 McDonald’s

  • 共享应用内支付等复杂业务逻辑
  • 支撑每月 650 万+ 笔支付
  • 验证 KMP 在电商/支付场景的可行性

5.4 Forbes

  • 在 iOS 与 Android 间共享 80%+ 逻辑
  • 双端可同步上线新功能,缩短发布周期

5.5 其他采用者

  • Wrike、Bilibili、Feres 等使用 KMP + Compose Multiplatform
  • Vouched、Karma 等采用 KMP 共享核心业务

六、最佳实践与注意事项

6.1 共享范围建议

适合共享 不建议共享
数据模型、DTO 平台特有 UI 组件
网络请求、序列化 复杂平台动画、手势
Repository、UseCase、ViewModel 平台特定系统 API 封装
业务规则、校验逻辑 第三方 SDK 深度集成
本地存储(SQLDelight、DataStore) 推送、支付等强平台相关逻辑

6.2 常见陷阱

  1. 在 commonMain 中引用平台 API:编译器会报错,应使用 expect/actual 抽象
  2. expect/actual 签名不一致:必须保持完全一致(包名、函数名、参数、返回值)
  3. 并发与线程:Kotlin/Native 对线程模型有约束,需注意 @ThreadLocalfreeze
  4. 依赖版本:多平台库需保证各目标使用兼容版本

6.3 学习路线建议

  1. 搭建最小 KMP 工程,跑通 Android + iOS
  2. 用 expect/actual 封装 1~2 个平台 API
  3. 接入 Ktor + kotlinx.serialization 实现共享网络层
  4. 共享一个 ViewModel,在双端展示数据
  5. 按业务模块逐步迁移,避免一次性大改

七、小结

KMP 以 「共享逻辑、原生 UI」 为核心,通过 expect/actualSource Sets 实现跨平台抽象,在不牺牲原生体验的前提下显著降低双端重复开发。从 Cash App、Netflix、McDonald’s 等实践来看,KMP 已可用于生产环境,特别适合业务逻辑复杂、对一致性与性能要求较高的应用。结合 Jetpack Compose 与 SwiftUI,KMP 正成为 Android 开发者拓展 iOS 能力的重要路径。


参考资料

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 核心组件与核心设计思想

由浅入深,从四大组件、应用骨架到设计哲学与最佳实践,系统梳理 Android 核心组件体系与架构思想


一、基本概念

1.1 什么是 Android 核心组件?

Android 核心组件是系统提供的、用于构建应用的基本构建块。应用由多个组件组成,组件之间通过 IntentBinder 等机制协作,由系统负责创建、调度和生命周期管理。

1
2
3
4
5
6
7
8
9
10
11
12
13
┌─────────────────────────────────────────────────────────────────────────┐
│ Android 应用 = 组件的组合与协作 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 四大组件 │
│ Activity / Service / BroadcastReceiver / ContentProvider │
│ │ │
│ ▼ │
│ Intent(意图)──► 组件间通信、启动、数据传递 │
│ Context(上下文)──► 访问资源、启动组件、获取系统服务 │
│ Application ──► 进程级单例,应用全局状态与初始化 │
│ │
└─────────────────────────────────────────────────────────────────────────┘

1.2 为什么是「组件化」?

Android 没有「一个 main 入口跑到底」的传统应用模型,而是采用 组件化 + 系统调度

设计点 说明
可复用 任意应用可通过 Intent 启动其他应用的 Activity(如调起相机、地图),无需链接其代码
可替换 同一种组件类型可有多个实现(如多个浏览器),用户或系统可选择
生命周期由系统管 组件由系统创建与销毁,便于在内存紧张时回收、在配置变更时重建
安全与隔离 组件运行在应用进程内,通过权限与 Intent 过滤控制谁能调谁

二、四大核心组件

2.1 Activity:界面与用户交互

Activity 代表一个「界面」或「屏幕」。用户看到的每一个全屏/窗口化界面,通常对应一个 Activity(或 Fragment 承载的视图)。

要点 说明
职责 提供 UI、处理用户输入、与用户交互
生命周期 onCreate → onStart → onResume → (运行中) → onPause → onStop → onDestroy
启动方式 其他 Activity 或应用通过 startActivity(Intent) 启动
任务栈 多个 Activity 组成「返回栈」,Back 键按栈顶依次退出
1
2
3
4
5
6
7
8
9
10
11
12
┌─────────────────────────────────────────────────────────────────────────┐
│ Activity 典型生命周期(简化) │
│ │
│ 创建 ──► onCreate() ──► onStart() ──► onResume() ──► 可见且可交互 │
│ │ │
│ ▼ │
│ 切到后台 / 被遮挡 ──► onPause() ──► onStop() │
│ │ │
│ 再次回到前台 ──► onRestart() ──► onStart() ──► onResume() │
│ │
│ 销毁 ──► onDestroy() │
└─────────────────────────────────────────────────────────────────────────┘
  • 与 Fragment 的关系:一个 Activity 可包含多个 Fragment,Fragment 是「界面片段」,用于在同一个 Activity 内做模块化 UI 与复用。

2.2 Service:后台任务与长期运行

Service 用于在「无界面」的情况下执行长时间运行的任务,或为其他组件提供后台能力(如播放音乐、下载、同步)。

类型 说明 典型场景
Started Service 由 startService() 启动,可长期运行直到 stopSelf() 或 stopService() 音乐播放、下载、上传
Bound Service 由 bindService() 绑定,为其他组件提供 C/S 式接口,无客户端绑定后可由系统回收 本地 SDK、数据服务、AIDL 服务
  • 生命周期:onCreate → onStartCommand(Started)或 onBind(Bound)→ 运行 → onUnbind / onDestroy。
  • 前台服务:若需长时间在后台运行且不被系统轻易杀死,可调用 startForeground() 并显示持久通知,符合系统对「前台服务」的规范。

2.3 BroadcastReceiver:事件广播与响应

BroadcastReceiver 用于接收系统或应用发出的「广播」(如开机完成、网络变化、电量低、自定义事件),并做轻量级响应。

要点 说明
注册方式 静态注册(AndroidManifest.xml)或动态注册(代码中 registerReceiver)
执行 主线程、短时执行;耗时逻辑应交给 Service 或 WorkManager
有序广播 可指定优先级与「截断」传播(abortBroadcast)

常见系统广播:ACTION_BOOT_COMPLETEDACTION_BATTERY_LOWCONNECTIVITY_ACTION 等。

2.4 ContentProvider:数据抽象与跨进程共享

ContentProvider 对数据提供统一的「增删改查」接口(类似小型数据库 API),并支持跨应用、跨进程访问,是 Android 中「数据共享」的标准方式。

要点 说明
职责 封装数据源(SQLite、文件、内存、网络),对外提供 URI 与 Cursor/ContentValues API
跨进程 底层通过 Binder 暴露,其他应用通过 ContentResolver 访问,无需直接依赖实现方
权限 可在 AndroidManifest 中为 Provider 声明读写权限,由系统做权限校验

系统示例:通讯录、媒体库、设置等,均通过 ContentProvider 暴露给其他应用。


三、支撑性核心:Intent、Context、Application

3.1 Intent:意图与组件间通信

Intent 表示「要做的一件事」或「要传递的一包信息」,用于启动 Activity/Service、发送广播,并携带数据与标志。

类型 说明 典型用法
显式 Intent 指定 ComponentName(包名 + 类名),明确启动哪个组件 应用内页面跳转、指定 Service
隐式 Intent 只指定 action、category、data(可选),由系统根据各组件声明的 <intent-filter> 匹配 调起相机、分享、打开链接、跨应用启动
1
2
显式:我知道要启动谁 → setComponent / setClassName
隐式:我只描述「要做什么」→ setAction / addCategory / setData,系统找合适的组件
  • Intent 可携带:Bundle 数据、Extra、Flags(如 FLAG_ACTIVITY_NEW_TASK、FLAG_ACTIVITY_SINGLE_TOP)等,是组件间解耦通信的载体。

3.2 Context:上下文与资源访问

Context 是「当前组件/应用所在环境」的抽象,提供:

  • 访问应用资源(布局、字符串、drawable、主题等)
  • 启动组件(startActivity、startService、sendBroadcast)
  • 获取系统服务(getSystemService:如 LayoutInflater、ActivityManager、LocationManager)
  • 访问应用专属目录与 SharedPreferences 等
常见实现 说明
Activity Activity 本身是 Context,且带有「界面/任务」相关能力(如 startActivityForResult)
Application 进程级单例 Context,生命周期等于进程,适合做全局初始化与单例持有
Service Service 也是 Context,但无界面相关 API

注意:长时间持有 Activity 的 Context 容易导致内存泄漏(如静态变量引用 Activity),在异步回调中应避免;可改用 Application Context 或弱引用。

3.3 Application:应用级单例

Application 在进程启动时由系统创建,且每个进程只有一个实例。常用作:

  • 应用级初始化(SDK、全局配置、数据库等)
  • 全局单例或依赖注入容器的持有
  • 实现 Application 级别的生命周期回调(如 onConfigurationChanged)

在 AndroidManifest.xml 中通过 <application android:name=".MyApplication"> 指定自定义子类。


四、核心设计思想

4.1 组件化与「应用即组件集合」

Android 不强调「一个程序从 main 开始跑」,而是强调:

  • 应用由多个组件构成,每个组件有明确类型(Activity/Service/…)和声明(AndroidManifest)。
  • 入口是「可被系统或其它应用调用的组件」:例如 LAUNCHER Activity、可被隐式 Intent 匹配的 Activity/Service。
  • 组件之间通过 Intent 解耦:调用方只表达「意图」,不直接依赖实现类,便于复用与替换。
1
2
3
4
5
6
7
8
9
10
11
12
┌─────────────────────────────────────────────────────────────────────────┐
│ 组件化思想:应用 = 组件 + 声明 + 意图驱动 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ AndroidManifest.xml 声明组件与 Intent-Filter │
│ │ │
│ ▼ │
│ 系统 / 其他应用 ──► 发出 Intent ──► 系统解析 ──► 创建并启动对应组件 │
│ │
│ 同一「动作」可有多个实现(如多个浏览器),用户或系统选择其一 │
│ │
└─────────────────────────────────────────────────────────────────────────┘

4.2 生命周期由系统托管

组件的创建、暂停、恢复、销毁由 系统 根据用户行为、系统资源、配置变更(如旋转、多窗口、语言切换)统一调度:

  • 开发者实现生命周期回调(如 Activity 的 onPause/onResume),在「合适的时机」保存状态、释放资源、恢复 UI。
  • 系统可在内存紧张时回收后台组件;配置变更时销毁并重建 Activity,通过 onSaveInstanceState / ViewModel 等恢复状态。

这种设计把「何时创建/销毁」交给系统,应用只响应「已经发生」的生命周期事件,便于多任务与资源管理。

4.3 安全与权限模型

  • 进程隔离:每个应用默认运行在独立进程,内存与执行空间隔离。
  • 组件级权限:可在 AndroidManifest 中为 Activity/Service/ContentProvider 等声明 android:permission,调用方需具备相应权限才能启动或访问。
  • 运行时权限:危险权限(如相机、位置、存储)需在运行时向用户申请,用户同意后应用才可使用。
  • Intent 与导出:组件可设为 android:exported="true/false",控制是否允许其他应用通过隐式 Intent 调起。

4.4 多任务与任务栈

  • 任务(Task):通常对应用户概念里的「一个应用的一组界面」,由一组 Activity 的栈组成。
  • 启动模式:Activity 的 launchMode(standard、singleTop、singleTask、singleInstance)以及 Intent 的 Flags 共同决定「新 Activity 如何入栈、是否复用已有实例」,从而影响返回栈行为与多任务表现。

五、从设计思想到最佳实践

5.1 用 Intent 做解耦

  • 应用内跳转:可用显式 Intent,也可用隐式 Intent + 自定义 action,便于后续替换实现或做 Deep Link。
  • 跨应用能力:尽量用系统或公共的 action/data 约定(如 VIEW + http/https),或在自己的 Provider/Activity 上声明清晰的 intent-filter,便于被系统或其他应用发现和调起。

5.2 生命周期与状态保存

  • 短时状态:在 onSaveInstanceState 中保存,在 onCreate/onRestoreInstanceState 中恢复。
  • 界面相关数据:使用 ViewModel + LiveData/StateFlow,在配置变更时保留,避免在 Activity 里堆业务状态。
  • 释放资源:在 onPause/onStop 中暂停耗时操作、释放监听与引用,在 onDestroy 中做最终清理,避免泄漏。

5.3 Service 与后台行为规范

  • 短时任务:优先用 WorkManager协程 + 应用前后台状态,避免长时间占住 Service。
  • 需要长时间运行且用户可感知(如音乐播放、导航):使用 前台 Service,并按规定显示通知。
  • 避免在 BroadcastReceiver 中做重活:应启动 Service 或提交到 WorkManager。

5.4 架构分层与组件角色

  • Activity/Fragment:只做 UI 与用户输入,将业务与数据交给 ViewModel 或 Presenter。
  • ViewModel:持有界面状态与业务逻辑入口,不持有 Context/View,便于测试与复用。
  • Repository / UseCase:封装数据来源(网络、本地、ContentProvider),对上层提供统一接口。
  • ContentProvider:仅在需要「跨应用/跨进程共享数据」时使用;应用内本地数据可用 Room + DAO 等,不必强行上 Provider。

六、小结

主题 要点
四大组件 Activity(界面)、Service(后台)、BroadcastReceiver(广播)、ContentProvider(数据抽象与跨应用共享)
支撑核心 Intent(意图与组件通信)、Context(资源与系统服务)、Application(进程级单例)
设计思想 组件化、系统托管生命周期、Intent 驱动与解耦、安全与权限、任务栈与多任务
实践方向 善用 Intent 解耦、重视生命周期与状态保存、规范使用 Service 与后台、UI 与业务分层

理解「应用即组件的组合」「生命周期由系统托管」「Intent 表达意图、系统负责匹配与调度」,就能更好地把握 Android 核心组件与设计思想,并在此基础上用好 ViewModel、WorkManager、Jetpack 等现代组件与工具。