PAYATHON 2026

如何调试带有 in-app billing 功能的 released apk?

架构老王

结论

可以调试订阅购买流程,但不要将正式构建设为 debuggable=true 来模拟线上环境。建议按以下方式测试:

  1. 使用与 Play Console 中应用一致的 applicationId。
  2. 使用正式签名配置生成 APK/AAB。
  3. 将构建发布到内部测试、封闭测试或开放测试轨道。
  4. 将测试账号加入测试人员名单,再通过 Play Console 提供的测试链接,从 Google Play 安装应用。
  5. 使用 License Testing(许可测试)账号购买订阅。

出现 The item you requested is not available for purchase.,通常与代码调试权限无关。更常见的原因是 Google Play 无法根据当前应用、账号或地区找到可购买的订阅商品。

为什么不应修改正式包的 debuggable

Google Play Billing 会结合以下条件识别应用和商品:

  • 应用包名,也就是 applicationId
  • 应用签名和 Play 分发身份
  • 当前安装来源
  • 测试轨道及测试人员资格
  • 商品是否已启用
  • 订阅的 base plan 和 offer 是否可用
  • 测试账号所在国家或地区
  • Play Console 配置是否已经同步

如果在本地生成一个 debuggable=true 的 “release APK” 并直接安装,即使它使用了正式包名和正式签名,也不一定等同于 Google Play 从测试轨道分发的版本。Billing API 此时可能无法正确关联应用和订阅商品。

debuggable 只决定应用能否被调试,不会让 Google Play Billing 自动进入测试模式。

推荐的测试步骤

1. 保持 release 构建不可调试

android {
    buildTypes {
        release {
            debuggable false
            minifyEnabled true
            signingConfig signingConfigs.release
        }
    }
}

不要为了测试 Billing 而修改线上 release 类型。如果需要保留日志,可以单独增加一个测试构建类型:

android {
    buildTypes {
        release {
            debuggable false
            minifyEnabled true
            signingConfig signingConfigs.release
        }

        billingTest {
            initWith release
            debuggable false
            minifyEnabled false
            signingConfig signingConfigs.release
            matchingFallbacks = ["release"]
        }
    }
}

billingTest 必须使用与 Play Console 应用相同的 applicationId。如果添加了 applicationIdSuffix,Google Play 会将其视为另一个应用,原有订阅商品也无法匹配。

2. 通过测试轨道发布

将 APK 或 AAB 上传到 Play Console 的内部测试或其他测试轨道,并完成发布。订阅商品也需要已经创建,并处于可供测试的状态。

使用新版订阅模型时,还要确认:

  • subscription 已启用;
  • 至少有一个 active base plan;
  • base plan 覆盖测试账号所在地区;
  • 如果代码指定了 offer,测试账号符合该 offer 的资格要求。

3. 配置测试账号

测试 Google 账号需要同时满足以下条件:

  • 已加入对应测试轨道;
  • 已通过测试链接接受测试;
  • 已加入 Play Console 的 License Testing 配置;
  • 是设备上 Google Play 商店当前使用的账号;
  • 不要因为该账号是应用开发者的付款账号,就默认所有商品一定可见。

如果设备登录了多个 Google 账号,Google Play 实际用于购买的账号可能不是预期的账号。遇到商品不可用时,最好改用只登录测试账号的设备或测试用户环境重新验证。

4. 从 Google Play 安装测试版本

先卸载通过 Android Studio 或 adb install 安装的本地版本,再打开测试邀请链接,从 Google Play 安装对应测试轨道中的版本。

这样可以排除以下问题:

  • 本地安装包与 Play 分发包的签名不一致;
  • 设备上仍然保留旧版本;
  • 当前账号没有加入对应测试轨道;
  • 实际运行的不是刚发布的版本。

能否旁加载测试包,取决于 Play Billing Library 版本、许可测试配置和 Play 分发方式。要复现真实购买流程,应优先从 Play 测试轨道安装应用。

如何获得调试信息

调试 Billing 流程通常不需要将整个 release APK 设为可调试。可以记录 BillingResult 的响应码和调试信息:

billingClient.queryProductDetailsAsync(params) { billingResult, productDetailsList ->
    Log.d(
        "Billing",
        "responseCode=${billingResult.responseCode}, " +
            "debugMessage=${billingResult.debugMessage}, " +
            "products=${productDetailsList.size}"
    )

    productDetailsList.forEach { product ->
        Log.d(
            "Billing",
            "productId=${product.productId}, type=${product.productType}"
        )
    }
}

启动购买流程时,也要检查返回结果:

val billingResult = billingClient.launchBillingFlow(activity, billingFlowParams)

Log.d(
    "Billing",
    "launch responseCode=${billingResult.responseCode}, " +
        "debugMessage=${billingResult.debugMessage}"
)

购买回调中也要保留必要的日志:

private val purchasesUpdatedListener =
    PurchasesUpdatedListener { billingResult, purchases ->
        Log.d(
            "Billing",
            "purchase responseCode=${billingResult.responseCode}, " +
                "debugMessage=${billingResult.debugMessage}"
        )

        if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
            purchases.orEmpty().forEach(::handlePurchase)
        }
    }

不要只显示统一的 “购买失败” 提示,否则会丢失排查问题所需的信息。生产日志也不能记录 purchase token、账号标识或其他敏感数据。

如果必须调试 release 构建中与 Billing 无关的代码,可以采用以下方式:

  • 添加可控的诊断日志;
  • 使用 Crashlytics 等崩溃报告;
  • 查看 Play Console 的 pre-launch report;
  • 在本地构建一个使用相同混淆和优化配置的调试变体。

排查 ITEM_UNAVAILABLE

收到 “商品不可购买” 的返回结果时,可以按以下顺序检查:

  1. 查询所用的商品 ID 是否与 Play Console 完全一致,包括字母大小写。
  2. 查询订阅时是否使用了正确的商品类型:
QueryProductDetailsParams.Product.newBuilder()
    .setProductId("your_subscription_id")
    .setProductType(BillingClient.ProductType.SUBS)
    .build()
  1. queryProductDetailsAsync 是否确实返回了该商品。
  2. 是否从返回结果中选取了有效的 subscription offer token。
  3. subscription、base plan 和相关 offer 是否可用。
  4. base plan 是否覆盖测试账号所在地区。
  5. 测试账号是否接受了测试邀请。
  6. 应用是否从正确的 Play 测试轨道安装。
  7. 设备上的包名是否与 Play Console 应用一致。
  8. 新配置是否仍在同步。Play Console 中的发布和商品变更不一定会立即在所有设备上生效。

下面是使用 offer token 启动订阅购买的示例:

val offer = productDetails.subscriptionOfferDetails
    ?.firstOrNull()
    ?: error("No eligible subscription offer")

val productDetailsParams =
    BillingFlowParams.ProductDetailsParams.newBuilder()
        .setProductDetails(productDetails)
        .setOfferToken(offer.offerToken)
        .build()

val billingFlowParams = BillingFlowParams.newBuilder()
    .setProductDetailsParamsList(listOf(productDetailsParams))
    .build()

val result = billingClient.launchBillingFlow(activity, billingFlowParams)

不要写死某个账号缓存的 offer token。应从当前 ProductDetails 查询结果中选取有效的 offer,因为账号、地区或订阅状态不同,可用的 offer 也可能不同。

解决问题的重点不是将 release APK 改为 debuggable,而是确保测试包、签名、安装来源、测试账号和订阅配置共同构成一条完整的 Google Play 测试链路。

备注:内容仅供参考。