异步回调的方式虽然实现了需求,但是牺牲了可读性,过多的回调让代码变得难以维护.
Java语言开发的时候,可以借助一些第三方库如RxJava,来让减少嵌套. 但是实际落地的效果并不理想, 因为不能保证团队里面每个人都擅长并且能够接受Rx的风格.
在使用kotlin协程的时候我发现了新世界.
我的需求是实现一个订阅功能,并将功能模块化后提供给其他产品线.
外部调用者传入一个id,sdk告诉外部购买成功还是发生异常.
// 偷懒 构造一个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. 建立和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() 复制代码
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的函数)对象接收.
总结起来就是:
大概这就是 纸上得来终觉浅,绝知此事要躬行吧.