Android --- Kotlin学习之路:协程的使用,什么是协程,为什么要用协程?(学习笔记)

Kotlin 协程(coroutine)学习

以下干货满满,掌握以下内容一定会对你在项目开发中有所帮助,记得收藏!!!

文章目录

什么是协程,为什么要用协程?

协程是基于线程,它是轻量级的线程

java用的是线程,kotlin用的是协程,协程是个并发管理工具,定位跟线程其实是一样的。

其实就是把java的线程包了一层。之前我们做网络请求的时候,想把网络请求的结果响应在主线程中,应该怎么写?

一般情况下我们会在主线程去开启一个子线程发起网络请求,然后将请求的结果在主线程显示

比如Retrofit,会帮我们实现自动在子线程去发起网络请求,不用我们自己new Thread,然后

会把结果通过onResponse的形式传递给我们,这个时候Retrofit也帮我们切到了主线程,而协程并不需要写回调了。

另外,比如我们想要先开启两个并行协程,然后需要最终合并到主线程,协程是实现是最简单的。

kotlin 复制代码
interface ApiService {
    @GET(value = "posts/1")
    fun queryUser(@Query(value = "userId", encoded = true) userId: String) : Call<UserResponse>

    @GET(value = "user")
    suspend fun getUser(@Query(value = "userid", encoded = true) username:String) :UserResponse
}

老的写法:

kotlin 复制代码
// 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}")

	}
})

协程的写法:

kotlin 复制代码
CoroutineScope(Dispatchers.Main).launch { // 主线程开启一个协程
	val create = Retrofit.create(ApiService::class.java)
	val user = create.getUser("zhangqi") // 这里调用的是一个挂起函数,挂起函数会在后台执行,原理是协程的Dispatchers.Main自己有线程池
	// 这一行可以做页面刷新,这行操作自动在主线程中执行,原理是,挂起函数会调用handler.post切换到主线程的
}

挂起函数挂起的是什么?

挂起的是协程,挂起之后,协程就会脱离当前线程,等待挂起函数执行完成之后再切回来

挂起函数中的suspend 关键字的作用是什么?

提醒函数调用者,我是一个耗时函数,需要放在后台运行,请在协程中调用我

suspend 只是提醒

如果你想在协程中执行串行的代码,而不是并行的,怎么处理?

需要使用withContext

kotlin 复制代码
CoroutineScope(Dispatchers.Main).launch {
	launch {
		println("--1-- ${Thread.currentThread().name}")
	}
	println("--2-- ${Thread.currentThread().name}")
}
上面的代码输出结果:
--2-- main
--1-- main

那如果我们想要按照顺序输出,就需要这样写

kotlin 复制代码
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里面的内容执行完才能执行

怎么样写一个自定义挂起函数?

本质上没有自定义挂起函数,就是使用suspend关键字,

然后用挂起函数withContext(){}将你的耗时内容包裹起来

并行协程的启动和交互

kotlin 复制代码
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()) // 将上面两个协程的结果合并
}

什么是耗时操作?

文件读写,网络请求,等待操作(delay(10))等等,都是耗时操作

挂起和阻塞的区别,什么叫挂起,什么叫阻塞?

挂起就相当于delay(12000)

阻塞就相当于Thread.sleep(12000)

挂起之后你可以干其他事,但是阻塞之后你就要等,不能干其他事

什么叫挂起:当前协程不再占用它正在工作的这个线程

协程所使用的调度器

Dispatcher.Main 主线程,底层还是用原生handler.post()方法切到主线程的

Dispatcher.IO 非主线程 ,底层有自己的Executor管理

Dispacther.Default 非主线程,底层有自己的Executor管理

Dispacther.Unconfiend 基本不会用到

DEFAULT和IO的区别:

DEFAULT 的线程数量是CPU的核心数相等,面向计算密集型任务

