You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

BillingClient queryProductDetailsAsync频繁返回错误码2、6、12排查求助

问题描述

我在游戏中用BillingClient实现内购功能,日志显示大量用户卡在queryProductDetailsAsync方法,无法启动购买流程,返回的错误码包括:

  • SERVICE_UNAVAILABLE = 2
  • ERROR = 6
  • NETWORK_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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 05:32:02