我试图为BillingClient v.2.2.0和Kotlin协同程序编写一个包装:
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)
我试图了解问题的原因,得到了ifBillingClientStateListener。onBillingSetupFinished
将被多次调用。可能存在异常非法状态异常:已恢复
。我一直想知道这是怎么可能的,因为我每次startConnection
调用都会创建新的侦听器?我无法在emulator或测试设备上重现此问题。有人能解释一下这里发生了什么,以及如何解决吗?
最后我在非法状态异常日志上做了一次尝试。。。
val result = suspendCoroutine<Boolean> { continuation ->
billingClient.startConnection(object : BillingClientStateListener {
override fun onBillingServiceDisconnected() {
WttjLogger.v("onBillingServiceDisconnected")
// Do not call continuation resume, as per documentation
// https://developer.android.com/reference/com/android/billingclient/api/BillingClientStateListener#onbillingservicedisconnected
}
override fun onBillingSetupFinished(billingResult: BillingResult) {
val responseCode = billingResult.responseCode
val debugMessage = billingResult.debugMessage
if (responseCode != BillingClient.BillingResponseCode.OK) {
WttjLogger.w("onBillingSetupFinished: $responseCode $debugMessage")
} else {
WttjLogger.v("onBillingSetupFinished: $responseCode $debugMessage")
}
try {
continuation.resume(responseCode == BillingClient.BillingResponseCode.OK)
} catch (e: IllegalStateException) {
WttjLogger.e(e)
}
}
})
}
我的设置有点不同(我用一个布尔值返回),但这是我能想到的最好的设置。与其说是真正的解释,不如说是一种变通方法:
private suspend fun start(): Boolean = suspendCoroutine {
billingClient.startConnection(object : BillingClientStateListener {
var resumed = false;
override fun onBillingSetupFinished(billingResult: BillingResult) {
if (!resumed) {
it.resume(billingResult.responseCode == BillingResponseCode.OK)
resumed = true
}
}
override fun onBillingServiceDisconnected() {
Toast.makeText(context, R.string.pay_error, Toast.LENGTH_SHORT).show()
Log.e(LOG_TAG, "Billing disconnected")
}
})
}
陪审团仍然不知道这是否是一个真正的长期解决方案。
一开始我也试着这么做,但原理不正确onBillingSetupFinished()
可能会被多次调用,这是出于设计考虑。一旦你调用BillingClient。startConnection(BillingClientStateListener)
通过回调,它会在内部存储回调,并在连接断开/恢复时再次调用它。您不应该在调用BillingClient时传入新对象。startConnection(BillingClientStateListener)
。
阅读onBillingServiceDisconnected()
上的文档:
调用以通知到计费服务的连接丢失。
注意:这不会删除计费服务连接本身-此服务绑定将保持活动状态,当计费服务下次运行且安装完成时,您将收到对onBillingSetupFinished(BillingResult)的调用。
这意味着当连接被删除并随后重新获得时,将再次调用onBillingSetupFinish(BillingResult)
,并且在您的实现中,您将尝试再次恢复协程,但是协程延续已经恢复,并且您将获得IllegalStateExcema
。
我最终所做的是在类本身中实现BillingClientStateListener
接口,并在回调中更新SharedFlow
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
我正在尝试在像这样: 然后在我的它呈现: 我正在将我的fetch记录到控制台中,它被调用了三次。流程是我在登录页面上(加载时挂载一次),我登录,它将我重定向到自己呈现的主页。此时,我在控制台中看到:
我有一个代码,在那里我处理多个线程。一个线程等待它所依赖的其他一些线程的执行。 线程的运行代码如下所示 很少有其他线程等待这个线程在相同的方法中完成执行,如图所示,通过调用join()来执行run()。 假设这个线程依赖于另外三个线程,它正在等待它们在thread.join()中完成执行。即nameList大小为3,但此线程也执行了3次操作。它在for循环的外部。 我用另一种方法启动这个线程,比如
我在我的应用程序中实现GCM。我已经按照GCM教程中给出的所有步骤从developer.android.com 我能够成功地从GCM获得注册ID,并且我正在将此ID传递给我的应用程序服务器。因此,成功地执行了注册步骤。 现在,当我的应用服务器向我的设备发送PUSH消息时,服务器将获得SUCCESS=8 FAILURE=0等消息,即:服务器成功发送消息,但OnMessage被调用8次取决于成功值。
问题内容: 我知道关于“多次调用getView”的问题很少,但是我的问题没什么不同。 我有一个带有自定义行的自定义listView(使用row_layout.xml)。通常效果很好。最初,我多次调用getView时遇到问题,并使用在stackoverflow中看到的一种方法解决了该问题。(使用’usedPositions’数组)。 现在,我在日志中看到这种情况:getView pos 0,getV
我有一个,它有一个字段,如下所示 我还有几个,它们是继承的的部分。 在其中一个如果这些片断中,我将在片断的方法中进行另一个调用,如下所示 当第一次创建片段时,只调用一次,但片段会转到后台,然后返回,它会被多次调用,这会引起问题。 如有任何帮助,我们将不胜感激。
问题内容: 关于的简单代码。是SessionScoped Bean,是RequestScoped Bean 内 我的问题是被叫很多。会告诉我们该方法在什么阶段被调用。首次加载页面时,请在阶段6-进行约 5次 呼叫。该页面上有一个,因此我在其中键入一些内容,然后单击(命令按钮)。然后在阶段1-> 4期间再呼叫 12次 。每个阶段调用此方法 3-4次 。然后,此属性的get 方法的setter方法(即