userService.doLoginAsync(username, password) { user ->
userService.requestCurrentFriendsAsync(user) { friends ->
val finalUser = user.copy(friends = friends)
toast(“User ${finalUser.name} has ${finalUser.friends.size} friends”)
progress.visibility = View.GONE
}
}
步骤如下:
- 显示一个进度条;
- 请求服务器校验用户名和密码;
- 等待校验成功后,请求服务器获取好友列表;
- 最后,隐藏进度条;
情况还可以更复杂,想象一下,不仅要请求好友列表,还需要请求推荐好友列表,并把两次结果合并进一个列表。
有两种选择:
- 最简单的方式就是,在请求完好友列表之后,再请求推荐好友列表,但是这种方式不够高效,因为后者并不依赖前者的请求结果;
- 这种方式相对复杂一些,同时请求好友列表和推荐好友列表,并同步两次请求的结果;
通常情况下,想要偷懒的人可能会选择第一种方式:
progress.visibility = View.VISIBLE
userService.doLoginAsync(username, password) { user ->
userService.requestCurrentFriendsAsync(user) { currentFriends ->
userService.requestSuggestedFriendsAsync(user) { suggestedFriends ->
val finalUser = user.copy(friends = currentFriends + suggestedFriends)
toast(“User ${finalUser.name} has ${finalUser.friends.size} friends”)
progress.visibility = View.GONE
}
}
}
到这里,代码开始变得复杂了,出现了可怕的回调地狱:后一个请求总是嵌套在前一个请求的结果回调里面,缩进变得越来越多。
由于使用的是 Kotlin
的 lambdas
,可能看起来并没有那么糟糕。但是随着请求的增多,代码变得越来越难以管理。
别忘了,我们使用的还是一种相对简单但并不高效的一种方式。
什么是协程(Coroutine
)
简单来说,协程像是轻量级的线程,但并不完全是线程。
首先,协程可以让你顺序地写异步代码,极大地降低了异步编程带来的负担;
其次,协程更加高效。多个协程可以共用一个线程。一个 App 可以运行的线程数是有限的,但是可以运行的协程数量几乎是无限的;
协程实现的基础是可中断的方法(suspending functions
)。可中断的方法可以在任意的地方中断协程的执行,直到该可中断的方法返回结果或者执行完成。
运行在协程中的可中断的方法(通常情况下)不会阻塞当前线程,之所以是通常情况下,因为这取决于我们的使用方式。具体下面会讲到。
coroutine {
progress.visibility = View.VISIBLE
val user = suspended { userService.doLogin(username, password) }
val currentFriends = suspended { userService.requestCurrentFriends(user) }
val finalUser = user.copy(friends = currentFriends)
toast(“User ${finalUser.name} has ${finalUser.friends.size} friends”)
progress.visibility = View.GONE