BillingClient queryProductDetailsAsync频繁返回错误码2、6、12排查求助
我在游戏中用BillingClient实现内购功能,日志显示大量用户卡在queryProductDetailsAsync方法,无法启动购买流程,返回的错误码包括:
SERVICE_UNAVAILABLE = 2ERROR = 6NETWORK_ERROR = 12
我是在startConnection调用成功后才执行queryProductDetailsAsync的,实在搞不懂为什么会出现这类网络/连接错误。我自己和朋友测试都正常,还有部分用户能完成购买,所以排除配置问题,所有失败日志都来自用户设备。
使用的依赖版本:implementation 'com.android.billingclient:billing:7.0.0'
以下是我的BillingHelper类代码:
import android.app.Activity import android.content.Context import android.os.Handler import android.os.Looper import com.android.billingclient.api.* import com.android.billingclient.api.BillingFlowParams.ProductDetailsParams import java.lang.ref.WeakReference class BillingHelper { // region Enum enum class PurchaseStatus { PURCHASED, PENDING } // endregion // region Companion companion object { private const val TAG = "BillingHelper" } // endregion // region Listener interface BillingHelperListener { fun purchaseStatusUpdated(itemId: String, status: PurchaseStatus) } // endregion // region Properties private var billingClient: BillingClient? = null private var context: WeakReference<Context>? = null private var listener: BillingHelperListener? = null private val handler = Handler(Looper.getMainLooper()) // endregion // region Init fun init(context: Context) { this.context = WeakReference(context) } fun setListener(listener: BillingHelperListener) { this.listener = listener } // endregion // region Purchase flow fun launchPurchase(activity: Activity, itemId: String, block: (purchaseStarted: Boolean) -> Unit) { KLog.i(TAG, "launchPurchase - $itemId") connectToBillingClient { connected -> KLog.i(TAG, "launchPurchase - $itemId. connected? - $connected") if (connected) { prepareSkuForPurchase(activity, itemId, block) } else { handler.post { KLog.i(TAG, "launchPurchase - $itemId. invoking false") block.invoke(false) } } } } private fun connectToBillingClient(block: (connected: Boolean) -> Unit) { KLog.i(TAG, "connectToBillingClient") getBillingClient()?.let { if (it.isReady) { KLog.i(TAG, "connectToBillingClient - already connected") block.invoke(true) return@let } it.startConnection(object : BillingClientStateListener { override fun onBillingSetupFinished(billingResult: BillingResult) { KLog.i(TAG, "connectToBillingClient::onBillingSetupFinished") if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) { KLog.i(TAG, "connectToBillingClient::onBillingSetupFinished - connected") block.invoke(true) return } KLog.i(TAG, "connectToBillingClient::onBillingSetupFinished - invoking false") block.invoke(false) } override fun onBillingServiceDisconnected() { KLog.i(TAG, "connectToBillingClient::onBillingServiceDisconnected - invoking false") block.invoke(false) } }) } } private fun prepareSkuForPurchase(activity: Activity, itemID: String, block: (purchaseStarted: Boolean) -> Unit) { KLog.i(TAG, "prepareSkuForPurchase - $itemID") val skuList = ArrayList<String>() skuList.add(itemID) val productList = listOf( QueryProductDetailsParams.Product.newBuilder() .setProductId(itemID) .setProductType(BillingClient.ProductType.INAPP) .build() ) val params = QueryProductDetailsParams.newBuilder() params.setProductList(productList) getBillingClient()?.queryProductDetailsAsync(params.build()) { billingResult, skuDetailsList -> KLog.i(TAG, "prepareSkuForPurchase::queryProductDetailsAsync - billing result - $billingResult") /** * PROBLEM IS HERE - ACCORDING TO THE LOG - IT SAYS: * 1 - billing result - Response Code: ERROR, Debug Message: An internal error occurred. * 2 - billing result - Response Code: NETWORK_ERROR, Debug Message: An internal error occurred. * 3 - billing result - Response Code: SERVICE_UNAVAILABLE, Debug Message: Timeout communicating with service. */ var purchaseStarted = false if (billingResult.responseCode == BillingClient.BillingResponseCode.OK && skuDetailsList.isNotEmpty()) { launchSkuPurchase(activity, skuDetailsList[0]) purchaseStarted = true } handler.post { KLog.i(TAG, "prepareSkuForPurchase::queryProductDetailsAsync - invoking purchase started? - $purchaseStarted") block.invoke(purchaseStarted) } } } private fun launchSkuPurchase(activity: Activity, productDetails: ProductDetails) { KLog.i(TAG, "launchSkuPurchase - $productDetails") val params = ProductDetailsParams.newBuilder() params.setProductDetails(productDetails) val billingFlowParams = BillingFlowParams.newBuilder().setProductDetailsParamsList(listOf(params.build())).build() getBillingClient()?.launchBillingFlow(activity, billingFlowParams) } private fun handleSuccessPurchase(purchase: Purchase) { KLog.i(TAG, "handleSuccessPurchase - $purchase") KLog.i(TAG, "handleSuccessPurchase. state = - ${purchase.purchaseState}") if (purchase.purchaseState == Purchase.PurchaseState.PURCHASED) { if (!purchase.isAcknowledged) { acknowledgePurchase(purchase) } else { handler.post { listener?.purchaseStatusUpdated(purchase.products.firstOrNull() ?: "", PurchaseStatus.PURCHASED) } } } else if (purchase.purchaseState == Purchase.PurchaseState.PENDING) { handler.post { listener?.purchaseStatusUpdated(purchase.products.firstOrNull() ?: "", PurchaseStatus.PENDING) } } } private fun acknowledgePurchase(purchase: Purchase) { KLog.i(TAG, "acknowledgePurchase - $purchase") val acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder().setPurchaseToken(purchase.purchaseToken).build() getBillingClient()?.acknowledgePurchase(acknowledgePurchaseParams) { result -> KLog.i(TAG, ":acknowledgePurchase::status - ${result.responseCode}") if (result.responseCode == BillingClient.BillingResponseCode.OK) { handler.post { listener?.purchaseStatusUpdated(purchase.products.firstOrNull() ?: "", PurchaseStatus.PURCHASED) } } } } private val purchaseUpdateListener = PurchasesUpdatedListener { billingResult, purchases -> KLog.i(TAG, "purchaseUpdateListener::onPurchasesUpdated : $billingResult") if (billingResult.responseCode == BillingClient.BillingResponseCode.OK && !purchases.isNullOrEmpty()) { val purchase = purchases.first() handleSuccessPurchase(purchase) } } // endregion // region Billing client private fun getBillingClient(): BillingClient? { val context = this.context?.get() ?: return let { KLog.i(TAG, "getBillingClient - context is null") null } var billingClient = this.billingClient if (billingClient == null) { KLog.i(TAG, "getBillingClient - creating new billing client") billingClient = BillingClient.newBuilder(context) .setListener(purchaseUpdateListener) .enablePendingPurchases() .build() this.billingClient = billingClient } else { KLog.i(TAG, "getBillingClient - using existing billing client") } return billingClient } // endregion }
请问我忽略了什么?
1. 未处理服务断开后的自动重连
你的onBillingServiceDisconnected直接返回连接失败,但Google Billing服务可能只是临时断开,此时应该触发自动重连,而不是直接放弃。
修改onBillingServiceDisconnected方法:
override fun onBillingServiceDisconnected() { KLog.i(TAG, "connectToBillingClient::onBillingServiceDisconnected - attempting reconnect") // 延迟3秒后重试,避免频繁请求 handler.postDelayed({ connectToBillingClient(block) }, 3000) }
2. 查询前未再次校验BillingClient状态
即使startConnection成功,后续调用时BillingClient也可能因为服务断开不再处于isReady状态。在调用queryProductDetailsAsync前必须再次检查:
修改prepareSkuForPurchase的开头:
private fun prepareSkuForPurchase(activity: Activity, itemID: String, block: (purchaseStarted: Boolean) -> Unit) { KLog.i(TAG, "prepareSkuForPurchase - $itemID") val billingClient = getBillingClient() if (billingClient == null || !billingClient.isReady) { KLog.i(TAG, "prepareSkuForPurchase - billing client not ready") handler.post { block.invoke(false) } return } // 剩余原有代码... }
3. 缺少可恢复错误的重试机制
针对NETWORK_ERROR、SERVICE_UNAVAILABLE这类临时错误,应该提供有限次数的重试,而不是直接终止流程。
给prepareSkuForPurchase添加重试逻辑:
// 新增重试次数参数,默认0次 private fun prepareSkuForPurchase(activity: Activity, itemID: String, block: (purchaseStarted: Boolean) -> Unit, retryCount: Int = 0) { // ... 原有校验代码 ... billingClient.queryProductDetailsAsync(params.build()) { billingResult, skuDetailsList -> KLog.i(TAG, "prepareSkuForPurchase::queryProductDetailsAsync - billing result - $billingResult") val maxRetries = 2 // 设置最大重试次数 when (billingResult.responseCode) { BillingClient.BillingResponseCode.OK -> { val purchaseStarted = skuDetailsList.isNotEmpty().also { if (it) launchSkuPurchase(activity, skuDetailsList[0]) } handler.post { block.invoke(purchaseStarted) } } BillingClient.BillingResponseCode.SERVICE_UNAVAILABLE, BillingClient.BillingResponseCode.NETWORK_ERROR -> { if (retryCount < maxRetries) { KLog.i(TAG, "prepareSkuForPurchase - retrying query, count: ${retryCount + 1}") handler.postDelayed({ prepareSkuForPurchase(activity, itemID, block, retryCount + 1) }, 2000) } else { handler.post { block.invoke(false) } } } else -> { handler.post { block.invoke(false) } } } } }
4. 依赖版本过低存在已知bug
你使用的7.0.0版本存在不少稳定性问题,建议升级到最新稳定版(如8.2.0),Google后续版本修复了大量连接和查询相关的bug。
修改依赖:
implementation 'com.android.billingclient:billing:8.2.0'
5. 未考虑用户设备的特殊环境
部分用户可能遇到:
- 网络信号弱、公共WiFi被拦截
- Google Play服务版本过低
- 系统版本兼容性问题
可以提前添加基础检查,提升用户体验:
// 新增网络检查方法 private fun isNetworkAvailable(context: Context): Boolean { val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager val network = connectivityManager.activeNetwork ?: return false val capabilities = connectivityManager.getNetworkCapabilities(network) ?: return false return capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) || capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) } // 在launchPurchase中调用检查 fun launchPurchase(activity: Activity, itemId: String, block: (purchaseStarted: Boolean) -> Unit) { KLog.i(TAG, "launchPurchase - $itemId") if (!isNetworkAvailable(activity)) { handler.post { block.invoke(false) // 可在此处提示用户检查网络 } return } // 原有连接逻辑... }
内容的提问来源于stack exchange,提问作者dor506

