文章目录
- 1.kotlin协程简介
- 2.kotlin协程的特点
- 3.启动协程的方式
- 3.1 runBlocking
- 3.2 launch
- 3.3 async/await
1.kotlin协程简介
Kotlin协程的主要作用是像写同步代码一样写异步代码。
避免回调地狱。
2.kotlin协程的特点
可控制:协程能做到可被控制的发起子任务
轻量级:协程非常小,占用资源比线程还小
语法糖:使多任务或多线程切换不再使用语法糖
3.启动协程的方式
- 启动协程需要添加依赖
// Gradle
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.4'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.0.0'
1:runBlocking:T // 用于执行协程任务,通常只用于启动最外层协程
2:launch:Job // 用于执行协程任务
3:async/await:Deferred // 用于执行协程任务,并执行得到结果
launch、async 需要实现 CoroutineScope 接口通过CoroutineScope 来开启协程
// CoroutineScope 让MainActivity带有作用域
class MainActivity : AppCompatActivity(), CoroutineScope {
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main
}
override fun onDestroy() {
// 这样在 Activity 退出的时候,对应的作用域就会被取消,所有在该 Activity 中发起的请求都会被取消掉。
cancel()
super.onDestroy()
}
3.1 runBlocking
- runBlocking 的作用于最外层,也就是作用于协程的范围。可以建立一个阻塞当前线程的协程. 所以它主要被用来在main函数中或者测试中使用, 作为连接函数.
- 假设一个三方登录的场景
第一步:请求token
第二步:请求用户权限
第三步:三方登陆
三次请求都按照前后顺序成功以后才算登录成功。
假设三个线程对应三个请求
class RequestToken : Runnable {
override fun run() {
Log.e(TAG, "RequestToken")
}
}
class RequestUserPermission : Runnable {
override fun run() {
Thread.sleep(200)
Log.e(TAG, "RequestUserPermission")
}
}
class SignWithWX : Runnable {
override fun run() {
Log.e(TAG, "SignWithWX")
}
}
采用回调的方式是这样的。
RequestToken(){
// 请求token回调
successCallBack(
// 请求权限
RequestUserPermission(){
// 请求permission回调
successCallBack(){
// 三方登录
SignWithWX(){
// 三方登录回调
// success
}
}
}
)
}
上面的代码只是简单的写了成功的回调还没有处理失败的回调,这种回调嵌套的代码看着感觉很乱
下面采用 runBlocking 的方式是这样的。runBlocking 默认是在当前线程开启协程的。runBlocking会阻塞当前线程,等到runBlocking全部执行完成后才会继续执行
private fun login() = runBlocking {
RequestToken().run()
RequestUserPermission().run()
SignWithWX().run()
Log.e(TAG, "--end--")
}
上面runBlocking的代码执行顺序Log如下:即使RequestUserPermission 在请求的时候 sleep了200依然是按照顺序执行的
MainActivity: RequestToken
MainActivity: RequestUserPermission
MainActivity: SignWithWX
MainActivity: --end--
非协程的线程中可以用Thread.sleep() 来使线程睡眠,协程中可以用 delay(100) 方法。非协程模块中是禁止调用 delay()方法的。
3.2 launch
CoroutineScope的扩展函数,返回Job。launch可以开启协程,也可以在其他协程中开启协程。**launch不会阻塞当前线程。**这个和runBlocking是有区别的
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
val newContext = newCoroutineContext(context)
val coroutine = if (start.isLazy)
LazyStandaloneCoroutine(newContext, block) else
StandaloneCoroutine(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
}
启动launch的方式如下
private fun loadMain() = launch(Dispatchers.Main) {
val name = Thread.currentThread().name
Log.e(TAG, "Dispatchers.Main-->$name")
}
private fun loadIO() = launch(Dispatchers.IO) {
val name = Thread.currentThread().name
Log.e(TAG, "Dispatchers.IO-->$name")
}
private fun loadDefault() = launch(Dispatchers.Default) {
val name = Thread.currentThread().name
Log.e(TAG, "Dispatchers.Default-->$name")
}
private fun loadUnconfined() = launch(Dispatchers.Unconfined) {
val name = Thread.currentThread().name
Log.e(TAG, "Dispatchers.Unconfined-->$name")
}
launch有几个参数可以选择,代表了协程在哪个线程中执行,以及协程中断以后是在哪个线程中恢复。
- Dispatchers.Main // 主线程
- Dispatchers.IO // 采用on-demand创建的线程池, 用于网络或者是读写文件的工作.
- Dispatchers.Default // 代表使用JVM上的共享线程池, 其大小由CPU核数决定, 不过即便是单核也有两个线程. 通常用来做CPU密集型工作
- Dispatchers.Unconfined // 不指定特定线程默认当前线程
打印的日志如下:
Dispatchers.IO-->DefaultDispatcher-worker-1
Dispatchers.Unconfined-->main
Dispatchers.Default-->DefaultDispatcher-worker-2
Dispatchers.Main-->main
- launch 返回的是一个 job 提供了一些方法来控制和获取任务的属性
start()
cancel()
cancel()
join()
3.3 async/await
async 和 launch 一样都是用来创建一个 Coroutine 的。通过 async 函数返回的 Defeered 对象可以获取 Coroutine 的返回值。
public fun <T> CoroutineScope.async(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): Deferred<T> {
val newContext = newCoroutineContext(context)
val coroutine = if (start.isLazy)
LazyDeferredCoroutine(newContext, block) else
DeferredCoroutine<T>(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
}
// start = CoroutineStart.LAZY 需要通过 start开启
val d1 = async(start = CoroutineStart.LAZY) { RequestUserPermission().run() }
val d2 = async(start = CoroutineStart.LAZY) { SignWithWX().run() }
d1.start()
}
- async{} 会在对应的 CoroutineContext 下创建一个新的协程,并且放回一个Deferred,通过 Deferred 可以异步获取结果,也就是调用Deffered 的 await() 方法。
下面代码Log打印出的值为 “testReturn”
非 start = CoroutineStart.LAZY 不用调用 start() 方法开启
private fun loadMain() = launch {
val d1 = async<String>{ testReturn() }
val result = d1.await()
Log.e(TAG, "await=$result")
}
private fun testReturn(): String {
return "testReturn"
}
await()方法是 suspend 修饰的方法,Kotlin协程中只有这一个关键字。suspend修饰的方法只能被suspend修饰的方法调用。
public suspend fun await(): T
在 launch 里面会创建一个新的 CoroutineContext,如果没有传入 Context 则使用的EmptyCoroutineContext,通过 newCoroutineContext() 函数会分配一个默认的 Dispatcher,也就是Dispatcher.default,默认的全局 Dispatcher,会在jvm 层级共享线程池,会创建等于cpu 内核数目的线(但是至少创建两个子线程)。接着判断 CoroutineStart 是否 Lazy 模式,如果 Lazy 模式,则该Coroutine 不会立马执行,需要你主动掉了 Job.start() 之后才会执行。
- suspend 修饰的函数或者 lambda 只能被 suspend 修饰的函数 lambda 调用