应用架构指南
本教程共 100 篇 · 第 77 篇 · 更新于 2026-07-28 · 约 8 分钟阅读
77. 应用架构指南
本节目标:理解官方推荐的 Android 应用架构,掌握分离关注点、单一可信来源、单向数据流三大原则,知道界面层、网域层、数据层各干什么,为后面写 ViewModel、MVVM 打基础。
前面学的 Activity、Compose、网络、存储都是单点能力。真正做应用,得先把架构搭好,不然代码越写越乱。这章讲官方推荐的应用架构。
为什么需要架构
打个比方,建房子。先把承重墙、水管、电线规划好,后面装修才顺手。要是直接堆砖头,装到一半发现厕所没通水管,推倒重来代价大。
应用也一样。Activity 里塞网络请求、数据库操作、UI 刷新、业务判断,看着能跑,但有几个问题:
- 旋转屏幕数据没了。
- 业务逻辑混在 UI 里,没法测试。
- 网络层换库要改 Activity。
- 多人开发冲突严重。
架构就是给应用分层、定边界,让每一部分职责清晰。
四大原则
1. 分离关注点(Separation of Concerns)
每层、每个类只干一件事。Activity 该管 UI 就别管数据;Repository 该管数据就别管 UI。
最常见的反例是「God Activity」:一个 Activity 几千行代码,UI、网络、数据库、业务逻辑全塞里面。改一处崩三处。
2. 数据模型驱动 UI
UI 是数据的视图,数据变了 UI 跟着变。不要让 UI 自己持有状态,状态应该来自数据层。
打个比方,UI 是镜子,数据是真人。镜子只负责照出真人的样子,真人变了镜子里的影像自然变。如果让镜子自己「想象」一个影像,和真人就脱节了。
3. 单一可信来源(Single Source of Truth,SSOT)
每种数据只有一个「权威来源」,其他地方都是它的副本。修改数据只能改 SSOT,其他地方订阅它的变化。
比如用户数据,SSOT 是数据库。ViewModel 从数据库读,UI 从 ViewModel 读。网络拉到新数据,先写数据库,数据库一变 ViewModel 自动收到,UI 自动更新。
4. 单向数据流(UDF)
数据从上往下流(SSOT -> UI),事件从下往上流(UI -> SSOT)。UI 不能直接改数据,只能发事件让 SSOT 改。
[数据层] -> [ViewModel] -> [UI]
↑ |
└-------- 事件 -------------┘
好处是数据流向清晰,调试好追,状态一致。
三层架构
官方推荐至少两层:界面层 + 数据层。复杂应用加一层网域层。
界面层(UI Layer)
负责在屏幕上显示数据。包含:
- UI 元素:Compose 可组合函数,或者 View/XML。
- 状态容器:通常是 ViewModel,持有 UI 状态,处理业务逻辑。
界面层不直接访问数据库或网络,它只观察 ViewModel 暴露的状态,并把用户事件传给 ViewModel。
class UserViewModel(private val repo: UserRepository) : ViewModel() {
private val _uiState = MutableStateFlow<UserUiState>(Loading)
val uiState = _uiState.asStateFlow()
fun loadUser(id: String) {
viewModelScope.launch {
_uiState.value = Loading
try {
_uiState.value = Success(repo.getUser(id))
} catch (e: Exception) {
_uiState.value = Error(e)
}
}
}
}
数据层(Data Layer)
包含业务逻辑和数据访问。由多个 Repository 组成,每个 Repository 管一类数据。
Repository 的职责:
- 向上层暴露数据(通常是 Flow)。
- 集中数据变化。
- 解决多数据源冲突(比如网络 vs 本地数据库)。
- 包含业务逻辑。
每个数据源类(DataSource)只管一个来源:网络、数据库、文件、缓存等。
class UserRepository(
private val remote: UserRemoteDataSource,
private val local: UserLocalDataSource
) {
fun observeUser(id: String): Flow<User> = local.observeUser(id)
suspend fun refreshUser(id: String) {
val user = remote.fetchUser(id)
local.saveUser(user)
}
}
Tip离线优先应用里,数据库是 SSOT。Repository 先把网络数据写库,UI 观察数据库 Flow,自动收到更新。这种模式叫「Single Source of Truth」。
网域层(Domain Layer)
可选层,介于界面层和数据层之间。封装复杂的业务逻辑,或者多个 ViewModel 共用的简单逻辑。
类叫「用例(UseCase)」或「交互方(Interactor)」。每个用例负责一项功能。
class GetTimeZoneUseCase(private val repo: SettingsRepository) {
suspend operator fun invoke(): TimeZone {
return repo.getTimeZone()
}
}
什么时候用网域层:
- 业务逻辑复杂,多个 ViewModel 共用。
- 需要组合多个 Repository 的数据。
- 团队约定要清晰分层。
简单应用可以不要这层,ViewModel 直接调 Repository。
推荐的现代架构
现代 Android 应用架构的特点:
- 自适应分层:能适应手机、平板、折叠屏、车载。
- 每层都用 UDF:状态单向流动。
- 状态容器:ViewModel 管理复杂 UI 状态。
- 协程和 Flow:异步和数据流的标准方案。
- 依赖注入:Hilt 或者手动 DI,解耦依赖。
单 Activity 架构
早期 Android 一个屏幕一个 Activity,导致 Activity 之间数据传递复杂、返回栈难管理。现代架构推荐单 Activity + 多 Compose 目的地:
- 一个 Activity 作为容器。
- 每个屏幕是一个 Composable,用 Navigation 组件管理跳转。
- ViewModel 作用域到 Navigation 目的地,返回栈管理简单。
Note单 Activity 不是强制,但官方主推。Fragment 时代也是单 Activity + 多 Fragment 的思路,现在 Compose 直接替代了 Fragment 的角色。
依赖管理
类之间会有依赖。比如 ViewModel 依赖 Repository,Repository 依赖 DataSource。两种模式管理:
- 依赖注入(DI):类不自己 new 依赖,由外部提供。Hilt 是官方推荐的 DI 库。
- 服务定位器:有个注册表,类去那里拿依赖。
DI 更易测试(测试时传 Mock)和解耦,是主流方案。下一章的 ViewModel 章节会演示,专门的依赖注入在第 97 章讲。
状态生命周期
状态容器(ViewModel)有自己的生命周期,跟它关联的 UI 元素绑定:
- Activity 的 ViewModel:Activity finish 时清除。
- Navigation 目的地的 ViewModel:目的地从返回栈移除时清除。
- Composable 的 ViewModel:Composable 离开组合时清除。
ViewModel 在配置变化(如旋转屏幕)时不重建,这是它最大的价值。
最佳实践
官方建议:
- 不要把数据存到应用组件里。Activity、Service 会被系统随时销毁,存数据会丢。
- 减少对 Android 类的依赖。业务类别依赖
Context、Resources,便于测试。 - 模块边界清晰。网络代码别散得到处都是,缓存和绑定别混在一个类。
- 少公开内部实现。模块对外只暴露必要 API。
- 专注核心业务。样板代码用 Jetpack 库处理。
- 配置变化保留状态。用 ViewModel。
- 可测试性。架构要让单元测试好写。
常见反模式
- God Activity:Activity 干所有事。拆成 ViewModel + Repository。
- 网络请求写在 UI 里:测试难、复用难。移到 Repository。
- Repository 返回 LiveData:LiveData 应该只在 ViewModel 和 UI 之间。Repository 用 Flow。
- 多个 SSOT:网络和数据库各存一份,不同步。统一以数据库为准。
- 业务逻辑散在 UI:换 UI 框架(View -> Compose)业务逻辑要重写。逻辑放 ViewModel 或者 UseCase。
小结
应用架构四大原则:分离关注点、数据驱动 UI、单一可信来源、单向数据流。三层结构:界面层(UI + ViewModel)、可选网域层(UseCase)、数据层(Repository + DataSource)。单 Activity + Compose 是现代主推,ViewModel 在配置变化时保留状态。
下一章讲 ViewModel 怎么写,把这层的职责说透。