【协程】安全的执行 launch

4,820 阅读4分钟

废话不多说,直接开始。

启动协程的时候,我们的基本写法是这样的:

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再次封装以后,为了传递一个方法,在不停的装箱操作。

(以上为例先创建了匿名对象onErroronLaunchBlock,接着再创建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)
    }
}

最基础的自定义类就写完了,你们可以自己发挥想象,想怎么组合怎么组合,核心方法就是那三个:handleJobExceptiononCompletedonCancelled

接下来定义一个扩展,就照着源码抄嘛,抄啊,会不会?

// 自己的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) {

}

完事,收工。

希望能打开大家的思路,举一反三,实现更多功能。