56、Android 应用内计费全解析与实战教程

Android应用内计费全解析与实战

Android 应用内计费全解析与实战教程

1. Android 应用内计费概述

应用内购买为 Android 应用提供了通过向用户销售虚拟产品和订阅来创收的途径。使用 Google Play 应用内计费库可以为应用添加应用内购买支持,主要步骤包括创建和初始化计费客户端,然后调用其上的方法执行诸如购买、列出可用产品以及消费现有购买等任务。

2. 连接到 Google Play 计费库

在成功创建计费客户端后,需要初始化与 Google Play 计费库的连接。具体操作如下:
- 调用计费客户端实例的 startConnection() 方法来建立连接。
- 由于连接是异步执行的,因此需要实现 BillingClientStateListener 以接收指示连接是否成功的回调。
- 还应添加代码来重写 onBillingServiceDisconnected() 方法,当与计费库的连接丢失时会调用此方法,可用于向用户报告问题并重新尝试连接。

示例代码如下:

private BillingClient billingClient = BillingClient.newBuilder(context)
    .setListener(purchasesUpdatedListener)
    .enablePendingPurchases()
    .build();

billingClient.startConnection(new BillingClientStateListener() {
    @Override
    public void onBillingSetupFinished(@NonNull BillingResult billingResult) {
        if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
            // 连接成功
        } else {
            // 连接失败
        }
    }
    @Override
    public void onBillingServiceDisconnected() {
        // 现有连接丢失
    }
});
3. 查询可用产品

当计费环境初始化并准备就绪后,下一步是请求可供购买的产品或订阅的详细信息。操作步骤如下:
- 创建一个 QueryProductDetailsParams 实例,并配置产品 ID 和类型( ProductType.SUBS 用于订阅, ProductType.INAPP 用于托管产品)。
- 将该实例传递给计费客户端的 queryProductDetailsAsync() 方法。

示例代码如下:

QueryProductDetailsParams queryProductDetailsParams =
    QueryProductDetailsParams.newBuilder()
        .setProductList(
            ImmutableList.of(
                QueryProductDetailsParams.Product.newBuilder()
                    .setProductId("one_button_click")
                    .setProductType(BillingClient.ProductType.INAPP)
                    .build()))
        .build();

billingClient.queryProductDetailsAsync(queryProductDetailsParams,
    new ProductDetailsResponseListener() {
        public void onProductDetailsResponse(
                @NonNull BillingResult billingResult,
                @NonNull List<ProductDetails> productDetailsList) {
            if (!productDetailsList.isEmpty()) {
                // 处理匹配的产品列表
            } else {
                // 未找到匹配的产品
            }
        }
    }
);
4. 启动购买流程

当用户查询并选择要购买的产品或订阅后,就可以启动购买流程。具体操作是调用计费客户端的 launchBillingFlow() 方法,并传入当前活动和一个配置了购买项目的 ProductDetail 对象的 BillingFlowParams 实例。

示例代码如下:

BillingFlowParams billingFlowParams =
    BillingFlowParams.newBuilder()
        .setProductDetailsParamsList(
            ImmutableList.of(
                BillingFlowParams.ProductDetailsParams.newBuilder()
                    .setProductDetails(productDetails)
                    .build()
            )
        )
        .build();

billingClient.launchBillingFlow(this, billingFlowParams);
5. 完成购买

购买成功后, PurchasesUpdatedListener 处理程序将接收到一个包含每个项目的 Purchase 对象的列表。可以通过调用 Purchase 实例的 getPurchaseState() 方法来验证项目是否已购买。

示例代码如下:

if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) {
    // 购买完成
} else if (purchase.getPurchaseState() == Purchase.PurchaseState.PENDING) {
    // 付款仍在处理中
}

对于非消耗性商品的购买,完成后必须进行确认,以防止向用户退款。对于消耗性购买,需要在商品被消耗时通知 Google Play,以便用户可以再次购买。

6. 查询以前的购买记录

