在开发Android应用时,经常会遇到需要同时请求多个接口并把结果合并处理的场景。比如一个用户主页,既要加载用户基本信息,又要获取最新的动态列表,只有等两个请求都完成才能刷新界面。传统写法容易嵌套回调,代码难看又难维护。
Kotlin协程配合Retrofit,能用很简洁的方式解决这个问题。其中,async 和 awaitAll 是常用手段,但如果你需要把多个独立请求的结果组合成一个新的数据结构,zip 操作就显得特别实用。
什么是 zip?
这里的 zip 并不是文件压缩,而是来自函数式编程的概念:把多个异步任务的结果按顺序打包在一起。就像拉链一样,把两边的齿一一对应合上。
假设我们有两个 suspend 函数:
suspend fun fetchUserInfo(): UserInfo {
delay(1000)
return UserInfo(name = "张三", age = 28)
}
suspend fun fetchUserPosts(): List<Post> {
delay(1500)
return listOf(Post(title = "今天天气不错"), Post(title = "周末去爬山了"))
}
我们想等这两个函数都执行完,然后把用户信息和帖子一起展示。这时候就可以在协程作用域里使用 async 启动两个并发任务,再通过类似 zip 的方式组合结果。
手动实现 zip 效果
Kotlin 标准库没有直接叫 zip 的协程函数,但我们可以通过 async 实现:
val result = coroutineScope {
val userInfoDeferred = async { fetchUserInfo() }
val postsDeferred = async { fetchUserPosts() }
// 等待两个结果,并合并
UserDetail(
info = userInfoDeferred.await(),
posts = postsDeferred.await()
)
}
这段代码会并发执行两个请求,总耗时取决于最慢的那个(约1.5秒),而不是累加。相比串行请求节省了时间。
封装成通用 zip 辅助函数
如果这类合并操作很多,可以封装一个简单的 zip 函数:
suspend fun <T1, T2> zip(
call1: suspend () -> T1,
call2: suspend () -> T2
): Pair<T1, T2> = coroutineScope {
val deferred1 = async(call1)
val deferred2 = async(call2)
deferred1.await() to deferred2.await()
}
调用时就很清晰:
val (userInfo, posts) = zip(::fetchUserInfo, ::fetchUserPosts)
val userDetail = UserDetail(info = userInfo, posts = posts)
这种写法逻辑清楚,也方便后续扩展成支持三个或更多参数的版本。
实际网络请求示例
结合 Retrofit 的 suspend 函数,真实项目中可能是这样的:
interface ApiService {
@GET("/user/profile")
suspend fun getUserProfile(): UserProfile
@GET("/user/feed")
suspend fun getUserFeed(): FeedResponse
}
// 使用
val api = retrofit.create(ApiService::class.java)
val (profile, feed) = zip(
{ api.getUserProfile() },
{ api.getUserFeed() }
)
页面初始化时发起这两个请求,数据齐了就更新UI,整个过程流畅且无嵌套。
注意:虽然 zip 能合并结果,但如果其中一个请求失败,整个表达式会抛出异常。可以在 await 前加 try-catch 做更精细的错误控制,或者根据业务决定是否要继续等待另一个结果。