做 Android 或后端开发时,常遇到这种场景:需要同时拉取用户信息、订单列表和未读消息数,等三者都返回后,把它们组装成一个首页数据对象。以前用 Callback 或 RxJava 处理起来绕来绕去,现在 Kotlin 协程配合 zip,真像拧瓶盖一样顺手。
zip 不是压缩,是“并行配对”
别被名字骗了——Kotlin 协程里的 zip 和 ZIP 文件压缩毫无关系。它本质是把两个(或多个)异步任务的结果,按顺序“拉链式”配对,等全部完成才吐出组合后的结果。就像你让两位同事分别查天气和查路况,你只关心“今天出门带不带伞 + 堵不堵车”这一组答案,而不是谁先回谁后回。
基础用法:两个 Deferred 合并
假设你有两个 suspend 函数:
suspend fun fetchUserInfo(): User { /* 网络请求 */ }
suspend fun fetchOrderCount(): Int { /* 网络请求 */ }
直接 zip 一下:
val (user, count) = async { fetchUserInfo() }
.zip(async { fetchOrderCount() }) { u, c -> Pair(u, c) }
.await()
注意:zip 返回的是一个新的 Deferred<Pair<User, Int>>,所以最后要 await() 才能解包。更简洁写法是直接用结构化声明:
val (user, count) = async { fetchUserInfo() }
.zip(async { fetchOrderCount() }) { u, c -> u to c }
.await()
三个及以上?用 coroutineScope + async 组合
协程原生 zip 只支持两个参数,但实际项目里经常要凑三四个接口。这时候别硬套,改用 coroutineScope 包一层,再用 async 并发发起,最后手动解构:
val (user, orders, unread) = coroutineScope {
val userAsync = async { fetchUserInfo() }
val ordersAsync = async { fetchOrderList() }
val unreadAsync = async { fetchUnreadCount() }
Triple(userAsync.await(), ordersAsync.await(), unreadAsync.await())
}
这段代码看着多几行,但逻辑清晰、可读性强,而且每个 async 是真正并发执行的,不会串行等待。
实战小技巧:加个超时,别卡死在某个慢接口上
真实网络环境里,总有一两个接口拖后腿。与其等它,不如设个兜底时间:
val (user, count) = withTimeoutOrNull(5_000) {
async { fetchUserInfo() }
.zip(async { fetchOrderCount() }) { u, c -> u to c }
.await()
} ?: run {
// 超时了,返回默认值或空数据
User.empty() to 0
}
这样首页至少能展示部分信息,用户体验稳多了。
别忘了取消:页面关了,请求也得停
如果用户快速滑走、Activity 销毁了,还在后台跑的 async 就成了内存泄漏隐患。务必把协程作用域绑定到生命周期,比如 Android 中用 lifecycleScope:
lifecycleScope.launch {
val (user, count) = async { fetchUserInfo() }
.zip(async { fetchOrderCount() }) { u, c -> u to c }
.await()
updateUI(user, count)
}
这样 Activity 销毁时,整个协程树自动取消,不用手动管理。