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

调用billingClient.launchBillingFlow前设置的成员变量在回调中丢失

问题诊断与解决方案

核心问题根源

你遇到的问题本质是每次调用purchase()方法时都会创建全新的GooglePlayPay实例:

  • 调用purchase()时,新实例设置了purchaseCallback并启动支付流程,但方法执行完后,这个实例因为没有强引用持有,很快被GC回收
  • 支付流程需要用户交互,耗时远长于getProduct()的API调用,等回调触发时,原来设置了回调的GooglePlayPay实例已经被销毁,导致purchaseCallback变为null

而getProduct()能正常工作,是因为它的异步流程完成速度快,实例还没被回收就触发了回调。


解决方案:复用GooglePlayPay实例

1. 让GooglePlayPayJSInterface持有唯一实例

不再每次调用都创建新的GooglePlayPay对象,而是在JSInterface中保留一个全局实例:

class GooglePlayPayJSInterface(val context: WebAppActivity) {
    // 持有唯一的GooglePlayPay实例
    private val googlePlayPay = GooglePlayPay(context)

    @JavascriptInterface 
    fun getProduct(str:String) {
        googlePlayPay.getProduct(str, ::getProductCallback)
    }

    fun getProductCallback(details: ProductDetails){
        context.postToJs("""{"data":{"productId":"${details.productId}", "name": "${details.name}", "price":"${details.oneTimePurchaseOfferDetails?.formattedPrice?:"n/a"}"}}""")
    }

    @JavascriptInterface 
    fun purchase(str:String) {
        googlePlayPay.purchase(str, ::purchaseCallback)
    }

    fun purchaseCallback(success:Boolean){
        val result = if(success) """{"data":{"result":"success"}}""" else """{"errcode":"Purchase failed"}"""
        context.postToJs(result)
    }

    // ...其他代码
}

2. 优化GooglePlayPay的生命周期管理

确保BillingClient和Activity生命周期绑定,避免内存泄漏或回调异常:

class GooglePlayPay(val context: NativeAppActivity) {
    private lateinit var billingClient: BillingClient
    var purchaseCallback: ((Boolean)->Unit)?=null
    private val purchasesUpdatedListener = MyPurchasesUpdatedListener()

    init {
        // 初始化BillingClient并绑定监听
        billingClient = BillingClient.newBuilder(context)
            .setListener(purchasesUpdatedListener)
            .enablePendingPurchases()
            .build()
        
        // 连接Google Play计费服务
        billingClient.startConnection(object : BillingClientStateListener {
            override fun onBillingSetupFinished(billingResult: BillingResult) {
                if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
                    // 初始化成功,可正常发起支付
                }
            }

            override fun onBillingServiceDisconnected() {
                // 断开连接时自动重试
                billingClient.startConnection(this)
            }
        })
    }

    inner class MyPurchasesUpdatedListener : PurchasesUpdatedListener {
        override fun onPurchasesUpdated(billingResult: BillingResult, purchases: List<Purchase>?) {
            Log.d("fp_purchUpdatedListener","PurchaseCallback: ${purchaseCallback.toString()}")
            when(billingResult.responseCode) {
                BillingClient.BillingResponseCode.OK -> {
                    purchases?.forEach { handlePurchase(it) }
                }
                BillingClient.BillingResponseCode.USER_CANCELED -> {
                    // 用户取消支付,回调失败
                    purchaseCallback?.invoke(false)
                }
                else -> {
                    // 其他错误场景,回调失败
                    purchaseCallback?.invoke(false)
                }
            }
            // 回调后清空引用,避免重复触发
            purchaseCallback = null
        }
    }

    fun handlePurchase(purchase: Purchase){
        // 这里处理订单验证、商品交付逻辑
        Log.d("fp_handlePurchase","PurchaseCallback: ${purchaseCallback.toString()}")
        // 处理完成后回调成功
        purchaseCallback?.invoke(true)
        // 清空回调引用
        purchaseCallback = null
    }

    fun purchaseProduct(productDetails: ProductDetails, cb:(Boolean)->Unit) {
        purchaseCallback = cb
        Log.d("fp_purchaseProduct","2.PurchaseCallback: ${purchaseCallback.toString()}")
        
        // 构建支付参数
        val billingFlowParams = BillingFlowParams.newBuilder()
            .setProductDetailsParamsList(
                listOf(
                    BillingFlowParams.ProductDetailsParams.newBuilder()
                        .setProductDetails(productDetails)
                        .build()
                )
            )
            .build()
        
        // 确保BillingClient已就绪再启动支付流程
        if (billingClient.isReady) {
            billingClient.launchBillingFlow(context, billingFlowParams)
        } else {
            // 未连接成功时直接回调失败
            cb.invoke(false)
            purchaseCallback = null
        }
    }

    // 配合Activity生命周期销毁资源
    fun onDestroy() {
        if (::billingClient.isInitialized) {
            billingClient.endConnection()
        }
    }
}

3. 在Activity销毁时清理资源

在WebAppActivity的生命周期方法中,调用GooglePlayPay的销毁方法,避免内存泄漏:

class WebAppActivity : AppCompatActivity() {
    private lateinit var jsInterface: GooglePlayPayJSInterface

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // ...初始化WebView等逻辑
        jsInterface = GooglePlayPayJSInterface(this)
        webView.addJavascriptInterface(jsInterface, "GooglePlayPay")
    }

    override fun onDestroy() {
        jsInterface.googlePlayPay.onDestroy()
        super.onDestroy()
    }
}

内容的提问来源于stack exchange,提问作者fpierrat

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 11:35:32