协程和线程的区别
线程是进程内的一个执行单元,是由操作系统调度的,线程一开始执行就不会暂停,直到任务结束,线程之间是抢占式的调度,不存在协作。
协程只是一种概念,它提供了一种避免阻塞线程并用更简单、更可控的操作替代线程阻塞的方法。程序可以自己处理挂起与恢复,本质上Kotlin协程就是作为在Kotlin语言上进行异步编程的解决方案,处理异步代码的方法。
协程可以使用阻塞的方式写出非阻塞式的代码,解决并发中常见的回调地狱。消除了并发任务之间的协作的难度,协程可以让我们轻松地写出复杂的并发代码。一些本来不可能实现的并发任务变的可能,甚至简单,这些才是协程的优势所在。
suspend
Kotlin协程设计的很巧妙,只需要使用suspend关键字就可以表示挂起函数,包含了一步和回调两层含义。suspend 函数不能在普通函数中调用。如需调用 suspend 函数,只能从其他 suspend 函数进行调用,或通过使用协程构建器来启动新的协程。
为什么普通函数不能调用suspend函数
当普通函数调用suspend函数时会报红,为什么会这样呢?这时候必须看一下协程编译后的源码了。先写两个简单的suspend函数,然后点击工具栏–>Tools–>Kotlin–>Show Kotlin Bytecode–>Decompile
suspend fun test() {
test2()
}
suspend fun test2(){
delay(1000)
}
@Nullable
public final Object test(@NotNull Continuation $completion) {
Object var10000 = this.test2($completion);
return var10000 == IntrinsicsKt.getCOROUTINE_SUSPENDED() ? var10000 : Unit.INSTANCE;
}
@Nullable
public final Object test2(@NotNull Continuation $completion) {
Object var10000 = DelayKt.delay(1000L, $completion);
return var10000 == IntrinsicsKt.getCOROUTINE_SUSPENDED() ? var10000 : Unit.INSTANCE;
}
通过反编译为字节码再转换为java代码,发现方法中多了一个Continuation参数,调用其他挂起函数时会将这个参数传递下去,因此可以知道为什么suspend函数中可以调用普通函数,但普通函数却不能调用suspend函数。
Continuation
Continuation是是一个接口,它的作用是程序挂起后的恢复,类似java中的Callback回调。
public interface Continuation<in T> {
public val context: CoroutineContext
public fun resumeWith(result: Result<T>)
}
@SinceKotlin("1.3")
@InlineOnly
public inline fun <T> Continuation<T>.resume(value: T): Unit =
resumeWith(Result.success(value))
@SinceKotlin("1.3")
@InlineOnly
public inline fun <T> Continuation<T>.resumeWithException(exception: Throwable): Unit =
resumeWith(Result.failure(exception))
Continuation 有一个 resumeWith 函数可以接收 Result 类型的参数。在结果获取成功时,调用resumeWith(Result.success(value))或者调用拓展函数resume(value);当出现异常时,调用resumeWith(Result.failure(exception))或者调用拓展函数resumeWithException(exception),其本质就是一个回调。从这里我们也就清楚了为什么协程可以用同步的方式写出异步的代码。
协程的挂起
所谓的协程的挂起其实就是程序执行流程发生异步调用时,当前调用流程的执行状态进入等待状态。但是挂起函数不一定会挂起,比如下面的代码:
suspend fun test() {
return
}
suspend fun test2(){
suspendCoroutine<Int> {
thread {
it.resume(1)
}
}
}
test方法一调用直接return返回了,没有发生挂起操作,test2方法使用suspendCoroutine获取当前所在协程的Continuation的实例作为参数将挂起函数当成异步函数来处理,然后开启了一个thread执行resume操作,因此无法同步执行,会进入挂起状态,直到结果返回。
挂起点
通过前面的例子,我们知道了一个函数想要自己挂起,所需要的无非就是一个Continuation实例,正因为如此挂起函数才能在协程体中运行,在协程的内部挂起函数的调用处被称为挂起点,挂起点如果出现异步调用,当前的协程就被挂起,直到Continuation的resume函数被调用才会恢复。
异步调用是否发生,取决于resume函数与对应的挂起函数的调用是否在相同的调用栈上,切换函数调用栈的方法可以是切换到其他线程上执行,也可以是不切换线程但在当前函数返回之后的某一个时刻再执行。前者比较容易理解,后者其实通常就是先将Continuation的实例保存下来,在后续合适的时机再调用。比如一下的挂起函数不会被挂起:
suspend fun notSuspend() = suspendCoroutine<Int> { continuation ->
continuation.resume(100)
}
挂起函数恢复
协程工作的核心就是它内部的状态机,invokeSuspend() 函数。 getUserName() 方法是一个挂起函数,这里通过反编译它来阐述协程状态机的原理。
suspend fun getUserName(): String {
delay(100)
return "jack"
}
@Nullable
//返回值由String变成了Object并增加了Continuation参数
public final Object getUserName(@NotNull Continuation var1) {
//将要执行的Continuation逻辑传入ContinuationImpl中
Object $continuation = new ContinuationImpl(var1) {
Object result;
int label;
@Nullable
//invokeSuspend()会在恢复协程挂起点时调用
public final Object invokeSuspend(@NotNull Object $result) {
this.result = $result;
this.label |= Integer.MIN_VALUE;
//又调用了getUserName方法
return MainActivity.this.getUserName(this);
}
};
Object $result = ((<undefinedtype>)$continuation).result;
Object var4 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
//第一次走到这里label=0进入case 0 分支,label变为1,第二次就会进入case 1 分支
switch(((<undefinedtype>)$continuation).label) {
case 0:
ResultKt.throwOnFailure($result);
((<undefinedtype>)$continuation).label = 1;
//delay()函数也是一个挂起函数,传入一个continuation回调,返回一个object结果。如果被挂起返回`COROUTINE_SUSPENDED`
if (DelayKt.delay(100L, (Continuation)$continuation) == var4) {
return var4;
}
break;
case 1:
//判断返回的value是否是Result.Failure,如果是就会抛出异常
ResultKt.throwOnFailure($result);
break;
default:
throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
}
return "jack";
}
上下文
CoroutineContext是协程的上下文,它和Android中的Context上下文类似,主要承载着资源获取、配置管理等工作,是执行环境的通用数据资源的统一管理者。它有很多作用,包括携带参数、拦截、调度、异常处理等。
public interface CoroutineContext {
//根据key获取对应元素
public operator fun <E : Element> get(key: Key<E>): E?
//从initial开始累加上下文中的条目
public fun <R> fold(initial: R, operation: (R, Element) -> R): R
//加法运算,具有相同键的元素会被覆盖。
public operator fun plus(context: CoroutineContext): CoroutineContext =
if (context === EmptyCoroutineContext) this else
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)
}
}
}
//删除当前上下文中指定key的元素
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
}
}
CoroutineContext是一个单链表的数据结构,主要有三个方法get
获取元素、minusKey
移除元素、plus
添加元素。最重要的是plus
方法,这个方法被operator
修饰,这是kotlin中的操作符重载,可以通过 + 来调用plus方法。
suspend fun test() {
val context = Dispatchers.IO + CoroutineName("jack") + CoroutineExceptionHandler { coroutineContext, throwable ->
}
CoroutineScope(context).launch {
var coroutineName = coroutineContext[CoroutineName.Key]
Log.e(TAG, coroutineName?.name ?: "")//可以获取到CoroutineName
val coroutineContext = coroutineContext.minusKey(CoroutineName.Key)
coroutineName = coroutineContext[CoroutineName.Key]
Log.e(TAG, coroutineName?.name ?: "")//CoroutineName被删除,获取不到
}
}
Coroutine继承关系图:
协程的拦截器
标准库提供了一个协程的拦截器ContinuationInterceptor,是CoroutineContext的子类,主要方法是通过interceptContinuation方法,将传入的Continuation对象包装后重新返回一个新的Continuation对象,类似于装饰器模式,下面是拦截器的使用方法:
suspend fun test() {
val context = LogInterceptor()
CoroutineScope(context).launch {
Log.e(TAG, "hello")
}
}
class LogInterceptor : ContinuationInterceptor {
override val key: CoroutineContext.Key<*>
get() = ContinuationInterceptor
override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> {
return LogContinuation(continuation)
}
}
class LogContinuation<T>(private val continuation: Continuation<T>) : Continuation<T> {
override fun resumeWith(result: Result<T>) {
Log.e("LogInterceptor", "before resumeWith")
continuation.resumeWith(result)
Log.e("LogInterceptor", "after resumeWith")
}
override val context: CoroutineContext
get() = continuation.context
}
为了确保拦截器在协程恢复的时候总是第一个被调用,在CoroutineContext中,每次调用plus方法都会将Key是ContinuationInterceptor类型的上下文放到链表的第一个位置。
协程的调度
协程的分发CoroutineDispatcher实现,主要方法有isDispatchNeeded、dispatch、interceptContinuation,通过dispatch方法调度到其他线程执行,继承于ContinuationInterceptor类。
public abstract class CoroutineDispatcher :
AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {
//...省略其他代码
// 是否需要调度
public open fun isDispatchNeeded(context: CoroutineContext): Boolean = true
// 传入一个Runnable调度到其他线程上
public abstract fun dispatch(context: CoroutineContext, block: Runnable)
// 通过DispatchedContinuation返回包装的continuation
public final override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> =
DispatchedContinuation(this, continuation)
//...省略其他代码
}
先看一下DispatchedContinuation类的实现:
internal class DispatchedContinuation<in T>(
@JvmField val dispatcher: CoroutineDispatcher,
@JvmField val continuation: Continuation<T>
) : DispatchedTask<T>(MODE_UNINITIALIZED), CoroutineStackFrame, Continuation<T> by continuation {
//...省略其他代码
override fun resumeWith(result: Result<T>) {
val context = continuation.context
val state = result.toState()
//判断是否需要线程调度
if (dispatcher.isDispatchNeeded(context)) {
_state = state
resumeMode = MODE_ATOMIC
// 将协程分发到另一个线程
dispatcher.dispatch(context, this)
} else {
// 如果不需要调度,恢复在当前线程执行
executeUnconfined(state, MODE_ATOMIC) {
withCoroutineContext(this.context, countOrElement) {
continuation.resumeWith(result)
}
}
}
}
//...省略其他代码
}
DispatchedContinuation拦截了协程,在resumeWith方法中调用dispatcher.dispatch(context, this) 将协程调度到其他线程,DispatchedContinuation继承DispatchedTask,它是一个Runnable,因此传入的this也是一个Runnable。
Dispatchers.Main
Dispatchers.Main是MainCoroutineDispatcher类的对象,在MainDispatcherLoader中通过工厂模式创建 MainCoroutineDispatcher。
internal object MainDispatcherLoader {
private val FAST_SERVICE_LOADER_ENABLED = systemProp(FAST_SERVICE_LOADER_PROPERTY_NAME, true)
@JvmField
val dispatcher: MainCoroutineDispatcher = loadMainDispatcher()
private fun loadMainDispatcher(): MainCoroutineDispatcher {
return try {
val factories = if (FAST_SERVICE_LOADER_ENABLED) {
FastServiceLoader.loadMainDispatcherFactory()
} else {
ServiceLoader.load(
MainDispatcherFactory::class.java,
MainDispatcherFactory::class.java.classLoader
).iterator().asSequence().toList()
}
@Suppress("ConstantConditionIf")
factories.maxBy { it.loadPriority }?.tryCreateDispatcher(factories)
?: createMissingDispatcher()
} catch (e: Throwable) {
createMissingDispatcher(e)
}
}
}
通过FastServiceLoader.loadMainDispatcherFactory()获取到AndroidDispatcherFactory。
internal class AndroidDispatcherFactory : MainDispatcherFactory {
//AndroidDispatcherFactory的createDispatcher方法创建HandlerContext,传入了Looper.getMainLooper(),可以看出是android的主线程调度器
override fun createDispatcher(allFactories: List<MainDispatcherFactory>) =
HandlerContext(Looper.getMainLooper().asHandler(async = true), "Main")
//...省略其他代码
}
那么主线程调度肯定是在HandlerContext 中实现了,我们来看下HandlerContext的代码实现:
internal class HandlerContext private constructor(
private val handler: Handler,
private val name: String?,
private val invokeImmediately: Boolean
) : HandlerDispatcher(), Delay {
//...省略其他代码
public constructor(handler: Handler,name: String? = null) : this(handler, name, false)
override fun isDispatchNeeded(context: CoroutineContext): Boolean {
return !invokeImmediately || Looper.myLooper() != handler.looper
}
override fun dispatch(context: CoroutineContext, block: Runnable) {
handler.post(block)
}
//...省略其他代码
}
HandlerContext的isDispatchNeeded()方法返回true,当协程启动的时候则由HandlerContext来通过 handler.post(runnable) 分发给主线程。在恢复的时候也是通过HandlerContext调度器来恢复。
Dispatchers.Default
Dispatchers.Default内部默认使用DefaultScheduler分发,DefaultScheduler是kotlin内部自己实现的线程池逻辑,Dispatchers.Default也可以通过系统配置获取java中Executor实现的线程池逻辑。
public actual object Dispatchers {
@JvmStatic
public actual val Default: CoroutineDispatcher = createDefaultDispatcher()
}
internal const val COROUTINES_SCHEDULER_PROPERTY_NAME = "kotlinx.coroutines.scheduler"
internal val useCoroutinesScheduler = systemProp(COROUTINES_SCHEDULER_PROPERTY_NAME).let { value ->
when (value) {
null, "", "on" -> true
"off" -> false
else -> error("System property '$COROUTINES_SCHEDULER_PROPERTY_NAME' has unrecognized value '$value'")
}
}
internal actual fun createDefaultDispatcher(): CoroutineDispatcher =
if (useCoroutinesScheduler) DefaultScheduler else CommonPool
从源码中可以看到,使用过获取系统属性拿到的值, 那我们就可以通过修改系统属性 去改变useCoroutinesScheduler的值,具体修改方法为:
val properties = Properties()
properties["kotlinx.coroutines.scheduler"] = "off"
System.setProperties(properties)
DefaultScheduler继承于ExperimentalCoroutineDispatcher,主要实现都在其父类中实现。
public open class ExperimentalCoroutineDispatcher(
private val corePoolSize: Int,
private val maxPoolSize: Int,
private val idleWorkerKeepAliveNs: Long,
private val schedulerName: String = "CoroutineScheduler"
) : ExecutorCoroutineDispatcher() {
public constructor(
corePoolSize: Int = CORE_POOL_SIZE,
maxPoolSize: Int = MAX_POOL_SIZE,
schedulerName: String = DEFAULT_SCHEDULER_NAME
) : this(corePoolSize, maxPoolSize, IDLE_WORKER_KEEP_ALIVE_NS, schedulerName)
override val executor: Executor
get() = coroutineScheduler
//创建CoroutineScheduler实例
private var coroutineScheduler = createScheduler()
override fun dispatch(context: CoroutineContext, block: Runnable): Unit =
try {
coroutineScheduler.dispatch(block)
} catch (e: RejectedExecutionException) {
DefaultExecutor.dispatch(context, block)
}
//请求阻塞,执行IO密集型任务
public fun blocking(parallelism: Int = BLOCKING_DEFAULT_PARALLELISM): CoroutineDispatcher {
require(parallelism > 0) { "Expected positive parallelism level, but have $parallelism" }
return LimitingDispatcher(this, parallelism, null, TASK_PROBABLY_BLOCKING)
}
//并发数量限制,执行CPU密集型任务
public fun limited(parallelism: Int): CoroutineDispatcher {
require(parallelism > 0) { "Expected positive parallelism level, but have $parallelism" }
require(parallelism <= corePoolSize) { "Expected parallelism level lesser than core pool size ($corePoolSize), but have $parallelism" }
return LimitingDispatcher(this, parallelism, null, TASK_NON_BLOCKING)
}
private fun createScheduler() = CoroutineScheduler(corePoolSize, maxPoolSize, idleWorkerKeepAliveNs, schedulerName)
}
ExperimentalCoroutineDispatcher类中createScheduler方法创建协程调度线程池,调用dispatch方法分发到CoroutineScheduler的dipatch方法。
internal enum class TaskMode {
//执行CPU密集型任务
NON_BLOCKING,
//执行IO密集型任务
PROBABLY_BLOCKING,
}
internal class CoroutineScheduler(
@JvmField val corePoolSize: Int,
@JvmField val maxPoolSize: Int,
@JvmField val idleWorkerKeepAliveNs: Long = IDLE_WORKER_KEEP_ALIVE_NS,
@JvmField val schedulerName: String = DEFAULT_SCHEDULER_NAME
) : Executor, Closeable {
fun dispatch(block: Runnable, taskContext: TaskContext = NonBlockingContext, tailDispatch: Boolean = false) {
trackTask() // this is needed for virtual time support
val task = createTask(block, taskContext)
// try to submit the task to the local queue and act depending on the result
val currentWorker = currentWorker()
val notAdded = currentWorker.submitToLocalQueue(task, tailDispatch)
if (notAdded != null) {
if (!addToGlobalQueue(notAdded)) {
// Global queue is closed in the last step of close/shutdown -- no more tasks should be accepted
throw RejectedExecutionException("$schedulerName was terminated")
}
}
val skipUnpark = tailDispatch && currentWorker != null
if (task.mode == TASK_NON_BLOCKING) {
if (skipUnpark) return
signalCpuWork()//处理CPU密集任务,Dispatchers.Default
} else {
signalBlockingWork(skipUnpark = skipUnpark)//处理IO密集任务,Dispatchers.IO
}
}
private fun signalBlockingWork(skipUnpark: Boolean) {
// Use state snapshot to avoid thread overprovision
val stateSnapshot = incrementBlockingTasks()
if (skipUnpark) return
if (tryUnpark()) return
if (tryCreateWorker(stateSnapshot)) return
tryUnpark() // Try unpark again in case there was race between permit release and parking
}
internal fun signalCpuWork() {
if (tryUnpark()) return
if (tryCreateWorker()) return
tryUnpark()
}
}
Dispatchers.IO
Dispatchers.IO的分发是LimitingDispatcher类实现,继承自ExecutorCoroutineDispatcher。
public actual object Dispatchers {
@JvmStatic
public val IO: CoroutineDispatcher = DefaultScheduler.IO
}
internal object DefaultScheduler : ExperimentalCoroutineDispatcher() {
//设置最大并发线程数量,taskModel设置为 TASK_PROBABLY_BLOCKING
val IO: CoroutineDispatcher = LimitingDispatcher(
this,
systemProp(IO_PARALLELISM_PROPERTY_NAME, 64.coerceAtLeast(AVAILABLE_PROCESSORS)),
"Dispatchers.IO",
TASK_PROBABLY_BLOCKING
)
//...省略其他代码
}
private class LimitingDispatcher(
private val dispatcher: ExperimentalCoroutineDispatcher,
private val parallelism: Int,
private val name: String?,
override val taskMode: Int
) : ExecutorCoroutineDispatcher(), TaskContext, Executor {
private val queue = ConcurrentLinkedQueue<Runnable>()
private val inFlightTasks = atomic(0)
override val executor: Executor
get() = this
override fun dispatch(context: CoroutineContext, block: Runnable) = dispatch(block, false)
private fun dispatch(block: Runnable, tailDispatch: Boolean) {
var taskToSchedule = block
while (true) {
val inFlight = inFlightTasks.incrementAndGet()
//判断不超过最大并发线程数量,最终调用到CoroutineScheduler的dispatch方法
if (inFlight <= parallelism) {
dispatcher.dispatchWithContext(taskToSchedule, this, tailDispatch)
return
}
//如果超过最大并发线程数量,则加入到队列中等待
queue.add(taskToSchedule)
if (inFlightTasks.decrementAndGet() >= parallelism) {
return
}
taskToSchedule = queue.poll() ?: return
}
}
//...省略其他代码
override fun afterTask() {
var next = queue.poll()
// If we have pending tasks in current blocking context, dispatch first
if (next != null) {
dispatcher.dispatchWithContext(next, this, true)
return
}
inFlightTasks.decrementAndGet()
next = queue.poll() ?: return
dispatch(next, true)
}
}
小结:
Dispatchers.Default和Dispatchers.IO都是共享CoroutineScheduler线程池,区别是核心线程数和最大线程数不同,主要是通过dispatch方法中的Mode区分,分别代表CPU密集型和IO密集型线程池。
Type | Mode | 线程池类型 |
---|---|---|
Default | NON_BLOCKING | CPU密集型 |
IO | PROBABLY_BLOCKING | IO密集型 |
Dispatchers.Unconfined
Dispatchers.Unconfined:Unconfined继承CoroutineDispatcher,重写isDispatchNeeded()方法返回false,不限于任何特定线程的协程调度程序。那么它的父类ContinuationInterceptor就不会把本次任务的调度交给子类来执行,而是由父类在当前线程立刻执行。
public actual val Unconfined: CoroutineDispatcher = kotlinx.coroutines.Unconfined
internal object Unconfined : CoroutineDispatcher() {
//返回false, 不拦截协程
override fun isDispatchNeeded(context: CoroutineContext): Boolean = false
override fun dispatch(context: CoroutineContext, block: Runnable) {
}
}
协程异常处理
当协程中发生异常,可以通过try-catch来捕获,但是如果出现了未捕获的异常就会出现闪退。那么怎么办呢?我们可以使用CoroutineExceptionHandler来处理。
public interface CoroutineExceptionHandler : CoroutineContext.Element {
public companion object Key : CoroutineContext.Key<CoroutineExceptionHandler>
public fun handleException(context: CoroutineContext, exception: Throwable)
}
使用方法:
suspend fun test() {
exceptionHandler =
CoroutineExceptionHandler { _, throwable ->
Log.e("CoroutineException", throwable.message ?: "")
}
val context = LogInterceptor() + Dispatchers.IO + exceptionHandler
CoroutineScope(context).launch {
Log.e(TAG, "hello")
throw IllegalArgumentException("occur exception!!!")
}
}
当发生异常会调用到JobSupport中的finalizeFinishingState方法,然后调用handleJobException方法处理异常。
public open class JobSupport constructor(active: Boolean) : Job, ChildJob, ParentJob, SelectClause0 {
final override val key: CoroutineContext.Key<*> get() = Job
//...省略其他代码
private fun finalizeFinishingState(state: Finishing, proposedUpdate: Any?): Any? {
//...省略其他代码
if (finalException != null) {
//处理异常
val handled = cancelParent(finalException) || handleJobException(finalException)
if (handled) (finalState as CompletedExceptionally).makeHandled()
}
//...省略其他代码
}
}
然后我们看一下StandaloneCoroutine的handleJobException异常处理:
private open class StandaloneCoroutine(
parentContext: CoroutineContext,
active: Boolean
) : AbstractCoroutine<Unit>(parentContext, active) {
override fun handleJobException(exception: Throwable): Boolean {
handleCoroutineException(context, exception)
return true
}
}
public fun handleCoroutineException(context: CoroutineContext, exception: Throwable) {
try {
//获取CoroutineExceptionHandler处理异常
context[CoroutineExceptionHandler]?.let {
it.handleException(context, exception)
return
}
} catch (t: Throwable) {
handleCoroutineExceptionImpl(context, handlerException(exception, t))
return
}
// 如果没有设置异常处理器,则调用全局的异常处理器
handleCoroutineExceptionImpl(context, exception)
}
//使用SPI机制,获取实现的异常处理器
private val handlers: List<CoroutineExceptionHandler> = ServiceLoader.load(
CoroutineExceptionHandler::class.java,
CoroutineExceptionHandler::class.java.classLoader
).iterator().asSequence().toList()
internal actual fun handleCoroutineExceptionImpl(context: CoroutineContext, exception: Throwable) {
//循环全局异常处理器处理异常
for (handler in handlers) {
try {
handler.handleException(context, exception)
} catch (t: Throwable) {
// Use thread's handler if custom handler failed to handle exception
val currentThread = Thread.currentThread()
currentThread.uncaughtExceptionHandler.uncaughtException(currentThread, handlerException(exception, t))
}
}
val currentThread = Thread.currentThread()
currentThread.uncaughtExceptionHandler.uncaughtException(currentThread, exception)
}
在kotlinx-coroutines-android包下实现了一个全局异常处理器AndroidExceptionPreHandler,他可以将错误堆栈信息传递给当前线程的uncaughtExceptionHandler。我们也可以自定义一个全局的异常处理器:
//@AutoService(CoroutineExceptionHandler::class)
class GlobalCoroutineExceptionHandler : CoroutineExceptionHandler {
override val key: CoroutineContext.Key<*>
get() = CoroutineExceptionHandler
override fun handleException(context: CoroutineContext, exception: Throwable) {
Log.e("CoroutineException", exception.message ?: "")
}
}
有两种方法可以加入到handlers中:
方法一:在根目录创建META-INF.services目录,并在其中创建一个名为kotlinx.coroutines.CoroutineExceptionHandler的文件,文件的内容就是我们的全局异常处理器的全类名。
com.jk.coroutinedemo.GlobalCoroutineExceptionHandler
方法二:使用google的auto-service框架,在GlobalCoroutineExceptionHandler类的前面加一个@AutoService(CoroutineExceptionHandler::class)
就行了。
参考
-
《深入理解Kotlin协程-霍丙乾》