Android开发

[Kotlin协程] 回调地狱的一种解决思路

本文主要是介绍[Kotlin协程] 回调地狱的一种解决思路,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

背景

异步回调的方式虽然实现了需求,但是牺牲了可读性,过多的回调让代码变得难以维护.

解决思路

rxjava->协程

Java语言开发的时候,可以借助一些第三方库如RxJava,来让减少嵌套. 但是实际落地的效果并不理想, 因为不能保证团队里面每个人都擅长并且能够接受Rx的风格.

在使用kotlin协程的时候我发现了新世界.

协程+流式设计

我的需求是实现一个订阅功能,并将功能模块化后提供给其他产品线.

外部调用

外部调用者传入一个id,sdk告诉外部购买成功还是发生异常.

内部实现

内部实现大体上是以上6步. 但是这里每一步都是异步的,如果用java实现,免不了处理很多回调代码. 我尝试使用kotlin+协程完成,发现出奇的简单易读.

// 偷懒 构造一个Activity的扩展方法,反正最后是以Activity的方式来提供出去
fun Activity.goSubscript(productID: String, exceptionHandler: CoroutineExceptionHandler) =
    CoroutineScope(Dispatchers.Main).launch(exceptionHandler) {
        SubscriptHelper(this@goSubscript).run {
            // 1
            init() 
            // 2
            getAvailableHistory()?.let { purchase ->
                if (checkPurchase(purchase)) {
                    return@launch;
                }
            }
            // 3
            val skuDetails = getAvailableProducte(productID)
            // 4
            val purchase = subscriptProducte(this@goSubscript, skuDetails)
            // 5
            val isCheckedSuccess = checkPurchase(purchase)
            // 6
            if (!isCheckedSuccess) {
                val acknowSuccess = acknowledgePurchase(purchase)
                logd(content = "客户端确认${if (acknowSuccess) "成功" else "失败"} ");
            }
        }
    }
复制代码

体会了下kotlin的协程代码风格,这大概就是Kotlin的魅力吧. 那么怎么来实现上诉风格的代码呢? 这里大概需要有两个问题要解决:

  1. 怎么把Java异步回调转化成suspend函数.
  2. 怎么处理java回调哪些异常场景.

把回调转化成suspend函数

解决办法:利用通道

  1. 使用suspendCoroutine
    // 1. 建立和GP/AppStore的连接
    suspend fun init(): Boolean = try {
        withTimeout(1500) {
            suspendCoroutine<Boolean> { continuation ->
                billingClient =
                    BillingClient.newBuilder(context).setListener(listenerUpdate)
                        .enablePendingPurchases()
                        .build()
                        .apply {
                            startConnection {
                                onBillingSetupFinishedFun {
                                    continuation.resume(true)
                                }
                            }
                        };
            }
        }
    } catch (e: TimeoutCancellationException) {
        throw InitException()
    }
复制代码

看上面这个初始化的栗子,先构造了一个withTimeout协程代码块,然后 在suspendCoroutine代码块会生成一个continuation参数,在onBillingSetupFinishedFun函数回调的时候,我们直接使用这个参数来发送消息,结束挂起.

例如成功的时候返回

    continuation.resume(true)
复制代码

异常的时候可以

    continuation.resumeWithException()
复制代码
  1. 使用channel 有的情况下回调不是通过callback完成的,例如使用Handler机制完成的回调代码,怎么转化成协程.
  var channel: Channel<BillingResult> = Channel();
  
  // 利用channel发送
    var listenerUpdate: PurchasesUpdatedListener = object : PurchasesUpdatedListener {
        override fun onPurchasesUpdated(
            billingResult: BillingResult?,
            purchases: MutableList<Purchase>?
        ) {
            CoroutineScope(Dispatchers.Main).launch {
                purchases?.let {
                    if (it.size >= 0) {
                        purchase = it[0]
                    }
                }
                channel.send(billingResult!!)
                channel.close()
            }
        }
    };  
    
// 利用channel接收
    suspend fun subscriptProducte(activity: Activity, skuDetails: SkuDetails): Purchase {
        billingClient.launchBillingFlow(
            activity,
            BillingFlowParams.newBuilder().setSkuDetails(skuDetails).build()
        )
        val result = channel.receive()
        when (result.responseCode) {
            BillingClient.BillingResponseCode.OK -> {
                return purchase!!
            }
            BillingClient.BillingResponseCode.USER_CANCELED -> {
                throw UserCancleException()
            }

            else -> {
                throw SubscriptProductException(result.debugMessage)
            }
        }
    }
复制代码

也就是说channel可以单独出来作为一个对象使用.

协程异常处理

上面的调用函数设计的时候,我构造了一个参数exceptionHandler,用作整个事件的异常处理.

fun Activity.goSubscript(productID: String, exceptionHandler: CoroutineExceptionHandler) =
    CoroutineScope(Dispatchers.Main).launch(exceptionHandler) {
复制代码

然后我定义了一些异常类型

class InitException(msg: String = "") : Exception()
class RepeateSubscription(msg: String = "") : Exception()
class NoProducteException(msg: String = "") : Exception()
class UserCancleException(msg: String = "") : Exception()
class SubscriptProductException(msg: String = "") : Exception()
class AcknowException(msg: String = "") : Exception()
复制代码

然后当内部出现购买失败的回调的时候,主动抛出一个异常,把整个事件流中断,外部处理这些异常也很简单,直接定义一个exceptionHandler(是个参数是throwable的函数)对象接收.

总结起来就是:

  1. 通过抛出异常来中断事件流
  2. 异常在协程内部会向上传导
  3. 外部开辟一个协程任务的时候,可以给内部指定一个异常处理器

总结

  1. 看过一些讲协程的文章,大多都是讲一些原理和API的使用,总感觉没有吸引到让我觉得他比线程模式更好,然后自己摸索着在项目中写了一次.

大概这就是 纸上得来终觉浅,绝知此事要躬行吧.

  1. Android开发,为什么要用Kotlin而不是Java
这篇关于[Kotlin协程] 回调地狱的一种解决思路的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!