Kotlin 协程它不香吗?,kotlin 开发游戏
asyncFunc1(opt, (...args1) {
asyncFunc2(opt, (...args2) {
asyncFunc3(opt, (...args3) {
asyncFunc4(opt, (...args4) {
// some operation
});
});
});
});
看起来是不是有点恶心,如果你没有感觉到恶心,笔者觉得你可能经常写这样的代码所以习惯了,有一句“名人”名言:吐着吐着就习惯了。
到这里优秀的你肯定又想到了 Rxjava 这把利器,我们可以通过它提供的「Observable」的编程范式进行链式调用,可以很好地消除回调。
那么这里介绍的协程到底可以做什么呢?上面的问题它自然是可以解决了,那它相较于 RxJava 的优势是什么呢?
笔者觉得最主要的是它可以用看起来同步的方式写出异步的代码。这
样写代码的人写起来很舒服,读代码的人读起来很畅快。
快速上手
下面笔者利用 Retrofit 配合协程实现一个登录功能
首先需要添加以下依赖库
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0"
implementation 'com.squareup.retrofit2:retrofit:2.6.2'
implementation 'com.squareup.retrofit2:converter-gson:2.6.0'
//为 Retrofit 添加对 Deferred 的支持
implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2'
根据 wanandroid 的登录 API 接口,通过 retrofit 框架敲出客户端的登录接口
接口 API 链接: www.wanandroid.com/blog/show/2
interface ApiService {
companion object {
const val BASE_URL = "https://www.wanandroid.com"
}
@FormUrlEncoded
@POST("/user/login")
fun login(@Field("username") username: String,
@Field("password") password: String): Deferred<WanResponse<User>>
}
Deferred 是什么?它是 Job 的子接口。那,,,Job 又是什么呢?可以简单理解,整个登录请求的过程就是会被封装成 Job,然后交给协程调度器处理。但 Job 在完成的时候是没有返回值的,所以就有了 Deferred,它的意思就是延迟,结果稍后才能拿到,它可以为任务完成时提供返回值。
根据请求后返回的 json,写出返回值的数据类
data class WanResponse<out T>(val errorCode: Int,val errorMsg: String,val data: T)
data class User(val collectIds: List<Int>,val email: String,
val icon: String,val id: Int,
val password: String, val type: Int, val username: String)
之后构建一个 retrofit 实例,用它来进行请求登录
class ApiRepository {
val retrofit = Retrofit.Builder()
.baseUrl(ApiService.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
//添加对 Deffered 的支持
.addCallAdapterFactory(CoroutineCallAdapterFactory.invoke())
.build()
.create(ApiService::class.java)
fun login(name: String,password: String): Deferred<WanResponse<User>>{
return retrofit.login(name,password)
}
}
接下来主角协程要出场了。我们可以通过 launch 函数开启一个协程
GlobalScope.launch(Dispatchers.IO) {
var result: WanResponse<User>?=null
result = repository.login(userName,userPassword).await()
launch(Dispatchers.Main) {
btnLogin.text = result.data.username
}
}
这段代码出现了 Dispatchers 调度器,它可以将协程限制在一个特定的线程执行,或者将它分派到一个线程池,或者让它不受限制地运行,关于 Dispatchers 这里先不展开了。
常用的 Dispatchers ,有以下三种:
Dispatchers.Main:Android 中的主线程
Dispatchers.IO:针对磁盘和网络 IO 进行了优化,适合 IO 密集型的任务,比如:读写文件,操作数据库以及网络请求
Dispatchers.Default:适合 CPU 密集型的任务,比如计算
但上面的栗子只是一次网络请求,如果有多次请求可能就变成这个样子:
GlobalScope.launch(Dispachers.IO) {
//io 操作
launch(Dispachers.Main){
//ui 操作
launch(Dispachers.IO) {
//io 操作
launch(Dispacher.Main) {
//ui 操作
}
}
}
}
这个嵌套???不是说协程可以不用写嵌套代码的吗
于是协程中有一个很实用的函数:withContext。这个函数可以切换到指定的线程,并在闭包内的逻辑执行结束之后,自动把线程切回去继续执行,
用 withContext 改写一下,它的结构大致就长这个亚子:
launch(Dispachers.Main) {
...
withContext(Dispachers.IO) {
...
}
...
withContext(Dispachers.IO) {
...
}
...
}
复制代码
比如上面的登录的栗子就可以改写成这样:
GlobalScope.launch(Dispatchers.Main) {
var result: WanResponse<User>?=null
withContext(Dispatchers.IO){
//请求登录
result = repository.login(userName,userPassword).await()
}
//更新 ui
btnLogin.text = result?.data?.username
}
好像的确变得简洁了许多,但离我们的目标:看起来同步的方式写出异步的代码还差那么一点。
既然不需要嵌套了,那就可以把 io 线程的操作,拿出来单独作为函数,就可以写成这样:
评论