如何在 Kotlin 中等待 billingClient.startConnection

How to await billingClient.startConnection in Kotlin

我正在关注 google 计费 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() {
    billingConnectionMutex.withLock {
        // fast path: avoid suspension if another coroutine already connected
        if (isReady) {
            return
        }
        connectOrThrow()
    }
}

private suspend fun BillingClient.connectOrThrow() = suspendCoroutine<Unit> { cont ->
    startConnection(object : BillingClientStateListener {
        override fun onBillingSetupFinished(billingResult: BillingResult) {
            if (billingResult.responseCode == BillingClient.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
        }
    })
}

在这里,互斥量的释放对应于 connectOrThrow() 的结束,无论哪个协程持有它,所以它会在连接成功后立即释放。如果其他连接失败,此方法将尝试连接本身,并自行判断成功或失败,以便在出现错误时通知调用者。

如果您更喜欢直接在 if 语句中处理结果代码,您可以 return 结果而不是抛出:

private val billingConnectionMutex = Mutex()

private val resultAlreadyConnected = BillingResult.newBuilder()
    .setResponseCode(BillingClient.BillingResponseCode.OK)
    .setDebugMessage("Billing client is already connected")
    .build()

/**
 * 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.connect(): BillingResult = billingConnectionMutex.withLock {
    if (isReady) {
        // fast path: avoid suspension if already connected
        resultAlreadyConnected
    } else {
        unsafeConnect()
    }
}

private suspend fun BillingClient.unsafeConnect() = suspendCoroutine<BillingResult> { cont ->
    startConnection(object : BillingClientStateListener {
        override fun onBillingSetupFinished(billingResult: BillingResult) {
            cont.resume(billingResult)
        }
        override fun onBillingServiceDisconnected() {
            // no need to setup reconnection logic here, call ensureReady()
            // before each purchase to reconnect as necessary
        }
    })
}

感谢 的回答和评论,我已经设法用 BillingResult returned

构建了一个解决方案
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 已经连接,它将 return responseCode.OK,因此无需检查 isReady