Android 应用内购买实战指南:从零实现到最佳实践

在当今的移动应用经济中,除了广告变现,通过向用户直接销售数字商品(如移除广告、解锁高级功能或购买虚拟货币)是获取收入的核心方式之一。然而,直接处理支付交易的复杂性(如合规性、安全性)是令人望而生畏的。

好消息是,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。

!image

图示:在 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 即可。祝你编码愉快,收益满满!

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。如需转载,请注明文章出处豆丁博客和来源网址。https://shluqu.cn/51813.html
点赞
0.00 平均评分 (0% 分数) - 0