Kotlin协程使用
Android中使用协程依赖添加,Google Developer
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1'
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.5.1"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1"
PS:AndroidStudio中查看依赖树方式,执行./gradlew :app:dependencie
./gradlew :app:dependencie
\--- org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1
+--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1
| \--- org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.6.1
| +--- org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.6.1
| | +--- org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1 (c)
| | +--- org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1 (c)
| | \--- org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.6.1 (c)
| +--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.0 -> 1.7.20 (*)
| \--- org.jetbrains.kotlin:kotlin-stdlib-common:1.6.0 -> 1.7.20
+--- org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.6.1 (*)
\--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.0 -> 1.7.20 (*)
在新建项目时只需要添加依赖appcompat,基本上就包含Jetpack 相关的部分组件(LiveData\ViewModel)
implementation 'androidx.appcompat:appcompat:1.5.1'
协程启动方式
协程最常用的启动方式launch、async方式,另外还有通过produce、actor创建channel来实现协程启动
class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
companion object{
private const val TAG = "MainActivity"
}
override fun onCreate(savedInstanceState: Bundle?) {
WindowCompat.setDecorFitsSystemWindows(window, false)
super.onCreate(savedInstanceState)
val deferred = async {
delay(1000)
1
}
launch {
Log.d(TAG, "${deferred.await()}")
}
var count = 0
val receiverChannel = produce(Dispatchers.Unconfined) {
while (count<=10){
Log.d(TAG, "produce send $count")
send(count++)
delay(1000)
}
}
launch{
val iterator = receiverChannel.iterator()
while (iterator.hasNext()){
Log.d(TAG, "produce receive ${iterator.next()}")
}
}
val sendChannel = actor<Int> {
while (iterator().hasNext()){
Log.d(TAG, "actor receive ${receive()}")
}
}
launch {
while (count<=10){
Log.d(TAG, "actor send $count")
sendChannel.send(count++)
delay(1000)
}
}
}
}
协程启动参数中包含3个参数,协程上下文、启动模式、协程体,协程通过协程作用域进行启动(CoroutineScope),在目前CPU执行速度上比较,DEFAULT\ATOMIC 结果基本一致,LAZY就需要主动调用后生效,UNDISPATCHED模式会在当前线程立即执行,直到遇到第一个挂起函数后执行就取决于挂起点本身的逻辑以及上下文当中的调度器
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
}
public enum class CoroutineStart {
DEFAULT,//立即执行协程体,如果协程执行之前被取消,就不会执行
LAZY,//只有在需要的情况下运行,需调用join\start启动协程
ATOMIC,//立即执行协程体,但在开始运行之前无法取消,运行在launch设置的协程中
UNDISPATCHED,//立即在当前线程执行协程体,直到第一个 suspend 调用,
}
launch(Dispatchers.IO, start = CoroutineStart.DEFAULT) {
log("DEFAULT")
}
val job = launch(Dispatchers.IO, start = CoroutineStart.LAZY) {
log("LAZY")
}
launch(Dispatchers.IO, start = CoroutineStart.ATOMIC) {
log("ATOMIC")
}
launch(Dispatchers.IO, start = CoroutineStart.UNDISPATCHED) {
log("UNDISPATCHED")
}
binding.start.setOnClickListener {
log("点击执行")
job.start()
}
System.out: test1 DEFAULT DefaultDispatcher-worker-1 23:54:20:061
System.out: test1 ATOMIC DefaultDispatcher-worker-1 23:54:20:061
System.out: test1 UNDISPATCHED main 23:54:20:061
System.out: test1 点击执行 main 23:54:20:061
System.out: test1 LAZY DefaultDispatcher-worker-3 23:54:20:061
System.out: test1 点击执行 main 23:54:20:061
System.out: test1 点击执行 main 23:54:20:061
协程上下文CoroutineContext
简单理解CorontineContext为一个链表结构,通过EmptyCoroutineContext、CombinedContext进行组合,将Element作为元素进行封装,比较常用的几种Element
- Job
- CoroutineName
- CoroutineDispatcher
- ContinuationInterceptor
- CoroutineExceptionHandler
public interface CoroutineContext {
public operator fun <E : Element> get(key: Key<E>): E?
public fun <R> fold(initial: R, operation: (R, Element) -> R): R
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)
}
}
}
public fun minusKey(key: Key<*>): CoroutineContext
public interface Key<E : Element>
public interface Element : CoroutineContext {
public val key: Key<*>
public override operator fun <E : Element> get(key: Key<E>): E? =
@Suppress("UNCHECKED_CAST")
if (this.key == key) this as E else null
public override fun <R> fold(initial: R, operation: (R, Element) -> R): R =
operation(initial, this)
public override fun minusKey(key: Key<*>): CoroutineContext =
if (this.key == key) EmptyCoroutineContext else this
}
}
协程调度器CoroutineDispatcher
- Default 线程池
- Main UI线程,Android主线程
- IO 线程池,基于 Default 调度器背后的线程池,并实现了独立的队列和限制,因此协程调度器从 Default 切换到 IO 并不会触发线程切换
- Unconfined直接执行
协程拦截器ContinuationInterceptor
private class MyContinuation<T>(val continuation: Continuation<T>) : Continuation<T> {
override val context: CoroutineContext
get() = continuation.context
override fun resumeWith(result: Result<T>) {
log("<MyContinuation> $result ${context[CoroutineName]} ")
continuation.resumeWith(result)
}
}
private val myContinuationInterceptor = object : ContinuationInterceptor {
override val key: CoroutineContext.Key<*>
get() = ContinuationInterceptor
override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> {
return MyContinuation(continuation)
}
}
协程异常处理器CoroutineExceptionHandler
private val coroutineExceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable ->
log("$throwable ${coroutineContext[CoroutineName]}")
}
//代码使用示例
launch(
Dispatchers.IO + myContinuationInterceptor + coroutineExceptionHandler+CoroutineName("①"),
start = CoroutineStart.DEFAULT
) {
log("DEFAULT")
delay(1000)
log("DEFAULT coroutineScope")
throw NullPointerException("DEFAULT_嘿嘿")
}
日志打印如下:
test1 <MyContinuation> Success(kotlin.Unit) CoroutineName(①) 05:31:35:718 main
test1 DEFAULT 05:31:35:718 main
test1 UNDISPATCHED 05:31:35:718 main
test1 ATOMIC 05:31:35:718 DefaultDispatcher-worker-3
test1 <MyContinuation> Success(kotlin.Unit) CoroutineName(①) 05:31:35:718 kotlinx.coroutines.DefaultExecutor
test1 DEFAULT coroutineScope 05:31:35:718 kotlinx.coroutines.DefaultExecutor
test1 java.lang.NullPointerException: DEFAULT_嘿嘿 CoroutineName(①) 05:31:35:718 kotlinx.coroutines.DefaultExecutor
协程异常传播
关于协程异常传播就涉及到一个概念,协同作用域coroutineScope、主从作用域supervisorScope,结论总结如下:
- coroutineScope中子协程产生的异常会触发父协程取消,同时会影响兄弟协程取消;
- supervisorScope中子协程产生的异常不会向上传递,会在父协程中的异常处理器中捕获;
- 两种作用域如果在父协程中不进行异常捕获均会造成程序崩溃;
//顶层作用域(父协程)需捕获异常,否则会引起程序崩溃
launch(coroutineExceptionHandler+CoroutineName("父协程")) {
supervisorScope {
launch(CoroutineName("①")) {
delay(1000)
log("子协程不受影响")
}
launch(CoroutineName("②")) {
delay(500)
log("子协程抛出异常")
throw NullPointerException("coroutineScope 抛出异常")
}
}
}
System.out: test1 子协程抛出异常 02:19:42:040 main
System.out: test1 CoroutineExceptionHandler java.lang.NullPointerException: coroutineScope 抛出异常 CoroutineName(②) 02:19:42:040 main
System.out: test1 子协程受影响 02:19:42:040 main
launch(coroutineExceptionHandler+CoroutineName("父协程")) {
coroutineScope {
launch(CoroutineName("①")) {
delay(1000)
log("子协程受影响")
}
launch(CoroutineName("②")) {
delay(500)
log("子协程抛出异常")
throw NullPointerException("coroutineScope 抛出异常")
}
}
}
System.out: test1 子协程抛出异常 02:19:42:040 main
System.out: test1 CoroutineExceptionHandler java.lang.NullPointerException: coroutineScope 抛出异常 CoroutineName(父协程) 02:19:42:040 main
通过async启动协程时,只有在await时才会触发异常,异常传递不会向上传播,而是在调用await()方法所处的协程作用域中传播
val deferred = async(coroutineExceptionHandler+CoroutineName("async父协程")) {
delay(500)
throw NullPointerException("coroutineScope 抛出异常")
1
}
launch(coroutineExceptionHandler+CoroutineName("launch父协程")) {
deferred.await()
}
System.out: test1 CoroutineExceptionHandler java.lang.NullPointerException: coroutineScope 抛出异常 CoroutineName(launch父协程) 02:45:44:714 main
协程取消
调用job/deferred 的cancel方法可以触发协程取消操作,通过suspendCancellableCoroutine可以让suspend()方法支持协程的取消操作,通过监听invokeOnCancellation回调获取suspend()方法所处协程的状态
val job = launch(Dispatchers.Default) {
log(1)
val user = getUser()
log(user)
log(2)
}
log(3)
job.cancel()
log(4)
private suspend fun getUser():String = suspendCancellableCoroutine {
it.invokeOnCancellation {
log("invokeOnCancellation is invoked")
}
it.resume("wang")
}
System.out: test1 3 03:18:15:330 main
System.out: test1 1 03:18:15:330 DefaultDispatcher-worker-1
System.out: test1 4 03:18:15:330 main
System.out: test1 invokeOnCancellation is invoked 03:18:15:330 DefaultDispatcher-worker-1