超长文,带你全面了解Kotlin的协程
Posted 郭霖
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了超长文,带你全面了解Kotlin的协程相关的知识,希望对你有一定的参考价值。
https://me.csdn.net/NJP_NJP
1. 什么是异步
我记得小学二年级碰到过一个让我受益终身的数学题: 烧开水需要15分钟,洗碗需要5分钟,扫地需要5分钟,请问做完这三件事,总共需要几分钟?从此我做什么事,都事先想想先后顺序,看看可不可以一并去做。
长大后才知道这就是异步的用法,它其实已经渗透到你的生活中。
2. 为什么需要回调
3. 回调的缺点
//烧2000mL热水来洗碗
boilWater(2000) { water ->
washDishes(water)
}
//客户端顺序进行三次网络异步请求,并用最终结果更新UI
request1(parameter) { value1 ->
request2(value1) { value2 ->
request3(value2) { value3 ->
updateUI(value3)
}
}
}
request1(parameter)
.map { value1 ->
request2(value1)
}.map { value2 ->
request3(value2)
}.subscribe { value3 ->
updateUI(value3)
}
//1.简单秩序的串行世界:
print("Hello ")
print("World!")
//结果为:Hello World!
//2.复杂混沌的并行世界:
Thread {
Thread.sleep(2000)
print("Hello ")
}.start()
print("World!")
val value1 = request1(parameter)
val value2 = request2(value1)
val value3 = request2(value2)
updateUI(value3)
request1(parameter) { value1 ->
request2(value1) { value2 ->
request3(value2) { value3 ->
updateUI(value3)
}
}
}
1. 添加依赖
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1'
2. 启动协程
GlobalScope.launch {
delay(1000L)
println("Hello,World!")
}
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit): Job {...}
CoroutineScope翻译过来就是“协程范围”,指的是协程内的代码运行的时间周期范围,如果超出了指定的协程范围,协程会被取消执行,上面第一段代码中的GlobalScope指的是与应用进程相同的协程范围,也就是在进程没有结束之前协程内的代码都可以运行。
-
context:协程上下文,可以指定协程运行的线程。默认与指定的CoroutineScope中的coroutineContext保持一致,比如GlobalScope默认运行在一个后台工作线程内。也可以通过显示指定参数来更改协程运行的线程,Dispatchers提供了几个值可以指定:Dispatchers.Default、Dispatchers.Main、Dispatchers.IO、Dispatchers.Unconfined。
-
start:协程的启动模式。默认的(也是最常用的)CoroutineStart.DEFAULT是指协程立即执行,除此之外还有CoroutineStart.LAZY、CoroutineStart.ATOMIC、CoroutineStart.UNDISPATCHED。
-
block:协程主体。也就是要在协程内部运行的代码,可以通过lamda表达式的方式方便的编写协程内运行的代码。
-
CoroutineExceptionHandler:除此之外还可以指定CoroutineExceptionHandler来处理协程内部的异常。
3. 调用挂起函数
println("Start")
GlobalScope.launch(Dispatchers.Main) {
delay(1000L)
println("Hello World")
}
println("End")
println("Start")
Thread {
Thread.sleep(1000L)
println("Hello World")
}.start()
println("End")
//协程代码
println("Start ${Thread.currentThread().name}")
GlobalScope.launch(Dispatchers.Main) {
delay(1000L)
println("Hello World ${Thread.currentThread().name}")
}
println("End ${Thread.currentThread().name}")
//线程代码
println("Start ${Thread.currentThread().name}")
Thread {
Thread.sleep(1000L)
println("Hello World ${Thread.currentThread().name}")
}.start()
println("End ${Thread.currentThread().name}")
public suspend fun delay(timeMillis: Long) {...}
GlobalScope.launch(Dispatchers.Main) {
println("Hello ${Thread.currentThread().name}")
test()
println("End ${Thread.currentThread().name}")
}
suspend fun test(){
println("World ${Thread.currentThread().name}")
}
GlobalScope.launch(Dispatchers.Main) {
println("Hello ${Thread.currentThread().name}")
test()
println("End ${Thread.currentThread().name}")
}
suspend fun test(){
withContext(Dispatchers.IO){
println("World ${Thread.currentThread().name}")
}
}
//客户端顺序进行三次网络异步请求,并用最终结果更新UI
request1(parameter) { value1 ->
request2(value1) { value2 ->
request3(value2) { value3 ->
updateUI(value3)
}
}
}
//用协程改造回调代码
GlobalScope.launch(Dispatchers.Main) {
//三次请求顺序执行
val value1 = request1(parameter)
val value2 = request2(value1)
val value3 = request2(value2)
//用最终结果更新UI
updateUI(value3)
}
//requestAPI适配了Kotlin协程
suspend fun request1(parameter : Parameter){...}
suspend fun request2(parameter : Parameter){...}
suspend fun request3(parameter : Parameter){...}
//并发请求
GlobalScope.launch(Dispatchers.Main) {
//三次请求并发进行
val value1 = async { request1(parameter1) }
val value2 = async { request2(parameter2) }
val value3 = async { request3(parameter3) }
//所有结果全部返回后更新UI
updateUI(value1.await(), value2.await(), value3.await())
}
//requestAPI适配了Kotlin协程
suspend fun request1(parameter : Parameter){...}
suspend fun request2(parameter : Parameter){...}
suspend fun request3(parameter : Parameter){...}
//复杂业务逻辑的Kotlin协程实现
GlobalScope.launch(Dispatchers.Main) {
//首先拿到request1的请求结果
val value1 = request1(parameter1)
//将request1的请求结果用于request2和request3两个请求的并发进行
val value2 = async { request2(value1) }
val value3 = async { request2(value1) }
//用request2和request3两个请求结果更新UI
updateUI(value2.await(), value3.await())
}
//requestAPI适配了Kotlin协程
suspend fun request1(parameter : Parameter){...}
suspend fun request2(parameter : Parameter){...}
suspend fun request3(parameter : Parameter){...}
1. 添加依赖
//添加Retrofit网络库和gsonConverter的依赖,注意一定要2.6.0版本以上
implementation 'com.squareup.retrofit2:retrofit:2.7.0'
implementation 'com.squareup.retrofit2:converter-gson:2.7.0'
//添加Jetpack中架构组件的依赖,注意viewmodel要添加viewmodel-ktx的依赖
implementation "androidx.lifecycle:lifecycle-livedata:2.1.0"
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0'
implementation "androidx.lifecycle:lifecycle-extensions:2.1.0"
//添加Glide的依赖用于图片加载
implementation 'com.github.bumptech.glide:glide:4.10.0'
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:layout_marginTop="10dp"
tools:context=".MainActivity">
<Button
android:id="@+id/button"
android:text="refresh"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<ImageView
android:id="@+id/imageView1"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:scaleType="centerCrop"
android:layout_marginTop="10dp"/>
<ImageView
android:id="@+id/imageView2"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:scaleType="centerCrop"
android:layout_marginTop="10dp"/>
<ImageView
android:id="@+id/imageView3"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:scaleType="centerCrop"
android:layout_marginTop="10dp"/>
</LinearLayout>
3. 编写网络层接口
{
"code": "200",
"imgurl": "https://img02.sogoucdn.com/app/a/100520113/20140811192414"
}
data class ImageDataResponseBody(
val code: String,
val imgurl: String
)
import com.njp.coroutinesdemo.bean.ImageDataResponseBody
import retrofit2.http.GET
import retrofit2.http.Query
//网络接口
interface ApiService {
//声明为suspend方法
@GET("image/sogou/api.php")
suspend fun getImage(@Query("type") type: String = "json"): ImageDataResponseBody
}
import com.njp.coroutinesdemo.bean.ImageDataResponseBody
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.create
import java.util.concurrent.TimeUnit
//网络层访问统一入口
object NetworkService {
//retorfit实例,在这里做一些统一网络配置,如添加转换器、设置超时时间等
private val retrofit = Retrofit.Builder()
.client(OkHttpClient.Builder().callTimeout(5, TimeUnit.SECONDS).build())
.baseUrl("https://api.ooopn.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
//网络层访问服务
val apiService = retrofit.create<ApiService>()
}
sealed class LoadState(val msg: String) {
class Loading(msg: String = "") : LoadState(msg)
class Success(msg: String = "") : LoadState(msg)
class Fail(msg: String) : LoadState(msg)
}
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.xxx.coroutinesdemo.bean.LoadState
import com.xxx.coroutinesdemo.network.NetworkService
class MainViewModel : ViewModel() {
//存放三张图片的url数据
val imageData = MutableLiveData<List<String>>()
//存放网路加载状态信息
val loadState = MutableLiveData<LoadState>()
//从网络加载数据
fun getData() {...}
}
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import com.bumptech.glide.Glide
import com.xxx.coroutinesdemo.R
import com.xxx.coroutinesdemo.bean.LoadState
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//获取ViewModel
viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
//对加载状态进行动态观察
viewModel.loadState.observe(this, Observer {
when (it) {
is LoadState.Success -> button.isEnabled = true
is LoadState.Fail -> {
button.isEnabled = true
Toast.makeText(this, it.msg, Toast.LENGTH_SHORT).show()
}
is LoadState.Loading -> {
button.isEnabled = false
}
}
})
//对图片Url数据进行观察
viewModel.imageData.observe(this, Observer {
//用Glide加载三张图片
Glide.with(this)
.load(it[0])
.into(imageView1)
Glide.with(this)
.load(it[1])
.into(imageView2)
Glide.with(this)
.load(it[2])
.into(imageView3)
})
//点击刷新按钮来网络加载
button.setOnClickListener {
viewModel.getData()
}
}
}
5. 实现getData方法
fun getData() {
viewModelScope.launch(CoroutineExceptionHandler { _, e ->
//加载失败的状态
loadState.value = LoadState.Fail(e.message ?: "加载失败")
}) {
//更新加载状态
loadState.value = LoadState.Loading()
//并发请求三张图片的数据
val data1 = async { NetworkService.apiService.getImage() }
val data2 = async { NetworkService.apiService.getImage() }
val data3 = async { NetworkService.apiService.getImage() }
//通过为LiveData设置新的值来触发更新UI
imageData.value = listOf(data1.await(), data2.await(), data3.await()).map {
it.imgurl
}
//更新加载状态
loadState.value = LoadState.Success()
}
}
/**
* ...
* This scope is bound to [Dispatchers.Main]
*/
val ViewModel.viewModelScope: CoroutineScope
get() {...}
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
fun ViewModel.launch(
block: suspend CoroutineScope.() -> Unit,
onError: (e: Throwable) -> Unit = {},
onComplete: () -> Unit = {}
) {
viewModelScope.launch(CoroutineExceptionHandler { _, e -> onError(e) }) {
try {
block.invoke(this)
} finally {
onComplete()
}
}
}
-
block:协程主体; -
onError:错误回调; -
onComplete:完成回调。
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.xxx.coroutinesdemo.bean.LoadState
import com.xxx.coroutinesdemo.launch
import com.xxx.coroutinesdemo.network.NetworkService
import kotlinx.coroutines.async
class MainViewModel : ViewModel() {
val imageData = MutableLiveData<List<String>>()
val loadState = MutableLiveData<LoadState>()
fun getData() {
launch(
{
loadState.value = LoadState.Loading()
val data1 = async { NetworkService.apiService.getImage() }
val data2 = async { NetworkService.apiService.getImage() }
val data3 = async { NetworkService.apiService.getImage() }
imageData.value = listOf(data1.await(), data2.await(), data3.await()).map {
it.imgurl
}
loadState.value = LoadState.Success()
},
{
loadState.value = LoadState.Fail(it.message ?: "加载失败")
}
)
}
}
{
"code": 200,
"data": {...},
"msg": "OK"
}
data class ResponseBody<T>(
val code: Int,
val msg: String,
val data: T
)
object Repository {
//数据脱壳与错误预处理
fun <T> preprocessData(responseBody: ResponseBody<T>): T {
return if (responseBody.code == 200) responseBody.data else throw Throwable(responseBody.msg)
}
suspend fun getImageData(paramter: Paramter1): ImageData {
//调用ApiService定义的接口方法
val responseBody = ApiService.getImage(paramter)
//返回处理后的数据
return preprocessData<ImageData>(responseBody)
}
suspend fun getOtherData(paramter: Paramter2): OtherData {...}
...
}
https://github.com/NaJiPeng/Coroutines-Demo
以上是关于超长文,带你全面了解Kotlin的协程的主要内容,如果未能解决你的问题,请参考以下文章
字节内部超全Kotlin学习笔记,快速上手 Kotlin 开发,基础 + 实战 + 源码,手把手带你吃透 Kotlin 语法与协程。
Kotlin 协程协程取消 ③ ( finally 释放协程资源 | 使用 use 函数执行 Closeable 对象释放资源操作 | 构造无法取消的协程任务 | 构造超时取消的协程任务 )
Kotlin 协程协程取消 ③ ( finally 释放协程资源 | 使用 use 函数执行 Closeable 对象释放资源操作 | 构造无法取消的协程任务 | 构造超时取消的协程任务 )
Kotlin 协程Flow 异步流 ⑤ ( 流的上下文 | 上下文保存 | 查看流发射和收集的协程 | 不能在不同协程中执行流的发射和收集操作 | 修改流发射的协程上下文 | flowOn函数 )