本篇将介绍Android中的协程
Kotlin - 协程
异步编程 之一 协程
协程的基本介绍
协程就非常轻量级的线程,线程是由系统调度的。线程它在进行切换或造成阻塞的时候开销都很大,而协程它是有开发者来进行控制的,也是依赖于线程的,但是协程挂起的时候是不需要阻塞线程的,几乎是无代价的。所以协程也想用户状态下的线程,非常轻量级
协程还有一点就是当它挂起
的时候,它是不会阻塞线程的。它的底层实现就是异步处理阻塞任务
挂起:挂起其实就是用 suspend来修饰一个函数。被suspend修饰的函数称为挂起函数
基本使用
第一步肯定是添加依赖
1 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1" |
GlobalScope
Kotlin中GlobalScope 类提供了几个协程构造函数
launch
接收参数
launch是CoroutineScope的扩展函数,接受3个参数,前面两个都是常规参数,最后一个是对象式函数。
1
2
3
4
5public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job这样的话就可以采用闭包的写法:() 里面写常规参数,{} 里面写函数式对象的实现
1
GlobalScope.launch(Dispatchers.Unconfined) {...}
第一个参数CoroutineContext
网上有说可以理解为协程的上下文,但按功能来说,它就是协程的线程调度器,有4种线程模式,用来指定运行线程:
Dispatchers.Main
使用这个调度器在Android主线程上运行一个协程,可以用来更新UI
Dispatchres.IO
使用这个调度器可以在主线程之外执行磁盘或网络I/O,在线程池中执行
Dispatchres.Default
使用这个调度器可以在主线程之执行CPU密集型的工作,在线程池中执行
Dispatchres.Unconfined
直接在调用者的线程执行
第二个参数CoroutineStart
这个就是启动模式,通过更改启动模式可以达到你需要它的时候,再调用启动(使用LAZY)
DEAFAULT
默认的模式,立即执行协程体
ATOMIC
立即执行协程体,但在开始运行之前无法取消
UNDISPATCHED
立即在当前线程执行协程体,直到第一个 suspend 调用
LAZY
只有在需要的情况下运行
block
这个就是闭包的方法体,用来定义协程内部需要执行的操作
返回值
Job
协程构造函数的返回值,也可以把Job看成协程对象本身,协程的操作都在Job身上了
Job.start
启动协程,但除了将协程设置为Lazy外,都不用手动启动
Job.join
等待协程执行完毕
Job.cancel
取消一个协程
Job.cancelAndJoin
等协程执行完毕之后再取消
使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19...
priavte val TAG = "test"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.e(TAG, "主线程id:${mainLooper.thread.id}")
val job = GlobalScope.launch {
delay(5000)
Log.e(TAG, "协程执行结束 -- 线程id:${Thread.currentThread().id}")
}
Log.e(TAG, "主线程执行结束")
}
...
//Job中的方法
job.isActive
job.isCancelled
job.isCompleted
job.cancel()
jon.join()运行结果:
特性
在上面的launch中,我添加了等待5秒,但主线程还是可以继续执行。也就可以看出,launch是不会阻断主线程的
async
async与launch的用法基本一致区别在于:async的返回值是Deferred。async可以支持并发,一般与await一同使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29...
private val TAG = "async"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
GlobalScope.launch {
val toDayTime = GlobalScope.async {
getThisTime()
}
val yesTerDay = GlobalScope.async {
getYesterdayTime()
}
val time = toDayTime.await() + yesTerDay.await()
Log.e(TAG, "昨天时间 和 今天时间为 --> $time")
}
}
private suspend fun getThisTime(): String {
delay(3000)
Log.e(TAG, "getThisTime: 这里已经执行完毕")
return "3-"
}
private suspend fun getYesterdayTime(): String {
delay(5000)
Log.e(TAG, "getYesterdayTime: 这里也已经执行完毕" )
return "7"
}
...运行结果:
这里也可以看出async是不阻塞线程的,也就是是getThisTime()和getYestertdayTime()是同时进行的.所以获取到
time
的时间是5s,而不是8s。launch 没有返回值,或者说返回只是 job ,能够知道任务的状态,却不能携带返回结果。async 有返回值,也就是返回的是 Deferred ,它是继承的 job ,所有job有的,它都有,还具备了job没有的携带数据回来的能力。
所以, launch 可以用来运行不需要操作结果的协程(如文件删除,创建等), async 可以用来运行异步耗时任务并且需要返回值的任务(网络请求,数据库操作,文件读写等)。withContext
withContext这个函数主要用来切换到指定的线程,并且再withContext闭包结束之后再切换回之前的线程
1
2
3
4
5
6coroutineScope.launch(Dispatchers.Main) { // 在 UI 线程开始
val image = withContext(Dispatchers.IO) { // 切换到 IO 线程,并在执行完成后切回 UI 线程
getImage(imageId) // 将会运行在 IO 线程
}
avatarIv.setImageBitmap(image) // 回到 UI 线程更新 UI
}
非GlobalScope的API
runBlocking
runBlocking与launch的功能基本一致,不同在于runBlocking是会阻塞线程的(和Thread.sleep()一样)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19...
private val TAG = "runBlocking"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
runBlocking {
val a = await()
Log.e(TAG, "onCreate: --> $a")
}
repeat(8) {
Log.e(TAG, "主线程执行$it")
}
}
private suspend fun await(): String {
delay(2000)
return "等待时间已过期"
}
...运行结果
协程体(挂起函数)
协程体是一个用suspend关键字修饰的一个函数类型,也就是挂起函数。注意:挂起函数只能在协程中或其他挂起函数中使用,而且它是顺序执行的,下面将演示一下顺序执行
1 | ... |
运行结果:
挂起与恢复
协程之外线程里肯定还有需要执行的代码,我们来看看前面的代码在挂起后何时才能恢复执行。
1 | suspend fun getToken(): String { |
运行结果:
协程挂起后,虽然延迟的时间到了,但是还得等到线程空闲时才能继续执行,这里要注意,协程可没有竞争 CPU 时间段,协程挂起后即便可以恢复执行了也不是马上就能恢复执行。简单来说,就是协程挂起之后想要恢复,必须得等到线程空闲了才可以继续,不然继续等
协程生命周期
这里可以将所有有生命周期的类继承CoroutineSocpe,这样就可以让全部协程跟着生命周期结束
如在activity里面使用
1
2
3
4
5
6
7
8
9MainActivity : AppCompatActivity(), CoroutineScope by MainScope(){
...
override fun onDestroy(){
super.onDestory()
cancel()
}
}在其他UI逻辑类中使用
1
2
3
4
5
6
7
8
9
10class MainActivityFacede : CoroutineScope {
private val job = Job()
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job
fun destroy() {
job.cancel()
}
}以上代码都会在调用 destroy 的时候取消这个作用域中的协程运行