- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我尝试使用 Kotlin Coroutines 为 BillingClient v.2.2.0 编写一个包装器:
package com.cantalk.photopose.billing
import android.app.Activity
import android.content.Context
import com.android.billingclient.api.*
import com.android.billingclient.api.BillingClient.*
import com.cantalk.photopose.util.Logger
import kotlinx.coroutines.CompletableDeferred
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
class BillingClientAsync(context: Context) {
private val billingClient: BillingClient = setupBillingClient(context)
private val pendingPurchaseFlows = HashMap<String, CompletableDeferred<Purchase>>()
private fun setupBillingClient(context: Context): BillingClient {
return newBuilder(context)
.enablePendingPurchases()
.setListener { billingResult, purchases ->
if (billingResult.responseCode == BillingResponseCode.OK && purchases != null) {
for (purchase in purchases) {
val deferred = pendingPurchaseFlows.remove(purchase.sku)
deferred?.complete(purchase)
}
} else {
val iterator = pendingPurchaseFlows.iterator()
while (iterator.hasNext()) {
val entry = iterator.next()
entry.value.completeExceptionally(BillingException(billingResult))
iterator.remove()
}
}
}
.build()
}
suspend fun queryPurchases(): List<Purchase> {
Logger.debug("query purchases")
ensureConnected()
val queryPurchases = billingClient.queryPurchases(SkuType.INAPP)
if (queryPurchases.responseCode == BillingResponseCode.OK) {
return queryPurchases.purchasesList
} else {
throw BillingException(queryPurchases.billingResult)
}
}
suspend fun querySkuDetails(@SkuType type: String, skus: List<String>): List<SkuDetails> {
Logger.debug("query sku details for", type)
ensureConnected()
return suspendCoroutine { continuation ->
val params = SkuDetailsParams.newBuilder()
.setType(type)
.setSkusList(skus)
.build()
billingClient.querySkuDetailsAsync(params) { billingResult, skuDetailsList ->
if (billingResult.responseCode == BillingResponseCode.OK) {
continuation.resume(skuDetailsList)
} else {
continuation.resumeWithException(BillingException(billingResult))
}
}
}
}
suspend fun purchase(activity: Activity, skuDetails: SkuDetails): Purchase {
Logger.debug("purchase", skuDetails.sku)
ensureConnected()
val currentPurchaseFlow = CompletableDeferred<Purchase>()
.also { pendingPurchaseFlows[skuDetails.sku] = it }
val params = BillingFlowParams.newBuilder()
.setSkuDetails(skuDetails)
.build()
billingClient.launchBillingFlow(activity, params)
return currentPurchaseFlow.await()
}
suspend fun consume(purchase: Purchase): String {
Logger.debug("consume", purchase.sku)
ensureConnected()
return suspendCoroutine { continuation ->
val params = ConsumeParams.newBuilder()
.setPurchaseToken(purchase.purchaseToken)
.setDeveloperPayload("TBD")
.build()
billingClient.consumeAsync(params) { billingResult, purchaseToken ->
if (billingResult.responseCode == BillingResponseCode.OK) {
continuation.resume(purchaseToken)
} else {
continuation.resumeWithException(BillingException(billingResult))
}
}
}
}
suspend fun acknowledgePurchase(purchase: Purchase) {
Logger.debug("acknowledge", purchase.sku)
ensureConnected()
return suspendCoroutine { continuation ->
val params = AcknowledgePurchaseParams.newBuilder()
.setPurchaseToken(purchase.purchaseToken)
.setDeveloperPayload("TBD")
.build()
billingClient.acknowledgePurchase(params) { billingResult ->
if (billingResult.responseCode == BillingResponseCode.OK) {
continuation.resume(Unit)
} else {
continuation.resumeWithException(BillingException(billingResult))
}
}
}
}
private suspend fun ensureConnected() {
if (!billingClient.isReady) {
startConnection()
}
}
private suspend fun startConnection() {
Logger.debug("connect to billing service")
return suspendCoroutine { continuation ->
billingClient.startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(billingResult: BillingResult) {
if (billingResult.responseCode == BillingResponseCode.OK) {
continuation.resume(Unit)
} else {
// TODO: 3 Google Play In-app Billing API version is less than 3
continuation.resumeWithException(BillingException(billingResult))
}
}
override fun onBillingServiceDisconnected() = Unit
})
}
}
}
java.lang.IllegalStateException:
at kotlin.coroutines.SafeContinuation.resumeWith (SafeContinuation.java:2)
at com.cantalk.photopose.billing.BillingClientAsync$startConnection$2$1.onBillingSetupFinished (BillingClientAsync.java:2)
at com.android.billingclient.api.zzai.run (zzai.java:6)
BillingClientStateListener.onBillingSetupFinished
会被多次调用可能有异常
IllegalStateException: Already resumed
.我想知道这怎么可能,因为我每隔
startConnection
创建一个新的监听器。称呼?我无法在模拟器或我的测试设备上重现此问题。谁能解释一下这里发生了什么以及如何解决它?
最佳答案
我一开始也尝试这样做,但理由不正确。 onBillingSetupFinished()
根据设计,可能会多次调用。一旦您调用 BillingClient.startConnection(BillingClientStateListener)
使用回调,它在内部存储回调,并在连接被丢弃/重新获得时再次调用它。您不应在对 BillingClient.startConnection(BillingClientStateListener)
的其他调用中传入新对象。 .
阅读 onBillingServiceDisconnected()
上的文档:
Called to notify that the connection to the billing service was lost.
Note: This does not remove the billing service connection itself -this binding to the service will remain active, and you will receive acall to onBillingSetupFinished(BillingResult) when the billing serviceis next running and setup is complete.
onBillingSetupFinished(BillingResult)
将再次被调用,并且在您的实现中,您将尝试再次恢复协程,但协程继续已经恢复,您将获得
IllegalStateException
.
BillingClientStateListener
类本身的接口(interface),在回调中我更新了
SharedFlow<Int>
与
BillingResult
来自
onBillingSetupFinished(BillingResult)
private val billingClientStatus = MutableSharedFlow<Int>(
replay = 1,
onBufferOverflow = BufferOverflow.DROP_OLDEST
)
override fun onBillingSetupFinished(result: BillingResult) {
billingClientStatus.tryEmit(result.responseCode)
}
override fun onBillingServiceDisconnected() {
billingClientStatus.tryEmit(BillingClient.BillingResponseCode.SERVICE_DISCONNECTED)
}
然后,如果计费客户端已连接,您可以收集流程以获取您的 SKU 价格或处理待处理的购买,或者如果未连接,则实现重试逻辑:
init {
billingClientStatus.tryEmit(BillingClient.BillingResponseCode.SERVICE_DISCONNECTED)
lifecycleOwner.lifecycleScope.launchWhenStarted {
billingClientStatus.collect {
when (it) {
BillingClient.BillingResponseCode.OK -> with (billingClient) {
updateSkuPrices()
handlePurchases()
}
else -> billingClient.startConnection(this)
}
}
}
}
如果您正在执行一些需要计费客户端连接的操作,您可以通过执行以下操作来等待它:
private suspend fun requireBillingClientSetup(): Boolean =
withTimeoutOrNull(TIMEOUT_MILLIS) {
billingClientStatus.first { it == BillingClient.BillingResponseCode.OK }
true
} ?: false
(请注意,我使用
SharedFlow<T>
而不是
StateFlow<T>
来表示
billingClientStatus
:原因是
StateFlow<T>
不支持发出连续相等的值)。
关于android - BillingClient.BillingClientStateListener.onBillingSetupFinished 被多次调用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61388646/
我尝试使用 Kotlin Coroutines 为 BillingClient v.2.2.0 编写一个包装器: package com.cantalk.photopose.billing impor
我有一个应用程序内购买功能,我认为遵循标准流程,但遇到了与按钮 setText() 更新相关的奇怪问题的挑战。这是我想做的: 启动结算连接 成功连接/onBillingSetupFinished()
我是一名优秀的程序员,十分优秀!