在当今的移动应用经济中,除了广告变现,通过向用户直接销售数字商品(如移除广告、解锁高级功能或购买虚拟货币)是获取收入的核心方式之一。然而,直接处理支付交易的复杂性(如合规性、安全性)是令人望而生畏的。
好消息是,Google Play 提供了一套强大且标准化的系统来处理这一切。在这篇文章中,我们将深入探讨如何利用 Google Play 结算库在 Android 应用中实现应用内购买(IAP)。我们将构建一个完整的应用示例,不仅为了演示代码如何编写,更为了帮助你理解整个支付流程的生命周期。我们将涵盖从项目配置、连接 Google Play、查询商品信息到处理购买回调的每一个关键环节。
闲话少叙,让我们准备好开发环境,开始这趟探索之旅吧。我们将创建一个基于 Empty Activity 的项目,并一步步完善它。
为什么需要结算库?
在开始编写代码之前,我们要明白为什么不能自己“造轮子”。Google Play 结算库是 Google 官方提供的库,它处理了与 Google Play 服务通信的复杂细节。利用这个库,我们可以安全地请求购买类型、处理购买流程,并确保交易的安全性(防止恶意破解)。它是连接你的应用与 Google Play 后台的桥梁。
分步实现指南
#### 步骤 #1:添加项目依赖
首先,我们需要在你的应用模块的 build.gradle 文件中引入结算库。这个库包含了我们所需的所有类和接口。
请打开你的 INLINECODEbdd53e2c(Module level)文件,在 INLINECODE48df4094 闭包中添加以下依赖项。请注意,版本号可能会迭代,但这里我们以经典稳定的 2.0.1 版本为例(当然,你也总是可以查看最新的稳定版)。
// build.gradle (Module: app)
dependencies {
...
// Google Play 结算库依赖
implementation ‘com.android.billingclient:billing:2.0.1‘
...
}
实用见解: 在添加完依赖后,别忘了点击右上角的 "Sync Now",让 Gradle 下载必要的库文件。这一步是所有后续工作的基础。
#### 步骤 #2:配置权限清单
为了让你的应用具备调用 Google Play 结算服务的权限,你必须在 INLINECODEa3c56893 文件中声明 INLINECODE724eccbe 权限。如果没有这个权限,系统将拒绝你的应用发起任何购买请求。
打开 INLINECODE16bd2b64,在 INLINECODE5b7c5974 标签之前添加以下代码行:
现在,基础配置工作已经完成。接下来,我们将进入核心代码的实现部分。
#### 步骤 #3:初始化结算客户端
在代码层面,一切操作的起点是 BillingClient。它是库与用户应用程序代码之间的主要通信接口。我们还需要定义我们要出售的产品 ID 列表。这些 ID 必须与你在 Google Play Console 后台配置的 ID 完全一致。
让我们先在 MainActivity 中定义必要的变量:
// MainActivity.kt
import com.android.billingclient.api.BillingClient
import com.android.billingclient.api.PurchasesUpdatedListener
class MainActivity : AppCompatActivity() {
// 声明 BillingClient 变量,这是与 Google Play 交互的核心对象
private lateinit var billingClient: BillingClient
// 定义我们要出售的产品 ID 列表(SKU:Stock Keeping Unit)
// 这些字符串必须与 Google Play Console 中的 ID 完全匹配
private val skuList = listOf("premium_subscription", "remove_ads_product")
// 其他代码...
}
深入理解: INLINECODE36dc2b6f 是一个 heavyweight 对象,通常我们将其设计为单例或者在 Activity 的生命周期内复用。INLINECODE2a6dae39 则是我们想要查询的商品目录。在实际开发中,这些列表可能来自服务器或者配置文件。
#### 步骤 #4:建立连接与监听器
在能够查询商品或发起购买之前,我们必须先与 Google Play 服务建立连接。这是一个异步过程,因为我们需要等待设备与服务器握手完成。
为了让 INLINECODE2d7cfa75 能够处理购买更新的结果,我们需要让它实现 INLINECODE520dece9 接口。然后,在 onCreate 中初始化并启动连接。
class MainActivity : AppCompatActivity(), PurchasesUpdatedListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 初始化并连接结算服务
setupBillingClient()
}
private fun setupBillingClient() {
// 1. 创建 BillingClient 实例
billingClient = BillingClient.newBuilder(this)
.enablePendingPurchases() // 允许处理待定交易(重要:防止交易丢失)
.setListener(this) // 设置购买结果监听器
.build()
// 2. 启动连接
billingClient.startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(billingResult: BillingResult) {
// 检查响应码是否为 OK
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
// 连接成功!现在我们可以查询商品或恢复购买了
Log.d("Billing", "成功连接到 Google Play")
// 可以在这里调用查询商品的方法
}
}
override fun onBillingServiceDisconnected() {
// 连接断开。在实践中,通常需要实现重试机制
// 比如使用指数退避算法来重新连接
Log.d("Billing", "Google Play 结算服务连接断开")
}
})
}
// 实现 PurchasesUpdatedListener 接口的方法
override fun onPurchasesUpdated(billingResult: BillingResult, purchases: MutableList?) {
// 这里我们将处理购买回调(在后续步骤中详细实现)
}
}
常见错误提示: 很多开发者容易忘记 enablePendingPurchases()。如果不加这个,当用户购买被中断(例如网络波动)时,你的应用可能无法恢复交易,导致用户付了钱却没收到商品。
#### 步骤 #5:在 Google Play Console 中配置商品
写代码之前,我们必须先在 Google Play Console 后台“进货”。代码是无法凭空创造商品的。
- 登录到你的 Google Play Console。
- 选择你的应用(如果还没有,需要先创建一个应用)。
- 在左侧菜单中找到 “变现” -> “商品”。
- 点击 “创建商品”。
你需要填写以下关键信息:
- ID: 这是代码中引用的唯一标识符。例如:
gfg_premium_course。请务必小心,一旦创建通常无法修改。 - 名称: 用户在购买弹窗中看到的名称。
- 描述: 商品功能的详细说明。
- 价格: 设置价格 tier。
图示:在 Play 开发者控制台中创建新应用及配置商品页面
重要提示: 修改商品配置通常需要几分钟甚至几小时才能在 Google Play 服务器上生效。如果你在代码中查询不到刚创建的商品,不要惊慌,喝杯咖啡稍后再试。
#### 步骤 #6:异步查询商品详情
连接成功后,我们不能硬编码价格,因为价格可能因国家而异。我们需要通过 SKU 向 Google Play 查询最新的价格和商品详情。
以下是一个完整的查询方法示例:
/**
* 查询应用内商品的详情
* 这是一个异步操作
*/
private fun querySkuDetails() {
// 如果连接未就绪,直接返回
if (!billingClient.isReady) {
Log.e("Billing", "BillingClient 未就绪")
return
}
// 构建查询参数
val params = SkuDetailsParams.newBuilder()
.setSkusList(skuList) // 传入我们在步骤3定义的ID列表
.setType(BillingClient.SkuType.INAPP) // 类型:INAPP (一次性消耗品) 或 SUBS (订阅)
.build()
// 发起异步查询
billingClient.querySkuDetailsAsync(params) { billingResult, skuDetailsList ->
// 处理查询结果
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK && skuDetailsList != null) {
// 遍历返回的商品列表
for (skuDetails in skuDetailsList) {
// skuDetails 包含了价格、标题、描述等信息
val sku = skuDetails.sku
val price = skuDetails.price
Log.d("Billing", "商品: $sku, 价格: $price")
// 在这里,你可以更新 UI,比如更新按钮上显示的价格
if ("premium_subscription" == sku) {
updateBuyButtonPrice(price)
}
}
} else {
Log.e("Billing", "查询商品失败: ${billingResult.debugMessage}")
}
}
}
// 一个模拟的 UI 更新方法
private fun updateBuyButtonPrice(price: String) {
// 假设我们有一个按钮
// buyButton.text = "以 $price 购买"
}
代码原理深度解析: INLINECODE4e825bca 会返回一个 INLINECODEfee524c1 对象的列表。这个对象非常重要,因为后续发起购买流程时,必须传入这个包含最新价格信息的对象,而不能仅仅传入商品 ID 字符串。这是为了确保用户看到的价格与 Google Play 扣款的价格完全一致。
#### 步骤 #7:发起购买流程
这是用户交互的核心环节。当用户点击“购买”按钮时,我们将启动 Google Play 的购买界面。
为了演示,我们假设用户点击按钮后,我们从步骤 6 获得的列表中找到对应的 SkuDetails,并启动流程。
/**
* 发起购买请求
* @param skuDetails 商品详情对象,必须从步骤6的查询结果中获取
*/
private fun launchBillingFlow(skuDetails: SkuDetails) {
// 构建购买流程参数
val flowParams = BillingFlowParams.newBuilder()
.setSkuDetails(skuDetails) // 必须传入有效的 SkuDetails 对象
.build()
// 启动购买流程
// 这会弹出 Google Play 的支付对话框
val responseCode = billingClient.launchBillingFlow(this, flowParams)
// launchBillingFlow 会立即返回一个 responseCode
// 注意:这并不代表购买成功,只是代表“启动操作”是否成功
when (responseCode) {
BillingClient.BillingResponseCode.OK -> {
// 对话框已成功显示,等待用户操作
}
else -> {
// 处理错误,例如网络不可用
Log.e("Billing", "无法启动购买流程: $responseCode")
}
}
}
在 UI 层(比如按钮点击事件中),我们可以这样调用:
// 假设我们在查询回调中缓存了 skuDetails 对象
// 这里为了演示简化了逻辑
val skuDetail = findSkuDetails("premium_subscription")
buyCourseBtn.setOnClickListener {
if (billingClient.isReady && skuDetail != null) {
launchBillingFlow(skuDetail)
} else {
Toast.makeText(this, "正在连接服务或商品未就绪...", Toast.LENGTH_SHORT).show()
}
}
#### 步骤 #8:处理购买结果
还记得我们在步骤 4 中实现的 INLINECODEfcba3d20 接口吗?当用户在 Google Play 弹窗中完成支付(或取消)后,结果会回调到 INLINECODE3053922a 方法。这是最关键的一步,直接关系到用户的利益。
override fun onPurchasesUpdated(billingResult: BillingResult, purchases: MutableList?) {
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK && purchases != null) {
// 购买成功!
for (purchase in purchases) {
handlePurchase(purchase)
}
} else if (billingResult.responseCode == BillingClient.BillingResponseCode.USER_CANCELED) {
// 用户取消了购买
Log.i("Billing", "用户取消购买")
} else {
// 处理其他错误
Log.e("Billing", "购买失败: ${billingResult.debugMessage}")
}
}
/**
* 处理成功的购买对象
*/
private fun handlePurchase(purchase: Purchase) {
// 1. 验证购买状态
// purchase.purchaseState == 0 表示已购买
if (purchase.purchaseState == Purchase.PurchaseState.PURCHASED) {
// 2. 如果商品是消耗品(如游戏币),则向 Google Play 确认交易,否则下次无法再次购买
// 如果是订阅或非消耗品(如去广告),则不需要立即 consume,但通常需要 acknowledge
if (!purchase.isAcknowledged) {
val acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder()
.setPurchaseToken(purchase.purchaseToken)
.build()
billingClient.acknowledgePurchase(acknowledgePurchaseParams) { billingResult ->
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
// 3. 发放商品:解锁功能、更新数据库、下发虚拟货币等
Log.d("Billing", "购买已确认并发放商品: ${purchase.sku}")
grantPremiumAccess()
}
}
} else {
// 已经确认过,直接恢复权益(用于应用重启后恢复购买状态)
grantPremiumAccess()
}
}
}
private fun grantPremiumAccess() {
// TODO: 更新本地状态或请求后端服务器验证
Toast.makeText(this, "恭喜!您已成为高级用户。", Toast.LENGTH_LONG).show()
}
最佳实践警告: 不要只信任客户端的结果。在实际的生产环境中,必须使用安全的服务器来验证 purchaseToken。如果不做服务器验证,黑客可以通过 Root 手机或修改 APK 来伪造购买成功的回调。
总结与后续步骤
恭喜你!通过上述步骤,我们已经成功实现了一个完整的 Android 应用内购买流程。让我们回顾一下关键点:
- 依赖与权限: 基础设施搭建。
- 连接与初始化:
BillingClient的建立与生命周期管理。 - 后台配置: Google Play Console 是数据的单一真实来源。
- 查询与展示: 动态获取价格并向用户展示。
- 处理与确认: 处理回调并向 Google Play 确认交易,防止退款欺诈。
给开发者的最后建议:
- 测试是关键: 尽量使用真实的测试账号和 Google Play 测试轨道进行测试。不要依赖
android.test.purchased这种过时的测试 ID。 - 处理异常: 网络断开、应用崩溃重启。利用 INLINECODEe7cecd92 的 INLINECODE86e3608f 在应用启动时恢复用户的购买状态。
- 安全性: 始终在服务器端验证签名。
希望这篇文章能帮助你顺利地在应用中实现变现功能。虽然细节繁多,但一旦搭建好这套框架,后续只需添加新的商品 ID 即可。祝你编码愉快,收益满满!