废话不多说,直接开始。
启动协程的时候,我们的基本写法是这样的:
lifecycleScope.launch {
}
或者
viewModelScope.launch {
}
这会存在一个问题,如果launch
里面发生错误,会直接导致崩溃,这是不安全的。
1. 于是最笨的方法诞生了:
lifecycleScope.launch {
try {
// 加个异常捕获
} catch(e: Throwable) {
}
}
问题解决了,但是并不优雅。每次launch
的时候都要去写try
也是很难受的。
2. 于是进阶方法诞生了,原型如下(各种变形不再列举):
// 扩展方法
fun CoroutineScope.xxxLaunch(
onError: ((Throwable) -> Unit)? = null,
onLaunchBlock: () -> Unit
) {
// 新增一个异常处理的 CoroutineContext
val handler = CoroutineExceptionHandler { _, throwable ->
onError?.invoke(throwable)
}
this.launch(handler) {
onLaunchBlock.invoke()
}
}
使用的时候就是这样:
lifecycleScope.xxxLaunch(
onError = {
},
onLaunchBlock = {
}
)
为了简单说明,直接传递了方法参数,DSL的使用是一样的,只是再封装了一层而已。
感觉怎么样,是不是觉得高级了不少。不管你们怎么感觉,反正我感觉还是怪怪的,特别是经过DSL再次封装以后,为了传递一个方法,在不停的装箱操作。
(以上为例先创建了匿名对象onError
和onLaunchBlock
,接着再创建DSL对象,再把实现的匿名对象传递给DSL对象,感情对象的创建没有开销是吧,再说了,就为了一个launch
,没必要没必要)
这里还有一个问题:例如onError
只能写一次,我想将onError
中的代码分类管理,这里是做不到的,什么意思呢?看下面
// 伪代码,表达意思
lifecycleScope.xxxLaunch(
onError = {
// 处理第一种异常的一大堆逻辑
},
onError = {
// 处理第二种异常的一大堆逻辑
},
onError = {
// 处理第三种异常的一大堆逻辑
},
onLaunchBlock = {
}
)
Flow
还记得么?Flow可以这么写:
flow<Int> {
}.catch {
}.catch {
}.onCompletion {
// 第一个
}.onCompletion {
// 第二个
}
3. 好了,说了那么多,最终版本来了,我们自己实现一个launch
不就完了么。😊
先看一下官方的实现方法:
// launch 系统源码
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
val newContext = newCoroutineContext(context)
// 重点在这里,不使用下面两个,自己实现一个 Coroutine
val coroutine = if (start.isLazy)
LazyStandaloneCoroutine(newContext, block) else
StandaloneCoroutine(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
}
第一步,先实现一个自定义 Coroutine
类(直接看注释吧):
@OptIn(InternalCoroutinesApi::class)
class SafetyCoroutine<T>(
parentContext: CoroutineContext
) : AbstractCoroutine<T>(parentContext + CoroutineExceptionHandler { _, error ->
// 这里打印日志,想写就写
error.printStackTrace()
}, initParentJob = true, active = true) {
/**
* 协程异常回调
* (数组定义为0是为了节省内存,定义为0的情况下,初始状态下不会分配内存,添加数据后 ArrayList 扩容比较收敛,
* 具体自己看源码,不要相信百度、CSDN教程,太老了!
* 这里添加的方法回掉不会很多的,不需要扩容大量内存)
*/
private var catchBlock = ArrayList<((Throwable) -> Unit)>(0)
/**
* 执行成功
* (没太必要添加,这里主要是为了展示。因为 launch 里的代码执行完毕一定是成功的)
*/
private var successBlock = ArrayList<((T) -> Unit)>(0)
/**
* 执行取消
*/
private var cancellBlock = ArrayList<((Throwable) -> Unit)>(0)
/**
* 执行完成
*/
private var completeBlock = ArrayList<((T?) -> Unit)>(0)
/**
* 下面三个主要的方法,你们可以自己发挥想象,自己组合回掉,我这里只是作为说明演示
*/
/**
* 代码发生异常,才会执行此方法
*/
override fun handleJobException(exception: Throwable): Boolean {
handleCoroutineException(context, exception)
if (exception !is CancellationException) { // CancellationException 的不处理
catchBlock.forEach { it.invoke(exception) }
}
return true
}
/**
* 只有代码正常执行完毕,才会执行此方法
* (一定是成功后才会走,协程被取消情况不会走这里)
*/
override fun onCompleted(value: T) {
super.onCompleted(value)
successBlock.forEach { it.invoke(value) }
completeBlock.forEach { it.invoke(value) }
removeCallbacks()
}
/**
* 协程被取消,会执行此方法
*/
override fun onCancelled(cause: Throwable, handled: Boolean) {
super.onCancelled(cause, handled)
cancellBlock.forEach { it.invoke(cause) }
completeBlock.forEach { it.invoke(null) }
removeCallbacks()
}
private fun removeCallbacks() {
successBlock.clear()
catchBlock.clear()
cancellBlock.clear()
completeBlock.clear()
}
fun onCatch(catch: (e: Throwable) -> Unit) = apply {
catchBlock.add(catch)
}
fun onSuccess(success: (T) -> Unit) = apply {
successBlock.add(success)
}
fun onCancell(cancell: (Throwable) -> Unit) = apply {
cancellBlock.add(cancell)
}
fun onComplete(complete: (T?) -> Unit) = apply {
completeBlock.add(complete)
}
}
最基础的自定义类就写完了,你们可以自己发挥想象,想怎么组合怎么组合,核心方法就是那三个:handleJobException
、onCompleted
、onCancelled
接下来定义一个扩展,就照着源码抄嘛,抄啊,会不会?
// 自己的launch扩展
@OptIn(ExperimentalCoroutinesApi::class)
fun <Result> CoroutineScope.launchSafety(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Result, // 这里返回 Result 泛型是为了回掉方便使用
): StatefulCoroutine<Response> {
val newContext = newCoroutineContext(context)
val coroutine = SafetyCoroutine<Result>(newContext)
coroutine.start(start, coroutine, block)
return coroutine
}
用法,很简单:
// 链式调用
lifecycleScope.launchSafety {
// 这里能执行完的代码,一定是成功的
}.onCatch {
// 想来几个就来几个,不想处理就一个都不写
}.onCatch {
}.onComplete {
}.onComplete {
}
// 修改执行线程,和官方的用法一摸一样,没有区别
lifecycleScope.launchSafety(Dispatchers.IO) {
}
完事,收工。
希望能打开大家的思路,举一反三,实现更多功能。