如何调试带有 in-app billing 功能的 released apk?
结论
可以调试订阅购买流程,但不要将正式构建设为 debuggable=true 来模拟线上环境。建议按以下方式测试:
- 使用与 Play Console 中应用一致的
applicationId。 - 使用正式签名配置生成 APK/AAB。
- 将构建发布到内部测试、封闭测试或开放测试轨道。
- 将测试账号加入测试人员名单,再通过 Play Console 提供的测试链接,从 Google Play 安装应用。
- 使用 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
收到 “商品不可购买” 的返回结果时,可以按以下顺序检查:
- 查询所用的商品 ID 是否与 Play Console 完全一致,包括字母大小写。
- 查询订阅时是否使用了正确的商品类型:
QueryProductDetailsParams.Product.newBuilder()
.setProductId("your_subscription_id")
.setProductType(BillingClient.ProductType.SUBS)
.build()
queryProductDetailsAsync是否确实返回了该商品。- 是否从返回结果中选取了有效的 subscription offer token。
- subscription、base plan 和相关 offer 是否可用。
- base plan 是否覆盖测试账号所在地区。
- 测试账号是否接受了测试邀请。
- 应用是否从正确的 Play 测试轨道安装。
- 设备上的包名是否与 Play Console 应用一致。
- 新配置是否仍在同步。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 测试链路。
备注:内容仅供参考。