IO 是固定的64个线程的线程池,面向I/O密集型型任务(读写文件,网络访问),CPU基本不工作,I/O的磁盘在做的

协程能够通过挂起函数实现切换线程之后切回来

定义协程必须指定其 CoroutineScope,常用的相关API有:

GlobalScope,生命周期是process级别的,顶级的,即使Activity或Fragment已经被销毁了,协程仍然在执行

MainScope, 在Activity中使用,可以在onDestory()中取消协程

viewModelScope,只能在ViewModel中使用,绑定ViewModel的生命周期

lifecycleScope,只能在Activity,Fragment中使用,会绑定Activity和Fragment的生命周期 ,会自动在onDestory 调用自己的cancel()方法取消

协程的启动

通过launch和async构建器启动协程

我们可以用async先启动一个协程,然后在别的协程中通过.await()获取它的结果

kotlin 复制代码
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则常用在需要返回结果的并发任务中

val scope = CoroutineScope(Dispacther.Default)

scope.launch {}

协程的启动模式(指定协程启动之后的一些行为)

DEFAULT:协程创建后,立即开始调度,在调度前如果协程被取消,其将直接进入取消响应的状态

ATOMIC:协程创建后,立即开始调度,协程执行到第一个挂起点之前不响应取消

LAZY:只有协程被需要时,包括主动调用协程的start、join或者await等函数时才开始调度,如果调度前就被取消,那么协程将直接进入异常结束状态

UNDESPATCHED:协程创建后立即在当前函数调用栈中执行,直到遇到第一个挂起的点

kotlin 复制代码
val job = async(context = Dispacther.IO, start = CoroutineStart.UNDESPATCHED){
	println("thread"&{Thread.currentThread().name})
} 
//这段代码输出的是主线程

协程的作用域构建器

coroutineScope 和 runBlocking

runBlocking 是常规函数,会阻塞线程,coroutineScope是挂起函数。

coroutineScope 一个协程失败了,所有其他其他兄弟协程也会被取消

supervisorScope 一个协程失败了,不会影响其他协程的执行

coroutineScope 和 runBlocking会等待所有的字协程执行完毕,这个作用域才会结束

Job对象

负责管理协程的生命周期

新创建(New)活跃(Active)完成中(Completing)已完成(Completed)取消中(Cancelling)已取消(Cancelled)

CPU密集型任务的取消

要通过isActive ensureActive() yield 取消

协程取消的副作用

如果在取消协程之后做了一些释放资源的操作,那么就坏菜了。我们可以通过try{}finally{在这里面释放资源,这里的东西一定执行}

还可以用use函数,use函数内部会自己close()资源

超时任务怎么处理

kotlin 复制代码
val result = withTimeoutOrNull(1300) {
	repeat(1000) { i ->
		println("job: I'm sleeping &i")
		delay(500L)
	}
	"Done"
}?: "Jack"
相关推荐
Kapaseker1 小时前
2026年,我们还该不该学编程?
android·kotlin
雨白17 小时前
Android 快捷方式实战指南:静态、动态与固定快捷方式详解
android
hqk17 小时前
鸿蒙项目实战:手把手带你实现 WanAndroid 布局与交互
android·前端·harmonyos
LING18 小时前
RN容器启动优化实践
android·react native
恋猫de小郭20 小时前
Flutter 发布官方 Skills ,Flutter 在 AI 领域再添一助力
android·前端·flutter
Kapaseker1 天前
一杯美式搞懂 Any、Unit、Nothing
android·kotlin
黄林晴1 天前
你的 Android App 还没接 AI?Gemini API 接入全攻略
android
恋猫de小郭1 天前
2026 Flutter VS React Native ,同时在 AI 时代 VS Native 开发,你没见过的版本
android·前端·flutter
冬奇Lab2 天前
PowerManagerService(上):电源状态与WakeLock管理
android·源码阅读
BoomHe2 天前
Now in Android 架构模式全面分析
android·android jetpack