面试高频问题:runBlocking 和 launch 有什么区别?什么时候该用 runBlocking?
答错这道题的人,其实挺多的。
一、先搞清楚:runBlocking 是什么
一句话:
runBlocking 会阻塞当前线程,直到内部所有协程执行完毕。

关键词:阻塞。
它把当前线程"卡住",等协程跑完才放行。
二、和 launch 的本质区别

| 对比 | launch |
runBlocking |
|---|---|---|
| 是否阻塞当前线程 | ❌ 不阻塞 | ✅ 阻塞 |
| 返回值 | Job(无结果) |
协程的返回值 |
| 使用场景 | 异步任务 | 桥接同步/异步 |
| 在主线程调用 | ✅ 安全 | ❌ 会卡 UI |
三、在 Android 中,runBlocking 的正确用法
✅ 场景一:OkHttp 拦截器
这是实际项目中最常见的 runBlocking 场景之一:

为什么必须用 runBlocking?
OkHttp 的 Interceptor.intercept() 签名是同步的,你改不了。但 token 刷新逻辑(查本地缓存 → 过期则请求新 token → 存储)通常是 suspend 函数。
更完整的例子------Token 过期自动重试:

✅ 场景二:ContentProvider
ContentProvider 的所有 CRUD 方法都是同步签名,但数据可能来自 Room 的 suspend 函数:

为什么 ContentProvider 必须用 runBlocking?
| 问题 | 原因 |
|---|---|
| 签名是同步的 | query() 返回 Cursor?,不是 suspend 函数 |
| 不能改签名 | 这是 Android 框架定义的接口 |
| 数据来自 Room | Room 推荐用 suspend 函数访问数据库 |
| 运行在 Binder 线程 | 非主线程,runBlocking 不会卡 UI |
✅ 场景三:WorkManager 的 Worker

更好的方式: 用
CoroutineWorker替代Worker:

四、❌ 这些场景绝对不能用
🚫 Activity / Fragment 主线程

🚫 ViewModel 里

🚫 协程内部嵌套

🚫 Dispatchers.Main 上 runBlocking

五、runBlocking vs coroutineScope
面试常考辨析题:
`

| 对比 | runBlocking |
coroutineScope |
|---|---|---|
| 类型 | 普通函数 | suspend 函数 |
| 线程行为 | 阻塞线程 | 挂起协程 |
| 使用场景 | 同步→异步的桥梁 | 协程内部划分作用域 |
| Android 中 | 仅用于同步桥接 | 业务代码中使用 |
runBlocking是同步世界的入口,coroutineScope是协程世界的容器。
六、Android 场景速查表
✅ 该用的场景
| 场景 | 示例 |
|---|---|
| OkHttp 拦截器 | override fun intercept() = runBlocking { } |
| ContentProvider | override fun query() = runBlocking(IO) { } |
| Worker 同步桥接 | override fun doWork() = runBlocking { } |
🚫 不该用的场景
| 场景 | 后果 | 正确方案 |
|---|---|---|
| Activity/Fragment | 卡 UI / ANR | lifecycleScope.launch |
| ViewModel | 阻塞主线程 | viewModelScope.launch |
| 协程内部嵌套 | 浪费线程 / 死锁 | 直接调 suspend 函数 |
| Dispatchers.Main | 死锁 | withContext(Dispatchers.IO) |
| Compose | 卡 recomposition | LaunchedEffect |
七、面试回答模板
runBlocking 是同步代码和协程之间的桥梁,会阻塞当前线程直到内部协程全部完成。
在 Android 中,主要用在同步回调桥接的场景:OkHttp 拦截器、ContentProvider、Worker 等------这些 API 签名是同步的,但内部需要调用 suspend 函数。
绝对不能在主线程使用,会导致 UI 卡顿甚至 ANR。也不应该在协程内部嵌套,可能造成死锁。
在 Activity 用
lifecycleScope.launch,在 ViewModel 用viewModelScope.launch,在 Compose 用LaunchedEffect------这些都自带生命周期管理,比 runBlocking 安全得多。