Kotlin之协程的使用
- 协程是基于线程的,可以理解为轻量级的线程。协程的调度是协作式的,程序员需要手动控制协程的切换。
- Java用的是线程,Kotlin用的是协程,协程是个并发管理工具,定位跟线程其实是一样的。
协程在网络请求中的应用
我们做网络请求的时候,想把网络请求的结果响应在主线程中,应该怎么写?
一般情况下我们会在主线程去开启一个子线程发起网络请求,然后将请求的结果在主线程显示。比如Retrofit会帮我们实现自动在子线程去发起网络请求,不用我们自己new Thread,然后会把结果通过onResponse回调的形式传递给我们,这个时候Retrofit也帮我们切到了主线程。而协程就不需要写回调了。
Retrofit+线程切换
- 定义接口
interface ApiService {
@GET("posts/{userId}")
fun queryUser(@Query(value = "userId", encoded = true) userId: String) : Call<UserResponse>
}
- Retrofit 请求
// Retrofit 请求
val create = Retrofit.create(ApiService::class.java)
create.queryUser("1").enqueue(object : Callback<UserResponse> {
override fun onResponse(call: Call<UserResponse>, response: Response<UserResponse>) {
if (response.isSuccessful) {
val body = response.body()
body?.let {
Log.i("Retrofit", "get success : $it")
}
}
}
override fun onFailure(call: Call<UserResponse>, t: Throwable) {
Log.e("Retrofit", "get onFailure : ${t.message}")
}
})
其中,enqueue
方法是Retrofit中 Call 接口的一部分,用于异步地执行HTTP请求。当调用 enqueue 方法时,Retrofit会立即返回,并且HTTP请求会在后台线程中执行。一旦请求完成(无论成功还是失败),enqueue 方法提供的 Callback 接口的 onResponse 或 onFailure 方法将被调用,以分别处理响应或错误。
- onResponse:当HTTP请求成功完成时调用,允许你访问从服务器返回的响应体。
- onFailure:当HTTP请求失败时调用,比如网络问题、服务器错误或请求超时等。
Retrofit+协程
- 定义接口
interface ApiService {
@GET(value = "user")
suspend fun getUser(@Query(value = "userid", encoded = true) username:String) :UserResponse
}
- Retrofit 请求
// 主线程开启一个协程
CoroutineScope(Dispatchers.Main).launch {
val create = Retrofit.create(ApiService::class.java)
// 这里调用的是一个挂起函数,挂起函数会在后台执行,原理是协程的Dispatchers.Main自己有线程池
val user = create.getUser("phc")
// 后续逻辑可以做页面刷新,这行操作自动在主线程中执行。因为挂起的函数会调用handler.post切换到主线程的
}
协程的启动
基本启动方式
可以通过launch和async构建器启动协程。我们可以用async先启动一个协程,然后在别的协程中通过.await()获取它的结果
lifecycleScope.launch {
// deferred1 和 deferred2 并行
val deferred1 = async {println("Test1 ${Thread.currentThread().name}") }
val deferred2 = async {println("Test2 ${Thread.currentThread().name}") }
showContributors(deferred1.await() + deferred2.await()) // 将上面两个协程的结果合并
}
注:可以通过join和await等待作业的结果返回
launch和async的区别:
- launch 返回的是一个Job对象,async返回的是一个Defferd对象,Defferd本质也是一个Job
- launch常用于不需要返回结果的并发任务,async则常用在需要返回结果的并发任务中
协程的启动模式
启动模式即指定协程启动之后的一些行为
DEFAULT:协程创建后,立即开始调度,在调度前如果协程被取消,其将直接进入取消响应的状态
ATOMIC:协程创建后,立即开始调度,协程执行到第一个挂起点之前不响应取消
LAZY:只有协程被需要时,包括主动调用协程的start、join或者await等函数时才开始调度,如果调度前就被取消,那么协程将直接进入异常结束状态
UNDESPATCHED:协程创建后立即在当前函数调用栈中执行,直到遇到第一个挂起的点
val job = async(context = Dispacther.IO, start = CoroutineStart.UNDESPATCHED){
println("thread"&{Thread.currentThread().name})
}
//这段代码输出的是主线程
答疑解惑
-
挂起函数挂起的是什么?
挂起的是协程,挂起之后,协程就会脱离当前线程,等待挂起函数执行完成之后再切回来 -
挂起函数中的suspend 关键字的作用是什么?
提醒函数调用者,我是一个耗时函数,需要放在后台运行,请在协程中调用我。suspend 只是提醒 -
如果想在协程中执行串行的代码,而不是并行的,怎么处理?
需要使用withContextCoroutineScope(Dispatchers.Main).launch { launch { println("--1-- ${Thread.currentThread().name}") } println("--2-- ${Thread.currentThread().name}") } 上面的代码输出结果: --2-- main --1-- main
如果想要按照顺序输出,就需要这样写:
CoroutineScope(Dispatchers.Main).launch { withContext(Dispatchers.IO) { println("--1-- ${Thread.currentThread().name}") } println("--2-- ${Thread.currentThread().name}") } 输出结果: --1-- DefaultDispatcher-worker-1 --2-- main
因为withContext将线程切到了IO线程(阻塞),所以后面的操作要等待withContext里面的内容执行完才能执行
-
协程所使用的调度器
Dispatcher.Main 主线程,底层还是用原生handler.post()方法切到主线程的
Dispatcher.IO 非主线程 ,底层有自己的Executor管理
Dispacther.Default 非主线程,底层有自己的Executor管理 -
不同CoroutineScope的生命周期
定义协程必须指定其 CoroutineScope,常用的相关API有:- GlobalScope:生命周期是process级别的,顶级的,即使Activity或Fragment已经被销毁了,协程仍然在执行
- MainScope:在Activity中使用,可以在onDestory()中取消协程
- viewModelScope:只能在ViewModel中使用,绑定ViewModel的生命周期
- lifecycleScope:只能在Activity,Fragment中使用,会绑定Activity和Fragment的生命周期 ,会自动在onDestory 调用自己的cancel()方法取消