在 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 即使发生异常,也不会崩溃
相关推荐
艾小逗1 小时前
uniapp下载&打开实现方案,支持安卓ios和h5,下载文件到指定目录,安卓文件管理内可查看到
android·ios·uni-app·uniapp文件下载
追梦-北极星2 小时前
android系统查找应用包名以及主activity:
android
guishou先生3 小时前
手机联系人 查询 添加操作
android
我又来搬代码了3 小时前
【Android】application@label 属性属性冲突报错
android
机器视觉小小测试员4 小时前
自动化测试工具Ranorex Studio(七十五)-录制ANDROID测试
android·测试工具·自动化
van叶~5 小时前
仓颉语言实战——2.名字、作用域、变量、修饰符
android·java·javascript·仓颉
m0_748239335 小时前
【PHP】部署和发布PHP网站到IIS服务器
android·服务器·php
小林爱6 小时前
【Compose multiplatform教程14】【组件】LazyColumn组件
android·前端·kotlin·android studio·框架·多平台
牧杉-惊蛰6 小时前
html转PDF
android·pdf
yangfeipancc13 小时前
数据库-用户管理
android·数据库