Android 开发中 Kotlin Coroutines 如何优雅地处理异常

简介: Android 开发中 Kotlin Coroutines 如何优雅地处理异常

一. 尽量少用 GlobalScope



GlobalScope 是 CoroutineScope 的实现类。我们以前使用过的 launch、async  函数都是 CoroutineScope 的扩展函数。


GlobalScope 没有绑定任何 Job 对象,它用于构建最顶层的协程。这些协程的生命周期会跟随着 Application。


在 GlobalScope 中创建的 Coroutines,是有可能会导致应用崩溃的。


例如:

fun main() {
    GlobalScope.launch {
        throw RuntimeException("this is an exception")
        "doSomething..."
    }
    Thread.sleep(5000)
}


即使在 main 函数中,增加了 try...catch 试图去捕获异常,下面的代码仍然抛出异常。

fun doSomething(): Deferred<String> = GlobalScope.async {
    throw RuntimeException("this is an exception")
    "doSomething..."
}
fun main() {
    try {
        GlobalScope.launch {
            doSomething().await()
        }
    } catch (e:Exception) {
    }
    Thread.sleep(5000)
}


这是因为,在 doSomething() 内创建了子 Coroutine,子 Coroutine 的异常会导致整个应用的崩溃。


接下来,通过一个自定义 CoroutineScope ,并且它的 CoroutineContext 与 Job 对象相加,Job 对象可以直接管理该协程。但是子 Coroutine 依然可能会抛出异常,从而导致应用的崩溃。

val job: Job = Job()
val scope = CoroutineScope(Dispatchers.Default+job)
fun doSomething(): Deferred<String> = scope.async {
    throw RuntimeException("this is an exception")
    "doSomething..."
}
fun main() {
    try {
        scope.launch {
            doSomething().await()
        }
    } catch (e:Exception) {
    }
    Thread.sleep(5000)
}


二. SupervisorJob、CoroutineExceptionHandler 的使用



对于 GlobalScope 创建的 Coroutines,前面已经介绍过可能会导致 Crash。


例如:

text1.setOnClickListener {
            GlobalScope.launch(UI) {
                Toast.makeText(mContext,"cannot handle the exception", Toast.LENGTH_SHORT).show()
                throw Exception("this is an exception")
            }
        }


如果能够创建一个 CoroutineScope,由该 CoroutineScope 创建的 Coroutines 即使抛出异常,依然能够捕获,那将是多么的理想。


例如:

text2.setOnClickListener {
            uiScope().launch {
                Toast.makeText(mContext,"handle the exception", Toast.LENGTH_SHORT).show()
                throw Exception("this is an exception")
            }
        }


如果还能够对异常做一些处理,那将是再好不过的了。


例如:

text3.setOnClickListener {
            val errorHandle = object : CoroutineErrorListener {
                override fun onError(throwable: Throwable) {
                    Log.e("errorHandle",throwable.localizedMessage)
                }
            }
            uiScope(errorHandle).launch {
                Toast.makeText(mContext,"handle the exception", Toast.LENGTH_SHORT).show()
                throw Exception("this is an exception")
            }
        }


上面使用的 uiScope 是调用的 SafeCoroutineScope 来创建的 CoroutineScope。

SafeCoroutineScope 的 CoroutineContext 使用了 SupervisorJob 和 CoroutineExceptionHandler。


SupervisorJob 里面的子 Job 不相互影响,一个子 Job 的失败,不会不影响其他子 Job 的执行。

CoroutineExceptionHandler 和使用 Thread.uncaughtExceptionHandler 很相似。 CoroutineExceptionHandler 被用来将通用的 catch 代码块用于在协程中自定义日志记录或异常处理。


我们来看一下它们的封装:

val UI: CoroutineDispatcher      = Dispatchers.Main
fun uiScope(errorHandler: CoroutineErrorListener?=null) = SafeCoroutineScope(UI,errorHandler)
class SafeCoroutineScope(context: CoroutineContext, errorHandler: CoroutineErrorListener?=null) : CoroutineScope, Closeable {
    override val coroutineContext: CoroutineContext = SupervisorJob() + context + UncaughtCoroutineExceptionHandler(errorHandler)
    override fun close() {
        coroutineContext.cancelChildren()
    }
}
class UncaughtCoroutineExceptionHandler(val errorHandler: CoroutineErrorListener?=null)  :
        CoroutineExceptionHandler, AbstractCoroutineContextElement(CoroutineExceptionHandler.Key) {
    override fun handleException(context: CoroutineContext, throwable: Throwable) {
        throwable.printStackTrace()
        errorHandler?.let {
            it.onError(throwable)
        }
    }
}


所以,点击 text2、text3 按钮时,不会导致 App Crash。


在点击 text4 时,即使子 Coroutine 抛出异常,也不会导致 App Crash

text4.setOnClickListener {
            uiScope().launch {
                try {
                    uiScope().async {
                        throw RuntimeException("this is an exception")
                        "doSomething..."
                    }.await()
                } catch (e: Exception) {
                }
            }
            Toast.makeText(mContext,"handle the exception", Toast.LENGTH_SHORT).show()
        }