在处理应用内计费时,检查用户是否已经购买了某个产品或订阅是常见需求。可以通过调用计费客户端实例的 queryPurchasesAsync() 方法并实现 PurchaseResponseListener 来生成用户以前特定类型购买的列表。

示例代码如下:

QueryPurchasesParams queryPurchasesParams =
    QueryPurchasesParams.newBuilder()
        .setProductType(BillingClient.ProductType.INAPP)
        .build();

billingClient.queryPurchasesAsync(queryPurchasesParams, 
    new PurchasesResponseListener() {
        @Override
        public void onQueryPurchasesResponse(@NonNull BillingResult billingResult, 
                @NonNull List<Purchase> list) {
            // 处理购买列表
        }
    }
);
7. 应用内购买示例项目介绍

这个示例项目的简单概念是,在一个应用中,必须先购买一个应用内产品,才能点击一个按钮。每次点击按钮时,该应用内产品都会被消耗,这就要求用户每次想要点击按钮时都重新购买该产品。

8. 创建 InAppPurchase 项目

具体步骤如下:
1. 启动 Android Studio,从欢迎屏幕中选择“New Project”选项。
2. 在新项目对话框中选择“Empty Views Activity”模板,然后点击“Next”按钮。
3. 在“Name”字段中输入 InAppPurchase ,并指定一个在 Google Play 生态系统中唯一标识应用的包名(例如, com.<your company>.InAppPurchase )。
4. 在点击“Finish”按钮之前,将“Minimum API level”设置为 API 26: Android 8.0 (Oreo),并将“Language”菜单设置为 Java。
5. 项目创建完成后,按照相关步骤将项目转换为使用视图绑定。

9. 向项目添加库

在编写代码之前,需要向项目构建配置中添加一些库,包括标准的 Android 计费客户端库和 Google 的 Guava Core Java 库中的 ImmutableList 类。具体操作是修改 Gradle Scripts -> build.gradle.kts (Module: app) 文件,添加以下依赖:

dependencies {
    implementation ("com.android.billingclient:billing:6.0.1")
    implementation ("com.google.guava:guava:24.1-jre")
    implementation ("com.google.guava:guava:27.0.1-android")
}

然后点击编辑器面板顶部的“Sync Now”链接以提交这些更改。

10. 设计用户界面

用户界面将由现有的 TextView 和两个 Button 组成,具体设计步骤如下:
1. 将 activity_main.xml 文件加载到编辑器中,将两个 Button 视图拖放到布局中,使一个位于 TextView 上方,另一个位于下方。
2. 选择 TextView ,将其 id 属性更改为 statusText
3. 点击工具栏中的“Clear all Constraints”按钮,然后按住 Shift 键选择所有三个视图。
4. 右键单击最上方的 Button 视图,选择“Center -> Horizontally in Parent”菜单选项。再次重复此步骤,选择“Chains -> Create Vertical Chain”。
5. 将最上方按钮的 text 属性更改为“Consume Purchase”, id 更改为 consumeButton ,并将 onClick 属性配置为调用名为 consumePurchase 的方法。
6. 选择最下方的按钮,重复上述步骤,将 text 设置为“Buy Product”, id 设置为 buyButton onClick 回调设置为 makePurchase

11. 将应用添加到 Google Play 商店

具体步骤如下:
1. 按照“创建、测试和上传 Android 应用捆绑包”章节中概述的步骤,登录 Play 控制台,创建一个新应用,并设置一个新的内部测试轨道,包括指定测试人员的电子邮件地址。
2. 返回 Android Studio,为项目生成一个签名的发布应用捆绑包。
3. 生成捆绑包文件后,将其上传到内部测试轨道并推出进行测试。

12. 创建应用内产品

在 Play 控制台中选择应用,向下滚动左侧面板中的选项列表,直到出现“Monetize”部分。在该部分中,选择“Products”下列出的“In-app products”选项。在“in-app products”页面上,点击“Create product”按钮。在新产品屏幕上,输入以下信息,然后保存新产品:
- 产品 ID: one_button_click
- 名称: A Button Click
- 描述: This is a test in-app product that allows a button to be clicked once.
- 默认价格:设置为您首选货币的最低可能价格。

