开发过程中,有时候需要从网络上下载数据,并刷新界面。
fun init(){
val userInfo = fetchUserInfo() // 网络请求
refreshUI(userInfo) // 刷新UI
}
fetchUserInfo是比较耗时的操作,会一直阻塞当前线程直到数据返回。
在android项目中,为了避免阻塞UI线程造成anr,都是新开线程去执行耗时的操作,获取到执行结果后返回UI线程继续执行余下的操作。
修改后的代码:
发起网络请求时注册callBack方法,当完成网络请求后,在UI线程回掉callBack方法。
fun init(){
fetchUserInfo(callBack = { userInfo ->
refreshUI(userInfo) // 刷新UI
}) // 网络请求
}
这样可以避免anr的产生。但是一些复杂的情况下容易产生“callBack hell”,比如网络请求返回后,先存入本地数据库(耗时操作),在进行UI刷新操作。
fun init(){
fetchUserInfo(callBack = { userInfo ->
saveUserInfo(userInfo){ userInfo ->
refreshUI(userInfo) // 刷新UI
}
}) // 网络请求
}
callback层层嵌套,代码将会横向扩展,十分臃肿。
为了解决代码横向扩展带来的问题,可以借助RxJava,使横向扩展转变为纵向扩展。
fun init() {
saveUserInfo(userInfo)
.flatMap{ userInfo ->
saveUserInfo(userInfo)
}.subscribe{ userInfo ->
refreshUI(userInfo)
}
}
但是Rxjava学习成本高昂,其次代码变得不直观明朗,一眼望去全是flatMap等操作符。而且RxJava是单参数传递,如果我想同时传递两个参数,必须新建一个包装类。
以上两种方式都不是完美的解决方法。我们的终极目标是以写同步代码的方式书写异步代码。
就像这样
fun init(){
val userInfo = fetchUserInfo() // 网络请求
refreshUI(userInfo) // 刷新UI
}
当然上边只是理想情况,我们需要让编译器知道哪些代码是异步的,是需要后台执行的,需要和同步代码区分开来。
kotlin的最终实现方案:
fun init(){
launch {
val userInfo = async {
fetchUserInfo() // 网络请求
}
refreshUI(userInfo.await()) // 刷新UI
}
}
launch创造了一个协程执行的上下文,async代码块包裹的是需要异步执行的任务,当执行userInfo.await()时,代码会暂停执行(但是不阻塞UI线程),直到网络请求返回后再执行refreshUI的操作。
下一篇会详细介绍kotlin coroutines实现机制。
网友评论