如何在 Kotlin 中等待 billingClient.startConnection
How to await billingClient.startConnection in Kotlin
我正在关注 google 计费 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() {
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
。
我正在关注 google 计费 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() {
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
。