13. 启用许可证测试人员

在测试应用内计费时,启用内部测试轨道测试人员的许可证测试可以在不花费真钱的情况下进行测试购买。具体操作如下:
1. 在 Play 控制台中返回主屏幕,选择“Setup -> License testing”选项。
2. 在许可证测试屏幕中,添加为内部测试轨道添加的测试人员,将“License response”设置为 RESPOND_NORMALLY ,并保存更改。

14. 初始化计费客户端

编辑 MainActivity.java 文件,进行以下更改以开始实现应用内购买功能:

import androidx.annotation.NonNull;
import android.util.Log;
import com.android.billingclient.api.*;

public class MainActivity extends AppCompatActivity {
    private ActivityMainBinding binding;
    private BillingClient billingClient;
    private ProductDetails productDetails;
    private Purchase purchase;
    static final String TAG = "InAppPurchaseTag";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = ActivityMainBinding.inflate(getLayoutInflater());
        View view = binding.getRoot();
        setContentView(view);
        billingSetup();
    }

    private void billingSetup() {
        billingClient = BillingClient.newBuilder(this)
           .setListener(purchasesUpdatedListener)
           .enablePendingPurchases()
           .build();

        billingClient.startConnection(new BillingClientStateListener() {
            @Override
            public void onBillingSetupFinished(@NonNull BillingResult billingResult) {
                if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
                    Log.i(TAG, "OnBillingSetupFinish connected");
                    queryProduct();
                } else {
                    Log.i(TAG, "OnBillingSetupFinish failed");
                }
            }
            @Override
            public void onBillingServiceDisconnected() {
                Log.i(TAG, "OnBillingSetupFinish connection lost");
            }
        });
    }
}
15. 查询产品

为确保产品可供购买,需要创建一个配置了在 Play 控制台中指定的产品 ID 的 QueryProductDetailsParams 实例,并将其传递给计费客户端的 queryProductDetailsAsync() 方法。

示例代码如下:

private void queryProduct() {
    QueryProductDetailsParams queryProductDetailsParams =
        QueryProductDetailsParams.newBuilder()
           .setProductList(
                ImmutableList.of(
                    QueryProductDetailsParams.Product.newBuilder()
                       .setProductId("one_button_click")
                       .setProductType(BillingClient.ProductType.INAPP)
                       .build()))
           .build();

    billingClient.queryProductDetailsAsync(
        queryProductDetailsParams,
        new ProductDetailsResponseListener() {
            public void onProductDetailsResponse(
                    @NonNull BillingResult billingResult,
                    @NonNull List<ProductDetails> productDetailsList) {
                if (!productDetailsList.isEmpty()) {
                    productDetails = productDetailsList.get(0);
                    runOnUiThread(() -> {
                        binding.buyButton.setEnabled(true);
                        binding.statusText.setText(productDetails.getName());
                    });
                } else {
                    Log.i(TAG, "onProductDetailsResponse: No products");
                }
            }
        }
    );
}
16. 启动购买流程

当用户点击购买按钮时,调用 makePurchase() 方法启动购买流程。

示例代码如下:

public void makePurchase(View view) {
    BillingFlowParams billingFlowParams =
        BillingFlowParams.newBuilder()
           .setProductDetailsParamsList(
                ImmutableList.of(
                    BillingFlowParams.ProductDetailsParams.newBuilder()
                       .setProductDetails(productDetails)
                       .build()
                )
            )
           .build();

    billingClient.launchBillingFlow(this, billingFlowParams);
}
17. 处理购买更新

购买过程的结果将通过在初始化阶段分配给计费客户端的 PurchaseUpdatedListener 报告给应用。

示例代码如下:

private final PurchasesUpdatedListener purchasesUpdatedListener = new PurchasesUpdatedListener() {
    @Override
    public void onPurchasesUpdated(BillingResult billingResult, 
            List<Purchase> purchases) {
        if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK
                && purchases != null) {
            for (Purchase purchase : purchases) {
                completePurchase(purchase);
            }
        } else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.USER_CANCELED) {
            Log.i(TAG, "onPurchasesUpdated: Purchase Canceled");
        } else {
            Log.i(TAG, "onPurchasesUpdated: Error");
        }
    }
};

