在 Android 中 Coroutine async 崩还是不崩

前置知识

需要从 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 创建了一个 StandaloneCoroutineJob

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父JobSupervisorJob , 即 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个路径执行

  1. 设置了 CoroutineExceptionHandler ,则会执行 CoroutineExceptionHandler#handleException 方法
  2. 调用 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
}

总结

  1. StandaloneCoroutine 的异常处理行为是:将异常抛出到线程世界中;而在 Android 中,某线程未处理异常会直接导致崩溃
  2. 当使用 async 启动协程的时候,如果在协程树上的任一父节点不存在 StandaloneCoroutine , 则 async 即使发生异常,也不会崩溃
相关推荐
顾林海27 分钟前
Android ClassLoader加载机制详解
android·面试·源码
用户20187928316728 分钟前
🎨 童话:Android画布王国的奇妙冒险
android
whysqwhw1 小时前
OkHttp框架的全面深入架构分析
android
你过来啊你1 小时前
Android App冷启动流程详解
android
泓博2 小时前
KMP(Kotlin Multiplatform)改造(Android/iOS)老项目
android·ios·kotlin
移动开发者1号2 小时前
使用Baseline Profile提升Android应用启动速度的终极指南
android·kotlin
移动开发者1号2 小时前
解析 Android Doze 模式与唤醒对齐
android·kotlin
Devil枫4 小时前
Kotlin扩展函数与属性
开发语言·python·kotlin
菠萝加点糖4 小时前
Kotlin Data包含ByteArray类型
android·开发语言·kotlin
IAM四十二9 天前
Google 端侧 AI 框架 LiteRT 初探
android·深度学习·tensorflow