Main-safe:现代 Android 架构真正的分水岭

文章内容

在 Android 架构设计里,"main-safe" 经常被提到,但大多数人只把它理解成一句简单的话:"Repository 要切线程,UI 层不用关心 IO。" 如果只停留在这个理解,其实还远远没抓住它的本质。

main-safe 并不仅仅是线程规范,它的本质是一种架构契约——Data 层对调用方的承诺,也正因如此,它成了衡量架构是否真正分层的一道分水岭。

一、什么是 main-safe?

main-safe 的意思是:调用方可以始终在 Main 线程调用它,而不需要关心内部是否涉及 IO、网络、数据库、CPU 计算。

kotlin
viewModelScope.launch {    val user = getUserUseCase()}

调用方不需要写 withContext(Dispatchers.IO),也不需要知道 API 是否阻塞、DB 是否耗时、是否需要切线程。

二、main-safe 的本质:复杂度是否扩散

main-safe 真正解决的不仅仅是线程问题,而是一个更深层的问题:复杂度是否扩散到调用方。

2.1 非 main-safe 架构(复杂度暴露)

kotlin
viewModelScope.launch(Dispatchers.IO) {    val user = api.getUser()}

或者:

kotlin
viewModelScope.launch {    withContext(Dispatchers.IO) {        api.getUser()    }}

表面问题是线程,但本质问题是:UI 层必须知道哪些是 IO、哪些是 CPU、什么时候切线程。UI 变成了"调度中心",线程逻辑到处出现,同一件事在三层重复解决。

2.2 main-safe 架构(复杂度收敛)

UI 层只表达意图:

kotlin
viewModelScope.launch {    val user = getUserUseCase()}

Repository 内部处理 IO:

kotlin
class UserRepository(    private val api: Api) {    suspend fun getUser(): User =        withContext(Dispatchers.IO) {            api.getUser()        }}

如果上面的 api 是 Retrofit,其实不用 withContext(Dispatchers.IO)。

对比维度 非 main-safe main-safe
UI 是否关心线程 是 否
IO 逻辑位置 到处都是 Data 层统一
调用复杂度 高 低
心智负担 重 轻

三、main-safe 是什么的契约?——四个"不泄漏"

理解 main-safe,不只是"Repository 用 withContext(IO)"这么简单。它本质上是 Data 层对 UI 层承诺的一套调用契约,具体体现在四个维度的"不泄漏"。

3.1 不泄漏线程模型

UI 不需要知道 IO 线程池的存在、Default 调度器的用途、是否并发执行。调用方不应该看到 Dispatchers.IO、Dispatchers.Default 这些词出现在 ViewModel 或 UseCase 里——这是 Data 层的内部实现。

3.2 不泄漏阻塞风险

UI 不需要担心 Room 查询是不是 blocking、文件 IO 会不会卡主线程、网络请求是否 suspend-safe。调用方只需要 suspend fun,至于它背后是 Retrofit 的协程适配器、Room 的 suspend 查询,还是手动 withContext,都不该暴露出来。

3.3 不泄漏并发语义

UI 不需要知道是否多线程并发访问同一数据、是否有 mutex/lock 保护、是否存在 race condition。这些并发细节属于 Data 层的内部状态管理。一旦 UI 层需要理解并发语义才能正确调用,就说明边界已经被打破了。

3.4 不泄漏调用成本

UI 不需要猜这个函数是不是"很重"、会不会引发 ANR、要不要包一层 withContext(Dispatchers.IO)。调用方看到一个 suspend fun,就应该可以放心地在 viewModelScope.launch {} 里调用,而不需要事先去翻 Repository 的实现。

main-safe 不是在描述"Repository 怎么写",而是在描述"UI 层不需要知道什么"。这四个"不泄漏",是 Data 层对上层的承诺,也是架构边界能否真正落地的检验标准。

main-safe 四个不泄漏架构契约

四、为什么 main-safe 是架构分水岭?

因为它改变了一个关键问题:谁在控制复杂度?

非 main-safe:UI 在控制复杂度。UI 必须知道 IO、网络、DB、并发——UI 变成了执行层,而不是表达层。

main-safe:Data 层控制复杂度。UI 只做一件事:发出请求 + 展示状态。复杂度被收敛到 Data 层:IO、线程、调度、数据源切换,全在那里解决。

UI 不再拥有"执行权",只拥有"Intent(我要什么)"和"State(我展示什么)"。Data 层拥有"执行权",负责如何拿数据、从哪里拿、是否缓存、是否 IO、是否并发。

五、main-safe 之后更深的一层问题

很多项目做到 main-safe 后,仍然会变乱。原因是:main-safe 只解决"线程外溢",没有解决"业务外溢"。

错误结构——Repository 做业务决策:

kotlin
class UserRepository {    suspend fun getUser(): User {        val user = api.getUser()        if (user.level > 10) {            cache.save(user)        }        return user    }}

Repository 开始做业务决策,数据层变成"上帝对象",业务逻辑无法复用。

正确结构——职责收敛。Repository 只负责数据:

kotlin
class UserRepository {    suspend fun getUser(): User = api.getUser()}

UseCase 负责业务决策:

kotlin
class GetUserUseCase(    private val repo: UserRepository,    private val cache: Cache) {    suspend operator fun invoke(): User {        val user = repo.getUser()        if (user.level > 10) {            cache.save(user)        }        return user    }}

六、main-safe 的三个层级理解

层级 含义 核心问题
Level 1:线程安全 UI 不负责 IO 谁切线程?
Level 2:职责安全 Data 不负责业务判断 谁做决策?
Level 3:结构安全 每一层只做一件事,不越界 边界在哪里?

七、总结

main-safe 的本质不仅仅是线程切换,而是复杂度是否收敛在 Data 层。它通过四个"不泄漏"——不泄漏线程模型、不泄漏阻塞风险、不泄漏并发语义、不泄漏调用成本——构建起 UI 层与 Data 层之间真正的架构边界。

如果 Data 层无法做到 main-safe,复杂度就仍然分散在调用链中,所谓"分层架构"也只是形式上的结构,而非真正的边界设计。


转载:https://mp.weixin.qq.com/s/zh_bdnzBbs_vbrKrsL8iwg


本文由 AgentPlanFlow 生成

觉得不错?给作者点个赞吧

分类:设计模式

评论 (0)

无需注册,留言经站长审核后公开。

加载评论中…

Main-safe:现代 Android 架构真正的分水岭 · Yinchen 的博客