在 Clean Architecture 的分层规范中,UseCase是承载核心业务逻辑的灵魂。它位于架构的核心圈层,本应是最纯粹、最稳定的存在。
但在实战中,它往往沦为 ViewModel 的"瘦身工具"或 Repository 的"直传传声筒"。这种认知偏差导致项目陷入了"为了分层而分层"的泥潭:
- Repository 的转发器
- ViewModel 的工具类
- 协程调度中心
- 线程切换器
- 甚至是"万能胶水层"
结果就是:架构变复杂了,但没有变清晰。
这篇文章专门讲两个最容易踩坑的点:
- UseCase 不应该启动协程(launch)
- UseCase 不应该切线程(withContext)
- 但 UseCase 可以并发(async)
理解这三点,你的架构会立刻清爽很多。 当然在看文章之前,希望你能看看这篇 架构避坑:为什么 Repository 不该启动协程?
🎯 一句话总结 UseCase 的定位
UseCase 是业务流程的描述者,不是协程调度器,也不是线程管理器。
它应该回答的是:
- "要做什么业务?"
而不是:
- "在哪个线程执行?"
- "用哪个 scope 启动?"
- "怎么调度协程?"
🧭 一、为什么 UseCase 不该启动协程?
1. 职责错位:UseCase 不是执行器
错误示例:

看似方便,但问题巨大:
- 生命周期不可控
- 调用方无法等待结果
- 异常无法向上传递
- 无法组合多个 UseCase
- UseCase 变成"自嗨 API"
UseCase 的职责是:
描述业务流程,而不是决定何时执行。
正确方式:
由调用方决定怎么执行:


2. 错误处理会变得一团糟
UseCase 内部 launch 后:
- 异常不会冒泡
- 调用方无法捕获
- 无法统一错误处理
这会让业务流程变得不可控。
3. 可组合性被破坏
如果 UseCase 自己 launch,你无法:
- 在另一个 UseCase 里等待它
- 用 async/await 组合多个 UseCase
- 保证执行顺序
- 做事务性业务
UseCase 必须是"可等待的",也就是:
suspend- 或
Flow
🧭 二、为什么 UseCase 不该切线程?
错误示例:

看似合理,但会带来严重问题:
1. 线程策略写死在业务层
UseCase 的核心职责是业务编排 ,而不应关心执行环境。
2. UseCase 的复用性下降
如果 UseCase 内部写了 withContext(IO):
- 在已经是 IO 线程的场景下,会多切一次线程
- 在 CPU 密集场景下,调度策略不合适
- 在测试里会变慢、变不稳定
UseCase 越"纯",复用性越高。
3. 破坏了 Repository 的职责
根据 Google 的 Android 架构建议,Repository 应当负责自身的 Main-safety。
- 如果 Repository 已经处理了底层线程,UseCase 的切线程操作就是冗余代码。
- 如果 Repository 没处理,那也应该是 Repository 层的缺陷,而不该由 UseCase 来"打补丁"。
💡 最佳实践建议
核心原则: UseCase 应该是"线程中立"的。
推荐写法:

- Repository 层: 负责底层的线程切换(如数据库、网络请求),确保其挂起函数是 Main-safe 的。
- ViewModel 层: 负责启动协程,并根据需要指定初始调度器。
- UseCase 层: 仅作为纯粹的业务逻辑容器,像流水线一样处理数据。
🏁 总结
在 Clean Architecture 中,每一层都应该对下一层保持信任:
- ViewModel 信任 UseCase:只要我调用你,你就会按业务逻辑给我结果,且不会阻塞我。
- UseCase 信任 Repository :我不需要关心你是读内存还是读网络,我默认你是 Main-safe 的。
- Repository 信任底层驱动:它处理好具体的线程调度。
🧭 三、重点:UseCase 可以并发,但不能启动协程
这是你刚才问的关键点,我把它完整总结进来。
✔ UseCase 可以使用 async
✔ UseCase 可以并发
✔ UseCase 可以用 coroutineScope
❌ UseCase 不能 launch
❌ UseCase 不能决定 scope
❌ UseCase 不能决定线程
💡 精简示例:获取"我的钱包"数据 假设我们需要展示用户余额和对应的优惠券数量:

这里 UseCase 做了什么?
- 描述业务需要的并发
- 使用 async/await 组合结果
- 没有启动协程
- 没有决定线程
- 没有依赖外部 scope
这是 UseCase 并发的最佳实践。
调用方:

职责清晰:
- UseCase:描述业务流程(包括并发)
- ViewModel:决定怎么执行
下面是数据流转图

🏁 最终总结:UseCase 的"守边计划"
🟢 UseCase 应该做什么?(职责:业务编排)
- 描述业务流程: 将复杂的业务步骤转化为清晰的代码逻辑。
- 组合数据源: 作为中间人,协调多个 Repository 提供的数据。
- 执行业务判断: 处理诸如"权限检查"、"数据过滤"、"结果合并"等纯业务逻辑。
- 保持中立: 它是纯粹的 Kotlin 逻辑,不依赖 Android 环境,易于测试和复用。
🔴 UseCase 不该做什么?(边界:环境解耦)
- 禁止切线程: 不硬编码
Dispatchers,确保代码的线程透明度。 - 禁止启动协程:
ioScope,生命周期应由调用者(ViewModel)控制。 - 禁止处理 UI 状态: 它只返回业务数据或 Result,不关心进度条或弹窗。
- 禁止管理生命周期: 它应该是无状态的,随调随用,用完即走。
核心信条: 可以并行(async),但不要启动(launch); 可以组合,但不要调度; 可以编排业务,但不要管理线程。