private void completePurchase(Purchase item) {
    purchase = item;
    if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED)
        runOnUiThread(() -> {
            binding.consumeButton.setEnabled(true);
            binding.statusText.setText("Purchase Complete");
        });
}
18. 消耗产品

当用户点击“消耗”按钮时,需要确保产品被消耗,以便在购买另一次按钮点击之前只能执行一次点击。

示例代码如下:

public void consumePurchase(View view) {
    ConsumeParams consumeParams =
        ConsumeParams.newBuilder()
           .setPurchaseToken(purchase.getPurchaseToken())
           .build();

    ConsumeResponseListener listener = new ConsumeResponseListener() {
        @Override
        public void onConsumeResponse(BillingResult billingResult, 
                @NonNull String purchaseToken) {
            if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
                runOnUiThread(() -> {
                    binding.consumeButton.setEnabled(false);
                    binding.statusText.setText("Purchase consumed");
                });
            }
        }
    };
    billingClient.consumeAsync(consumeParams, listener);
}
19. 恢复以前的购买

为了解决在购买后未消耗就退出应用,重启应用时购买丢失的问题,可以配置一个 QueryPurchasesParams 实例来搜索未消耗的应用内产品,并将其传递给计费客户端的 queryPurchasesAsync() 方法。

示例代码如下:

private void reloadPurchase() {
    QueryPurchasesParams queryPurchasesParams = QueryPurchasesParams.newBuilder()
           .setProductType(BillingClient.ProductType.INAPP)
           .build();

    billingClient.queryPurchasesAsync(
        queryPurchasesParams,
        purchasesListener
    );
}

private final PurchasesResponseListener purchasesListener = new PurchasesResponseListener() {
    @Override
    public void onQueryPurchasesResponse(@NonNull BillingResult billingResult, 
            @NonNull List<Purchase> list) {
        if (!list.isEmpty()) {
            purchase = list.get(0);
            binding.consumeButton.setEnabled(true);
        } else {
            binding.consumeButton.setEnabled(false);
        }
    }
};

通过以上步骤和代码示例,你可以全面了解 Android 应用内计费的原理和实现方式,并通过示例项目进行实践操作。

Android 应用内计费全解析与实战教程

20. 各步骤流程图

为了更清晰地展示整个 Android 应用内计费的流程,下面给出 mermaid 格式的流程图:

graph LR
    classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
    classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px;

    A([开始]):::startend --> B(创建计费客户端):::process
    B --> C(连接到 Google Play 计费库):::process
    C --> D{连接是否成功}:::decision
    D -->|是| E(查询可用产品):::process
    D -->|否| C(连接到 Google Play 计费库):::process
    E --> F{是否有可用产品}:::decision
    F -->|是| G(启动购买流程):::process
    F -->|否| E(查询可用产品):::process
    G --> H{购买是否成功}:::decision
    H -->|是| I(完成购买):::process
    H -->|否| G(启动购买流程):::process
    I --> J{是否为消耗性产品}:::decision
    J -->|是| K(消耗产品):::process
    J -->|否| L(确认非消耗性购买):::process
    K --> M(恢复以前的购买):::process
    L --> M(恢复以前的购买):::process
    M --> N([结束]):::startend
21. 关键技术点总结
  • 计费客户端的创建与连接 :使用 BillingClient.newBuilder() 创建计费客户端,并通过 startConnection() 方法异步连接到 Google Play 计费库,同时需要实现 BillingClientStateListener 来处理连接结果和断开情况。
  • 产品查询 :通过 QueryProductDetailsParams 配置产品信息,调用 queryProductDetailsAsync() 方法查询可用产品,使用 ProductDetailsResponseListener 处理查询结果。
  • 购买流程 :使用 BillingFlowParams 配置购买信息,调用 launchBillingFlow() 方法启动购买流程,购买结果通过 PurchasesUpdatedListener 处理。
  • 购买完成处理 :根据 Purchase 对象的 getPurchaseState() 方法判断购买状态,对于非消耗性产品需要确认购买,对于消耗性产品需要调用 consumeAsync() 方法消耗产品。
  • 恢复购买 :使用 QueryPurchasesParams 配置查询信息,调用 queryPurchasesAsync() 方法恢复以前的购买,使用 PurchasesResponseListener 处理查询结果。
