如何在 Kotlin 中等待 billingClient.startConnection

问题描述

我正在关注 google billing integration instructions 并且在如何等待账单客户端连接结果方面遇到了困难。

每当我需要查询 sku 详细信息或购买时,我都需要确保计费客户端已初始化并连接。有 querySkuDetailsqueryPurchasesAsync 可等待的 kotlin 扩展函数,但 startConnection 是基于侦听器的。以下是文档中的代码示例。

private var billingClient = BillingClient.newBuilder(activity)
   .setListener(purchasesUpdatedListener)
   .enablePendingPurchases()
   .build()

billingClient.startConnection(object : BillingClientStateListener {
    override fun onBillingSetupFinished(billingResult: BillingResult) {
        if (billingResult.responseCode ==  BillingResponseCode.OK) {
            // The BillingClient is ready. You can query purchases here.
        }
    }
    override fun onBillingServicedisconnected() {
        // Try to restart the connection on the next request to
        // Google Play by calling the startConnection() method.
    }
})

suspend fun querySkuDetails() {
    // prepare params
    // Leverage querySkuDetails Kotlin extension function
    val skuDetailsResult = withContext(dispatchers.IO) {
        billingClient.querySkuDetails(params.build())
    }
    // Process the result.
}

如何使用挂起函数将所有这些组合在一起?

解决方法

创建暂停版本 startConnection 的一种方法如下:

/**
 * Returns immediately if this BillingClient is already connected,otherwise
 * initiates the connection and suspends until this client is connected.
 */
suspend fun BillingClient.ensureReady() {
    if (isReady) {
        return // fast path if already connected
    }
    return suspendCoroutine { cont ->
        startConnection(object : BillingClientStateListener {
            override fun onBillingSetupFinished(billingResult: BillingResult) {
                if (billingResult.responseCode == BillingResponseCode.OK) {
                    cont.resume(Unit)
                } else {
                    // you could also use a custom,more precise exception
                    cont.resumeWithException(RuntimeException("Billing setup failed: ${billingResult.debugMessage} (code ${billingResult.responseCode})"))
                }
            }

            override fun onBillingServiceDisconnected() {
                // no need to setup reconnection logic here,call ensureReady() 
                // before each purchase to reconnect as necessary
            }
        })
    }
}

如果另一个协程已经启动了连接,这将失败。 如果要处理对这个方法的潜在并发调用,可以使用互斥锁来保护连接部分:


val billingConnectionMutex = Mutex()

/**
 * Returns immediately if this BillingClient is already connected,otherwise
 * initiates the connection and suspends until this client is connected.
 * If a connection is already in the process of being established,this
 * method just suspends until the billing client is ready.
 */
suspend fun BillingClient.ensureReady() {
    when {
        isReady -> Unit // fast path,already connected
        else -> billingConnectionMutex.withLock {
            if (!isReady) {
                connect()
            }
        }
    }
}

private suspend fun BillingClient.connect() = suspendCoroutine<Unit> { cont ->
    startConnection(object : BillingClientStateListener {
        override fun onBillingSetupFinished(billingResult: BillingResult) {
            if (billingResult.responseCode == BillingResponseCode.OK) {
                cont.resume(Unit)
            } else {
                cont.resumeWithException(RuntimeException("Billing setup failed: ${billingResult.debugMessage} (code ${billingResult.responseCode})"))
            }
        }

        override fun onBillingServiceDisconnected() {
            // no need to setup reconnection logic here,call ensureReady() 
            // before each purchase to reconnect as necessary
        }
    })
}

这里,互斥锁的释放对应于 connect() 的结束,无论其他协程持有它,所以一旦连接成功,它就会被释放。如果其他连接失败,此方法将尝试连接自身(因此在互斥锁中进行就绪检查),并且会自行成功或失败,以便在发生错误时通知调用者。

,

感谢 Joffrey's 的回答和评论,我设法构建了一个返回 BillingResult 的解决方案

val billingConnectionMutex = Mutex()

suspend fun BillingClient.connect(): BillingResult =
    billingConnectionMutex.withLock {
        suspendCoroutine { cont ->
            startConnection(object : BillingClientStateListener {
                override fun onBillingSetupFinished(billingResult: BillingResult) {
                    if (billingResult.responseCode != BillingResponseCode.OK)
                        Log.d(TAG,"Billing setup failed: ${billingResult.debugMessage} (code ${billingResult.responseCode})")
                    cont.resume(billingResult)
                }

                override fun onBillingServiceDisconnected() {}
            })
        }
    }

然后在计费流程中这样调用

if(billingClient!!.connect().responseCode != BillingResponseCode.OK)
    return

如果 billingClient 已连接,它将返回 responseCode.OK,因此无需检查 isReady