前置知识
需要从 Coroutine Job 的层级 知道 Job 之间层级关系
async 什么时候崩
在 Android 日常使用中,我们在用 async
的时候,有时候会发生崩溃,有时候不会发生崩溃;
以 Job 层级结构来看,可以分为 lifecycleScope#async
和在 launch
内使用
Kotlin
lifecycleScope.async {
throw Exception() // no crash
}
lifecleScope.launch {
// ...
async {
throw Exception() // crash
}
// ...
}
看完这篇文章后,你将知道为什么在 lifecycleScope#async
的使用是 不会崩溃 的,而在 launch
内部使用是 会崩溃 的
launch 崩溃路径
StandaloneCoroutine
在看 launch
崩溃路径之前,我们首先要知道 launch
创建了一个 StandaloneCoroutine 的 Job;
该 StandaloneCoroutine 重写了 JobSupport#handleJobException
这个方法;从名字上看可以知道,这是一个处理异常的一个方法
Kotlin
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
}
private open class StandaloneCoroutine(
parentContext: CoroutineContext,
active: Boolean
) : AbstractCoroutine<Unit>(parentContext, initParentJob = true, active = active) {
override fun handleJobException(exception: Throwable): Boolean {
handleCoroutineException(context, exception)
return true
}
}
SupervisorJob
在 Android 使用的 lifecycleScope
或者 viewModelScope
,其内部的 Job 都为 SupervisorJob
Kotlin
// lifecycleScope
public val LifecycleOwner.lifecycleScope: LifecycleCoroutineScope
get() = lifecycle.coroutineScope
public val Lifecycle.coroutineScope: LifecycleCoroutineScope
get() {
while (true) {
val existing = mInternalScopeRef.get() as LifecycleCoroutineScopeImpl?
if (existing != null) return existing
val newScope = LifecycleCoroutineScopeImpl(
this,
SupervisorJob() + Dispatchers.Main.immediate
)
if (mInternalScopeRef.compareAndSet(null, newScope)) {
newScope.register()
return newScope
}
}
}
Kotlin
// viewModelScope
public val ViewModel.viewModelScope: CoroutineScope
get() {
val scope: CoroutineScope? = this.getTag(JOB_KEY)
if (scope != null) {
return scope
}
return setTagIfAbsent(
JOB_KEY,
CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
)
}
我们来看下 SupervisorJob
相关实现,能看到重写了 JobSupport#childCancelled
,而新实现是没有 做任何处理 的;
Kotlin
public fun SupervisorJob(parent: Job? = null) : CompletableJob = SupervisorJobImpl(parent)
private class SupervisorJobImpl(parent: Job?) : JobImpl(parent) {
override fun childCancelled(cause: Throwable): Boolean = false
}
Tips : JobSupport#childCancelled
主要用于 子Job 取消 父Job 的任务
launch 异常路径
由 一文搞懂 Kotlin Coroutine Job 的工作流程 文章可知异常流程最终会走到 JobSupport#finalizeFinishingState
中
Kotlin
public open class JobSupport constructor(active: Boolean) : Job, ChildJob, ParentJob, SelectClause0 {
private fun finalizeFinishingState(state: Finishing, proposedUpdate: Any?): Any? {
// ...
if (finalException != null) {
// step1: cancelParent 取消父协程
// step3: handleJobException 执行异常处理
val handled = cancelParent(finalException) || handleJobException(finalException)
}
// ...
}
private fun cancelParent(cause: Throwable): Boolean {
// step2: 由于当前 launch 的父协程 为 SupervisorJobImpl,直接返回false
return parent.childCancelled(cause) || isCancellation
}
}
从 lifecycleScope#launch
抛异常来看;源码会先执行 JobSupport#cancelParent
-> JobSupport(ParentJob)#childCancelled
;
由于当前 launch
的 父Job 为 SupervisorJob , 即 SupervisorJob#childCancelled
只会返回 false ,所以最后会调用 StandaloneCoroutine#handleJobException
方法
Kotlin
private open class StandaloneCoroutine(
parentContext: CoroutineContext,
active: Boolean
) : AbstractCoroutine<Unit>(parentContext, initParentJob = true, active = active) {
override fun handleJobException(exception: Throwable): Boolean {
handleCoroutineException(context, exception)
return true
}
}
public fun handleCoroutineException(context: CoroutineContext, exception: Throwable) {
try {
context[CoroutineExceptionHandler]?.let {
it.handleException(context, exception)
return
}
} catch (t: Throwable) {
handleCoroutineExceptionImpl(context, handlerException(exception, t))
return
}
handleCoroutineExceptionImpl(context, exception)
}
从上述的源码可看,StandaloneCoroutine#handleCoroutineException
会将自身异常往2个路径执行
- 设置了 CoroutineExceptionHandler ,则会执行
CoroutineExceptionHandler#handleException
方法 - 调用
handleCoroutineExceptionImpl
,并传入当前异常
handleCoroutineExceptionImpl
Kotlin
internal actual fun handleCoroutineExceptionImpl(context: CoroutineContext, exception: Throwable) {
// ...
val currentThread = Thread.currentThread()
runCatching { exception.addSuppressed(DiagnosticCoroutineContextException(context)) }
currentThread.uncaughtExceptionHandler.uncaughtException(currentThread, exception)
}
最终会把异常传递到 KillApplicationHandler#uncaughtExceptionHandler
中,从而发生应用崩溃和退出相关操作;
Java
// source code: https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/com/android/internal/os/RuntimeInit.java
public class RuntimeInit {
@UnsupportedAppUsage
protected static final void commonInit() {
// ...
Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(loggingHandler));
// ...
}
private static class KillApplicationHandler implements Thread.UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
try {
// ...
// step1: 处理应用 crash
ActivityManager.getService().handleApplicationCrash(
mApplicationObject, new ApplicationErrorReport.ParcelableCrashInfo(e));
} catch (Throwable t2) {
// ...
} finally {
// step2: 退出当前应用
Process.killProcess(Process.myPid());
System.exit(10);
}
}
// ...
}
}
Tips : KillApplicationHandler#uncaughtExceptionHandler
是在应用启动到 commonInit
阶段的时候,被注入的
launch
下使用 async
崩溃
由上面 JobSupport#cancelParent
可知,async
会先把 异常 传递给 launch
「父协程」;这样就等价于 launch
发生异常,最终表现与 第一种情况 一致,会发生 应用崩溃
Kotlin
public open class JobSupport constructor(active: Boolean) : Job, ChildJob, ParentJob, SelectClause0 {
private fun finalizeFinishingState(state: Finishing, proposedUpdate: Any?): Any? {
// ...
if (finalException != null) {
// step1: 取消父协程 或者 自身处理异常
val handled = cancelParent(finalException) || handleJobException(finalException)
}
// ...
}
}
lifecycleScope#async 不崩溃
async
的父类为 AbstractCoroutine 而不是 StandaloneCoroutine , 所以未重写 JobSupport#handleJobException
方法
而 JobSupport#handleJobException
默认方法是什么都不做,也就不会抛出异常,从而不会 应用崩溃
Kotlin
public fun <T> CoroutineScope.async(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): Deferred<T> {
val newContext = newCoroutineContext(context)
val coroutine = if (start.isLazy)
LazyDeferredCoroutine(newContext, block) else
DeferredCoroutine<T>(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
}
private open class DeferredCoroutine<T>(
parentContext: CoroutineContext,
active: Boolean
) : AbstractCoroutine<T>(parentContext, true, active = active), Deferred<T> {
override fun getCompleted(): T = getCompletedInternal() as T
override suspend fun await(): T = awaitInternal() as T
override val onAwait: SelectClause1<T> get() = onAwaitInternal as SelectClause1<T>
}
public abstract class AbstractCoroutine<in T>(
parentContext: CoroutineContext,
initParentJob: Boolean,
active: Boolean
) : JobSupport(active), Job, Continuation<T>, CoroutineScope {
// ...
}
public open class JobSupport constructor(active: Boolean) : Job, ChildJob, ParentJob, SelectClause0 {
// 默认实现不处理
protected open fun handleJobException(exception: Throwable): Boolean = false
}
总结
- StandaloneCoroutine 的异常处理行为是:将异常抛出到线程世界中;而在 Android 中,某线程未处理异常会直接导致崩溃
- 当使用
async
启动协程的时候,如果在协程树上的任一父节点不存在 StandaloneCoroutine , 则async
即使发生异常,也不会崩溃