22. 常见问题及解决方法
问题描述 解决方法
连接到 Google Play 计费库失败 检查网络连接,确保应用的包名、签名等信息在 Google Play 控制台配置正确,可在 onBillingServiceDisconnected() 方法中重新尝试连接。
查询不到可用产品 检查产品 ID 是否在 Google Play 控制台正确配置,确保产品已发布且处于可用状态。
购买流程无法启动 检查 BillingFlowParams 配置是否正确,确保 ProductDetail 对象有效。
购买完成后无法消耗产品 检查 ConsumeParams 中的购买令牌是否正确,确保购买状态为已购买。
23. 代码优化建议
  • 错误处理 :在各个回调方法中,除了简单的日志输出,还可以根据不同的错误码给用户更友好的提示信息。例如,在 onPurchasesUpdated() 方法中,根据不同的 BillingResponseCode 显示不同的错误提示。
  • 性能优化 :对于一些频繁调用的方法,如 queryProductDetailsAsync() ,可以考虑添加缓存机制,避免重复查询。
  • 代码复用 :将一些通用的方法封装,提高代码的复用性。例如,将创建 BillingFlowParams 的逻辑封装成一个独立的方法。
24. 总结

通过本文,我们全面了解了 Android 应用内计费的相关知识,包括从创建项目到实现应用内购买的整个流程。从创建 InAppPurchase 项目,到添加必要的库、设计用户界面,再到在 Google Play 控制台配置应用和应用内产品,最后通过代码实现产品查询、购买、消耗和恢复等功能。

同时,我们还通过流程图清晰地展示了整个流程,总结了关键技术点,分析了常见问题及解决方法,并给出了代码优化建议。希望这些内容能帮助开发者更好地实现 Android 应用内计费功能,为应用带来更多的收益。

在实际开发中,开发者可以根据自己的需求对示例代码进行修改和扩展,例如添加更多的产品类型、优化用户界面等。不断实践和探索,才能更好地掌握 Android 应用内计费的技术。

【四旋翼无人机】具备螺旋桨倾斜机构的驱动四旋翼无人机:建模控制研究(Matlab代码、Simulink仿真实现)内容概要:本文围绕具备螺旋桨倾斜机构的驱动四旋翼无人机展开研究,重点探讨其系统建模控制策略,结合Matlab代码Simulink仿真实现。文章详细分析了无人机的动力学模型,特别是引入螺旋桨倾斜机构后带来的驱动特性,使其在姿态位置控制上具备更强的机动性自由度。研究涵盖了非线性系统建模、控制器设计(如PID、MPC、非线性控制等)、仿真验证及动态响应分析,旨在提升无人机在复杂环境下的稳定性和控制精度。同时,文中提供的Matlab/Simulink资源便于读者复现实验并进一步优化控制算法。; 适合人群:具备一定控制理论基础和Matlab/Simulink仿真经验的研究生、科研人员及无人机控制系统开发工程师,尤其适合从事飞行器建模先进控制算法研究的专业人员。; 使用场景及目标:①用于驱动四旋翼无人机的动力学建模仿真平台搭建;②研究先进控制算法(如模型预测控制、非线性控制)在无人机系统中的应用;③支持科研论文复现、课程设计或毕业课题开发,推动无人机高机动控制技术的研究进展。; 阅读建议:建议读者结合文档提供的Matlab代码Simulink模型,逐步实现建模控制算法,重点关注坐标系定义、力矩分配逻辑及控制闭环的设计细节,同时可通过修改参数和添加扰动来验证系统的鲁棒性适应性。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值