问题描述
我正在关注 google billing integration instructions 并且在如何等待账单客户端连接结果方面遇到了困难。
每当我需要查询 sku 详细信息或购买时,我都需要确保计费客户端已初始化并连接。有 querySkuDetails
和 queryPurchasesAsync
可等待的 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
。