三. 在 View 中创建 autoDisposeScope



在 Android View 中创建的 Coroutines,需要跟 View 的生命周期绑定。


下面定义的 View 的扩展属性 autoDisposeScope,也是借助 SafeCoroutineScope。

// 在 Android View 中创建 autoDisposeScope,支持主线程运行、异常处理、Job 能够在 View 的生命周期内自动 Disposable
val View.autoDisposeScope: CoroutineScope
    get() {
        return SafeCoroutineScope(UI + ViewAutoDisposeInterceptorImpl(this))
    }


有了 autoDisposeScope 这个 CoroutineScope,就可以在 View 中放心地使用 Coroutines。

text2.setOnClickListener {
            text2.autoDisposeScope.launch {
                doSomeWork()
            }
        }


四. 总结



去年在《AAC 的 Lifecycle 结合 Kotlin Coroutines 进行使用》 一文中,曾经介绍过我封装的库:https://github.com/fengzhizi715/Lifecycle-Coroutines-Extension,本文是对该库的一次升级,也是对近期使用 Kotlin Coroutines 的经验总结。


https://github.com/fengzhizi715/Lifecycle-Coroutines-Extension,不仅是对 Lifecycle 的封装,也可以说是对 Coroutines 使用的封装。

相关文章
|
2月前
|
移动开发 前端开发 Android开发
【02】建立各项目录和页面标准化产品-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【02】建立各项目录和页面标准化产品-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
315 12
【02】建立各项目录和页面标准化产品-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
|
2月前
|
移动开发 JavaScript 应用服务中间件
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
284 5
【06】优化完善落地页样式内容-精度优化-vue加vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
|
2月前
|
移动开发 Rust JavaScript
【01】首页建立-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【01】首页建立-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
660 4
【01】首页建立-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
|
3月前
|
开发工具 Android开发
X Android SDK file not found: adb.安卓开发常见问题-Android SDK 缺少 `adb`(Android Debug Bridge)-优雅草卓伊凡
X Android SDK file not found: adb.安卓开发常见问题-Android SDK 缺少 `adb`(Android Debug Bridge)-优雅草卓伊凡
548 11
X Android SDK file not found: adb.安卓开发常见问题-Android SDK 缺少 `adb`(Android Debug Bridge)-优雅草卓伊凡
|
3月前
|
存储 消息中间件 人工智能
【08】AI辅助编程完整的安卓二次商业实战-修改消息聊天框背景色-触发聊天让程序异常终止bug牵涉更多聊天消息发送优化处理-优雅草卓伊凡
【08】AI辅助编程完整的安卓二次商业实战-修改消息聊天框背景色-触发聊天让程序异常终止bug牵涉更多聊天消息发送优化处理-优雅草卓伊凡
296 10
【08】AI辅助编程完整的安卓二次商业实战-修改消息聊天框背景色-触发聊天让程序异常终止bug牵涉更多聊天消息发送优化处理-优雅草卓伊凡
|
2月前
|
移动开发 Android开发
【03】建立隐私关于等相关页面和内容-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
【03】建立隐私关于等相关页面和内容-vue+vite开发实战-做一个非常漂亮的APP下载落地页-支持PC和H5自适应提供安卓苹果鸿蒙下载和网页端访问-优雅草卓伊凡
177 0
|
3月前
|
Java 开发工具 Maven
【01】完整的安卓二次商业实战-详细的初级步骤同步项目和gradle配置以及开发思路-优雅草伊凡
【01】完整的安卓二次商业实战-详细的初级步骤同步项目和gradle配置以及开发思路-优雅草伊凡
242 6
|
5月前
|
安全 数据库 Android开发
在Android开发中实现两个Intent跳转及数据交换的方法
总结上述内容,在Android开发中,Intent不仅是活动跳转的桥梁,也是两个活动之间进行数据交换的媒介。运用Intent传递数据时需注意数据类型、传输大小限制以及安全性问题的处理,以确保应用的健壯性和安全性。
404 11
|
5月前
|
移动开发 Java 编译器
Kotlin与Jetpack Compose:Android开发生态的演进与架构思考
本文从资深Android工程师视角深入分析Kotlin与Jetpack Compose在Android系统中的技术定位。Kotlin通过空安全、协程等特性解决了Java在移动开发中的痛点,成为Android官方首选语言。Jetpack Compose则引入声明式UI范式,通过重组机制实现高效UI更新。两者结合不仅提升开发效率,更为跨平台战略和现代架构模式提供技术基础,代表了Android开发生态的根本性演进。
227 0
|
6月前
|
安全 Java Android开发
为什么大厂要求安卓开发者掌握Kotlin和Jetpack?深度解析现代Android开发生态优雅草卓伊凡
为什么大厂要求安卓开发者掌握Kotlin和Jetpack?深度解析现代Android开发生态优雅草卓伊凡
297 0
为什么大厂要求安卓开发者掌握Kotlin和Jetpack?深度解析现代Android开发生态优雅草卓伊凡