…
// observer(观察者)个数有0到1时执行
// 即第一次调用observe或observeForever时执行
override fun onActive() {
super.onActive()
// 启动协程并执行协程体
blockRunner?.maybeRun()
}
// observer(观察者)个数有1到0时执行
// 即调用removeObserver时触发检查并执行回调
override fun onInactive() {
super.onInactive()
// 取消协程
blockRunner?.cancel()
}
}
可见CoroutineLiveData
是在onActive()
启动协程,在onInactive()
取消协程
所以使用LiveData
对协程的支持, 那么CoroutineActivity
Demo的代码写法如下
class CoroutineActivity : AppCompatActivity() {
private lateinit var testApi: TestApi
…
fun requestData1() {
liveData {
try {
val lastedNews = testApi.getLatestNews2()
emit(lastedNews.stories!![0].title!!)
} catch(e: Exception) {
emit(“error”)
}
}.observe(this, Observer {
tv_text.text = it
})
}
}
上面我们讲了协程在android里最常用的用法,下面将介绍协程的一些基本知识
协程上下文
协程上下文用CoroutineContext
表示,kotlin
中 比较常用的Job
、协程调度器(CoroutineDispatcher)
、协程拦截器(ContinuationInterceptor)
等都是CoroutineContext
的子类,即它们都是协程上下文
先看一下CoroutineContext
比较重要的plus
方法,它是一个用operator
修复的重载(+)
号的操作符方法
@SinceKotlin(“1.3”)
public interface CoroutineContext {
/**
- Returns a context containing elements from this context and elements from other [context].
- The elements from this context with the same key as in the other one are dropped.
*/
public operator fun plus(context: CoroutineContext): CoroutineContext =
if (context === EmptyCoroutineContext) this else // fast path – avoid lambda creation
context.fold(this) { acc, element ->
val removed = acc.minusKey(element.key)
if (removed === EmptyCoroutineContext) element else {
// make sure interceptor is always last in the context (and thus is fast to get when present)
val interceptor = removed[ContinuationInterceptor]
if (interceptor == null) CombinedContext(removed, element) else {
val left = removed.minusKey(ContinuationInterceptor)
if (left === EmptyCoroutineContext) CombinedContext(element, interceptor) else
CombinedContext(CombinedContext(left, element), interceptor)
}
}
}
}
比如上面说的MainScope
定义就使用了+号操作符
public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)
如果你看启动协程的源码就会发现,在kotlin
中 大量使用 + 号操作符,所以kotlin中大部分CoroutineContext
对象都是CombinedContext
对象
上面的example
使用的launch
方法启动协程有三个参数, 分别是协程上下文
、协程启动模式
、协程体
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
}
协程启动模式
- DEFAULT
立即执行协程体
runBlocking {
val job = GlobalScope.launch(start = CoroutineStart.DEFAULT) {
println("1: " + Thread.currentThread().name)
}
// 不需要调用join方法
// job.join()
}
打印结果
1: DefaultDispatcher-worker-1
CoroutineStart.DEFAULT
启动模式不需要手动调用join
或start
等方法,而是在调用launch
方法的时候就会自动执行协程体的代码
- LAZY
只有在需要的情况下才执行协程体
runBlocking {
val job = GlobalScope.launch(start = CoroutineStart.LAZY) {
println("1: " + Thread.currentThread().name)
}
// 一定调用join方法
job.join()
}
打印结果
1: DefaultDispatcher-worker-1
CoroutineStart.LAZY
启动模式一定要手动调用join
或start
等方法,否者协程体不会执行
- ATOMIC
立即执行协程体,但在开始运行之前无法取消, 即开启协程会无视cancelling
状态
runBlocking {
val job = GlobalScope.launch(start = CoroutineStart.ATOMIC) {
println("1: " + Thread.currentThread().name)
delay(1000)
println("2: " + Thread.currentThread().name)
}
job.cancel()
delay(2000)
}
打印结果
1: DefaultDispatcher-worker-1
CoroutineStart. ATOMIC
启动模式的协程体 即使调了cancel
方法 也一定会执行,因为开启协程会无视cancelling
状态;上面的example只打印了一句话,是因为执行delay(1000)
的时候 发现协程处于关闭状态, 所以出现了JobCancellationException
异常,导致下面的代码没有执行,如果 delay(1000)
这句代码用 try catch
捕获一下异常,就会继续执行下面的代码
- UNDISPATCHED
立即在当前线程执行协程体,直到第一个 suspend 调用 挂起之后的执行线程取决于上下文当中的调度器了
runBlocking {
println("0: " + Thread.currentThread().name)
// 注意这里没有用GlobalScope.launch
// 因为GlobalScope.launch启动的是一个顶层协程, 无法关联当前协程的上下文(coroutineContext), 导致结果有偏差
launch(context = Dispatchers.Default, start = CoroutineStart.UNDISPATCHED) {
println("1: " + Thread.currentThread().name)
delay(1000)
println("2: " + Thread.currentThread().name)
}
delay(2000)
}
打印结果
0: main
1: main
2: DefaultDispatche