Kotlin的协程(Coroutine)是轻量级的线程(Thread),它可以解决各种异步,阻塞问题。比如在Android,默认代码运行在主线程,此时如果网络请求或执行耗时操作,则会阻塞主线程,出现ANR(Application Not Responding)。而协程还有一个优势是不会消耗内存空间。
举个例子,如下代码创建了10万个协程,延时100ms后打印,可以快速地执行和创建协程。1
2
3
4
5
6
7
8fun main(args: Array<String>) = runBlocking {
repeat(100000) {
launch {
delay(100)
println("coroutine $it")
}
}
}
下面,我们开始一一介绍和了解协程。
一个场景
有一个showUserInfo
方法,需要两个网络请求,一个login
和一个fetchUserInfo
1
2
3
4
5
6
7
8
9fun showUserInfo(username: String, password: String) {
val user = login(username, password)
val info = fetchUserInfo(user.userId)
setUserInfo(info)
}
fun login(username: String, password: String) { }
fun fetchUserInfo(userId: Long) { }
使用回调方法
在login
和fetchUserInfo
添加一个入参callback
参数。代码如下1
2
3
4
5
6
7
8
9
10
11fun showUserOrders(username: String, password: String) {
login(username, password) {
user -> fetchUserInfo(user.userId) {
info -> setUserInfo(info)
}
}
}
fun login(username: String, password: String, callback: (User) -> Unit)
fun fetchUserInfo(userId: Long, callback: (UserInfo) -> Unit)
这种方法带来的问题是无法控制网络请求,比如取消请求等等。它们执行完销毁时,可能导致内存泄露问题。
使用RxJava方法
1 | fun showUserOrders(username: String, password: String) { |
使用RxJava方法解决使用回调方式的问题,虽然这种方法也是可行,没问题的,但是代码并不美观。RxJava使用链式编程,可能回出现嵌套代码情况。
使用Kotlin的协程
协程(Coroutine)可以顺序地编写代码风格,把耗时的任务调度到后台执行,没有阻塞主线程。代码直观,可读性高。1
2
3
4
5
6
7
8
9fun showUserInfo(username: String, password: String) = GlobalScope.launch(Dispatchers.Main) {
val user = withContext(Dispatchers.Default) { login(username, password) }
val info = withContext(Dispatchers.Default) { fetchUserInfo(user.userId) }
setUserInfo(info)
}
suspend fun login(username: String, password: String) { }
suspend fun fetchUserInfo(userId: Long) { }
协程的核心
- Suspend 方法
- Coroutine Context和Coroutine Scrope
- 协程的构建
Suspend 方法
Suspend方法:suspend修饰的方法,表示执行耗时直到完成也不会阻塞,且可以停止和恢复。suspend方法可以被其他suspend方法或协程调用。1
2
3suspend fun doBackgroundTask(param: Int): Int {
// 执行耗时任务
}
Coroutine Context
Coroutine Context是用于提供协程启动和运行时需要的信息。它是一个Set,里面有Map结构的元素,而Map里面有一个Element
,每个Element
有一个Key
与之对应。CoroutineContext
由4个Element
组成。
- Job:协程的唯一标识,用于控制协程的生命周期(new, active, compleing, completed, cancelling, cancelled)
- CoroutineDispatcher:指定协程运行的线程(Default, IO, Main, Unconfined)
- CoroutineName:指定协程的名称
- CoroutineExceptionHandler:指定协程异常处理器,用来处理未捕获的异常
Job
启动一个协程后,会返回一个Job对象,它是协程的唯一标识,这个Job对象状态变化过程如下。
Job只提供isActive
,isCancelled
,isCompleted
三个属性判断协程的状态。
CoroutineDispatcher
CouroutineDispatcher可以指定协程运行的线程,Kotlin内置4个线程,分别用Dispatchers.Main
,Dispatchers.IO
,Dispatchers.Default
,Dispatchers.Unconfined
方法来指定。
- Dispatchers.Main 不同平台表达不同主线程,比如Android,代表Android的主线程(UI线程)。
- Dispatchers.IO 适合执行I/O任务,它是一个共享线程池。
- Dispatchers.Default 默认的线程,通知适合执行CPU密集计算的任务,它和IO是同一个共享线程池。
- Dispatchers.Unconfined 这种情况比较特殊。如果协程存在子协程,此时父协程悬挂,恢复时父协程的线程是子协程的线程。
例子:1
2
3
4
5
6
7
8
9
10
11
12fun main() {
runBlocking<Unit> {
launch(Dispatchers.Unconfined) {
println("当前线程: ${Thread.currentThread().name}")
delay(1_000)
println("当前线程: ${Thread.currentThread().name}")
}
}
}
// 输出
当前线程: main
当前线程: kotlinx.coroutines.DefaultExecutor
Coroutine Scope
Coroutine Scope是协程的作用域,控制协程的生命周期。1
2
3
4
5
6
7
8fun main() {
val scope = CoroutineScope(CoroutineName("Greeting"))
val job = scope.launch(start = CoroutineStart.DEFAULT) {
println("Hello World");
}
Thread.sleep(1000)
}
通过自定义CoroutineScope,可以在应用程序的某一个层次开启或者控制协程的生命周期,例如Android,在ViewModel和Lifecycle类的生命周期里提供了CoroutineScope,分别是ViewModelScope和LifecycleScope。
协程的构建
启动一个协程需要3个东西CoroutineContext
,CoroutineScope
,CoroutineStart
CoroutineContext | CoroutineScope | CoroutineStart |
---|---|---|
提供协程启动和运行信息 | 协程作用域,控制协程的生命周期 | 控制协程的启动模式 |
创建Kotlin提供两个方法,就是调用CoroutineScope
的launch
和async
方式。
launch
调用后返回Job
对象,就是创建了一个协程在后台执行任务,但是执行的任务是没有返回结果的。1
2
3
4
5fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Joblaunch
默认CoroutineContext是EmptyCoroutineContext
,它使用默认的Dispatcher
。
默认CoroutineStart也是默认的启动模式是立即启动,还有其他启动模式CoroutineStart.LAZY
,CoroutineStart.ATOMIC
和CoroutineStart.UNDISPATCHED
。
1 | fun main(args: Array<String>) = runBlocking { |
async
调用后返回Deferred<T>
对象,它可以返回执行任务的结果。1
2
3
4
5fun CoroutineScope.async(
context: CoroutineContext = EmptyCorotuineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Deferred<T>
调用await
方法等待返回的结果。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23fun main(args: Array<String>) = runBlocking {
val time = measureTimeMillis {
val first = async { firstNumber() }
val second = async { secondNumber() }
val third = async { thirdNumber() }
val result = first.await() + second.await() + third.await()
}
println(time) // 7秒后输出结果
}
suspend fun firstNumber(): Int {
delay(3_000) // 延时3s
return 5
}
suspend fun secondNumber(): Int {
delay(5_000) // 延时5s
return 8
}
suspend fun thirdNumber(): Int {
delay(7_000) // 延时7s
return 10
}
需要等到所有悬挂的方法执行完,协程才完成结束。
后续
协程的介绍暂时到这里,后续会继续补充。