Google Play应用内购买演示应用






4.76/5 (14投票s)
本文是 Google Play 应用内购买版本 3 的示例。附带的 TestInAppBilling 源代码是一个完整的演示应用程序。
 
1. 引言
本文及相关源代码是 Google Play 应用内购买版本 3 的示例。附带的 TestInAppBilling 源代码是一个完整的演示应用程序。对于应用内购买,无法提供可直接运行的应用程序(APK 文件)。每个应用内购买都必须有一个唯一的软件包名称和一个由 Google Play 提供的唯一密钥。要获取许可证密钥,您必须能够访问 Google Play 开发者控制台。如果您没有访问权限,则必须注册成为开发者。第 2 部分“安装”将介绍获取演示应用程序并运行所需的所有步骤。
TestInAppBilling 源代码是 Google Play 上的 Android Color Selector for Programmers 应用程序的一部分。该应用程序是免费的。但是,用户可以通过应用内购买来购买该应用程序的源代码。如果您下载了 Android Color Selector,请前往“关于”屏幕查看应用内购买按钮。下面对 Android Color Selector 进行了简要说明。
面向程序员的 Android Color Selector 应用程序允许您从调色板中选择颜色。选择结果是一个 24 位 RGB 颜色值。该应用程序被设计为从另一个应用程序调用并将其结果返回给调用应用程序。如果您开发需要用户选择颜色的应用程序,Android Color Selector 将非常适合您。您还可以通过点击平板电脑上的图标来手动启动 Android Color Selector。结果将在屏幕上显示。
2. TestInAppBilling 源代码安装
如果您是经验丰富的 Android 程序员,可以跳过本节。您只需要更改软件包名称(从“com.granotech.testinginappbilling”到您自己的名称),在 Google Play 上定义一个产品 ID 为“product0001”的应用内产品,并将 Google Play 提供的应用程序密钥复制到 TestInAppBilling.java 中。
2.1. 先决条件
测试 TestInAppBilling 应用程序需要以下先决条件:
- 您已注册为开发者,并有权访问 Google Play 开发者控制台。
- 您已在计算机上安装了 Android Developer Tools (ADT)。
- 您有一台安装了 Google Play 商店软件的 Android 平板电脑。
- 您已在平板电脑上启用了开发者模式和 USB 访问。
- 您已在平板电脑上启用了未知来源的应用安装。
- 您的平板电脑上装有文件浏览器类软件。如果没有,请安装一个。
2.2. 将源代码导入 Android Developer Tools (ADT)
如果您知道如何导入现有的 Android 代码,可以跳过本节,但软件包重命名步骤除外。导入后,您必须将软件包名称从“com.granotech.testinappbilling”更改为一个全局唯一的名称,例如“com.yourcompany.yourappname”。
- 下载源代码。
- 将 Zip 文件中的文件解压到一个临时文件夹。请注意:ADT 导入功能不能直接处理下载的 Zip 文件。
- 启动 ADT 程序。
- 如果您想将项目加载到空工作区中,必须先向其中添加一个新空项目。您不能将项目导入空工作区。导入源代码后,可以删除此空项目。
- 从“文件”菜单中选择“导入”。
- 选择“Android --> Existing Android Code into Workspace”。点击“下一步”。
- 点击“浏览根目录”按钮。导航到您解压项目文件的位置。点击“确定”。
- 您将看到要导入的 TestInAppBilling 项目。
- 请确保选中“将项目复制到工作区”复选框。
- 单击“完成”。
- TestInAppBillingActivity 软件包已添加到您的“软件包资源管理器”中。
- 重命名(Alt-Shift-R)此名称为您选择的名称。
- 在“软件包资源管理器”窗格的“src”下,将 com.granotech.testinappbilling 重命名为“com.yourcompany.yourappname”。您必须将软件包名称更改为一个全局唯一的名称,Google Play 才能接受它。
- 如果创建了空项目,现在可以删除它。
- 您可以在调试模式下进行非常有限的测试。在能够测试应用内购买之前,必须完成接下来的两个步骤。首次运行时,请确保将其设置为 Android 应用程序。
2.3. 将 TestInAppBilling 应用程序添加到 Google Play 开发者控制台
- 登录您的 Google Play 开发者控制台帐户。
- 添加一个新应用程序。
- 为此应用程序指定一个您选择的标题。在本示例文章中,我们将使用“Test In-App Billing”。
- 定义一个应用内产品。对于本测试,它必须是一个托管产品,产品 ID 为“product0001”。所有其他信息与测试无关。
2.4. 将 TestInAppBilling APK 上传到 Alpha 版本
- 登录您的 Google Play 开发者控制台帐户。
- 选择“Test In-App Billing”应用程序。
- 选择“服务与 API”。
- 转到“此应用程序的许可证密钥”并复制 base64 编码的 RSA 公钥到剪贴板。
- 转到 ADT 并编辑 TestInAppBillingActivity.java 源代码模块。
- 将剪贴板中的 base64 编码的 RSA 公钥粘贴到 applicationPublicKey 字符串中,替换现有的占位符密钥。
- 点击“文件 --> 导出”。
- 选择“Android --> 导出 Android 应用程序”。点击“下一步”。
- 选择 TestInAppBilling 项目。点击“下一步”。
- 如果这是第一次,请创建密钥库,否则请输入密码。
- 创建新密钥或输入现有密钥的密码。
- 将 APK 文件保存在开发计算机的生产目录中。
- 返回开发者控制台并选择 TestInAppBilling 应用程序。
- 选择“APK Alpha 测试”。
- 点击“上传新的 APK 到 Alpha 版本”。
- 将您的生产 APK 文件拖放到 Google Play 上传。上传完成后点击“保存”。
2.5. 在您的平板电脑上测试 TestInAppBilling
- 通过 USB 数据线将平板电脑连接到您的开发计算机。
- 将上面第 2.4 步中创建的生产 APK 文件从开发计算机上的生产目录复制到平板电脑的下载目录。
- 在平板电脑上找到 APK 文件。点击它并在平板电脑上安装。
- 打开应用程序。
- 您有两种选择:购买产品或消耗产品。首先按“购买”按钮。程序将启动为开发者控制台中定义的产品进行购买的过程。一旦您购买了它,除非消耗掉,否则无法再次购买。要消耗该产品,请按第二个按钮。
- 如果出现错误,您将收到一条消息和错误位置。错误位置是源代码模块名称、行号和方法。
3. 应用程序源代码概述
附带的源代码包含三个 Java 模块:
- InAppBilling.java 
 InAppBilling 类负责与 Google Play 进行所有通信。这是您将集成到您的应用程序中以执行应用内购买的类。
- TestInAppBillingActivity.java 
 TestInAppBillingActivity 是您自己的应用内购买活动类的一个模拟。
- IInAppBillingService.aidl 
 IInAppBillingService.aidl 是 Android 接口定义语言 (AIDL) 文件,它定义了与 Google Play 服务的接口。源代码由 Google 提供。请注意:您不能更改此模块的名称或软件包名称。软件包名称必须是“com.android.vending.billing”。请访问以下链接获取有关此内容的全部信息:准备您的应用内购买应用程序。
4. InAppBilling 类代码概述
InAppBilling 类处理购买或消耗一项商品的店内购买流程。要购买一项商品,请实例化该类并调用 startServiceConnection 方法。购买过程是异步的。startServiceConnection 方法会启动该过程并立即返回。当该过程完成后(稍后),将调用 InAppBillingListener 类的一个回调方法。InAppBilling 类从调用 startServiceConnection 开始,一直到调用 InAppBillingListener 的回调方法之一为止。您可以多次调用 startServiceConnection,除非该类正在忙碌。在忙碌时调用 startServiceConnection 将被忽略。
4.1. InAppBilling 构造函数
public InAppBilling ( Activity parentActivity, final InAppBillingListener inAppBillingListener, String appPublicKeyStr, int purchaseRequestCode )
调用参数是:
- parentActivity:InAppBilling 的父活动上下文。在此演示项目中,它是- TestInAppBillingActivity。
- inAppBillingListener:一个实现回调方法以传递最终结果的类。- InAppBillingListener- 接口在下一节中进行描述。在此演示项目中,它是- TestInAppBillingActivity。
- appPublicKeyStr:Google Play 商店为此应用程序分配的 base64 格式的公钥。
- purchaseRequestCode:- onActivityResult的请求代码。
4.2. InAppBillingListener 接口
public interface InAppBillingListener
	{
	public void inAppBillingBuySuccsess();
	public void inAppBillingItemAlreadyOwned();
	public void inAppBillingCanceled();
	public void inAppBillingConsumeSuccsess();
	public void inAppBillingItemNotOwned();
	public void inAppBillingFailure(String errorMessage);
	}
监听器类必须实现 6 个方法:
- inAppBillingBuySuccsess():购买过程成功。添加代码以向客户提供购买的产品。
- inAppBillingItemAlreadyOwned():客户已拥有此产品。未向 Google Play 发送购买请求。在此情况下执行适当的操作。
- inAppBillingCanceled():购买过程已取消。您可以将此方法留空,或向用户提供一些反馈。
- inAppBillingConsumeSuccess():产品已成功消耗。换句话说,用户可以再次购买它。
- inAppBillingItemNotOwned():该商品未被拥有。因此无法消耗。
- inAppBillingFailure(String errorMessage):发生意外错误。错误消息包含适当的文本消息以及错误所在的源代码文件名和行号。此外,还会提供方法名称。
请注意:当调用任何监听方法时,InAppBilling 类将不再处于活动状态。换句话说,您可以使用同一个对象发起另一次购买或消耗。
4.3. 调用 startServiceConnection 方法
public void startServiceConnection ( String itemType, String itemSku, boolean consumeItem )
- itemType:该项可以是- ITEM_TYPE_ONE_TIME_PURCHASE= "inapp"(一次性购买)或- ITEM_TYPE_SUBSCRIPTION= "subs"(订阅)。
- itemSku:在 Google Play 开发者控制台中定义的产品 ID。在本示例中,它是 product0001。
- consumeItem:对于购买,应为- ACTIVITY_TYPE_PURCHASE= false。对于消耗,应为- ACTIVITY_TYPE_CONSUME= true。
4.4. InAppBilling 逻辑流程
- 通过调用构造函数实例化 InAppBilling 对象。只要对象未处于活动状态,就可以重用该对象。
- 调用 startServiceConnection方法。此方法将创建一个serviceConnection并将其绑定到设备上的 Google Play 商店服务。绑定过程是异步的。startServiceConnection将在连接激活之前立即返回。
- 绑定过程完成后,系统将调用 serviceConnected方法。
- serviceConnected方法将检查此设备上是否支持应用内购买服务。注意:- IInAppBillingService.isBillingSupported。
- 如果支持应用内购买,该方法将检查该商品是否可供销售。注意:IInAppBillingService.getSkuDetails。
- 如果该商品可供销售,该方法将检查客户是否已拥有该商品。注意:IInAppBillingService.getPurchases。
- 如果调用 startServiceConnection是为了消耗,并且客户拥有该商品,则该商品将被消耗。注意:inAppBillingService.consumePurchase。如果客户未拥有该商品,InAppBilling 将以调用inAppBillingItemNotOwned() 终止。
- 如果调用 startServiceConnection是为了购买,并且客户已拥有该商品,则 InAppBilling 将以调用inAppBillingItemAlreadyOwned() 终止。
- 如果调用 startServiceConnection是为了购买,并且客户未拥有该商品,程序将发送一个异步请求来购买该商品。注意:inAppBillingService.getBuyIntent和startIntentSenderForResult。
- 请注意:此时客户将看到 Google 对话框,要求客户批准购买。
- 当 Google Play 商店处理完请求后,系统将在父 Activity类中调用onActivityResult方法。此调用将转发到此类的onActivityResult方法。
- onActivityResult方法会检查结果。有四种可能的 outcome:(1) 用户取消 (2) 错误 (3) 结果正常但返回的数据无效 (4) 购买成功。将调用相应回调方法。
- 每当调用回调方法时,InAppBilling类都会从服务中解除绑定,重置活动标志,然后调用回调方法。
4.5. InAppBilling 编程注意事项
对 IInAppBillingService.getSkuDetails()、IInAppBillingService.getBuyIntent() 和 IINAppBillingService.getPurchases() 的调用返回的信息比本演示中使用的要多。信息以 JSON 键值对的形式存在。InAppBilling.java 源代码会标识您可能需要的额外信息。有关更多信息,请访问应用内购买参考 (IAB 版本 3)。
InAppBilling 类包含两个与签名验证相关的方法:verifySignature 和 decodeBase64。这些方法基于 Google 提供的示例项目。然而,两者都更简单。
在 InAppBilling 源代码的末尾,有一些与创建错误消息相关的方法。getErrorLocation 对 C 和 c++ 程序员特别有用,因为它展示了如何获取错误所在的源代码模块名称 __FILE__ 和行号 __LINE__。
5. TestInAppBillingActivity 编程注意事项
TestInAppBillingActivity 类是您自己活动类的一个模拟。这是将从其调用应用内购买的活动。下面您将找到此活动中定义的所有方法。有关该类的其他成员,请参阅源代码模块。
5.1. onCreate
此活动以编程方式创建布局。我们使用 RelativeLayout。将四个视图添加到布局中:标题、购买按钮、消耗按钮和文本消息区域。
@Override
protected void onCreate(Bundle savedInstanceState)
	{
	// call super class
	super.onCreate(savedInstanceState);
	// get display metrics
	DisplayMetrics displayMetrics = new DisplayMetrics();
	getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
	
	// conversion factor mm to pixels
	mmToPixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 1.0F, displayMetrics);
	
	// text size and margin
	float textSize = 7.0F;
	float msgTextSize = 5.0F;
	int margin = (int) (4.0F * mmToPixels);
	
	// create layout
	layout = new RelativeLayout(this);
	
	// take all screen area
	layout.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT));
	// same background color
	layout.setBackgroundColor(Color.rgb(163, 176, 255));
	// center view at the center of the screen
	layout.setGravity(Gravity.CENTER);
	// create title 1
	createTextView(TITLE1_ID, "Google Play", textSize, 0);
	// create title 2
	createTextView(TITLE2_ID, "In-App Billing Demo", textSize, 0);
	
	// create title 3
	createTextView(TITLE3_ID, "Granotech Limited", msgTextSize, margin);
	// create buy button
	createButton(BUY_BUTTON_ID, "Buy Product", textSize, margin).setOnClickListener(new OnClickListener()
    		{
    		// define on click listener to button
		public void onClick(View view)
			{
			buyProduct();
			return;
			}});
    
	// create consume button
	createButton(CONSUME_BUTTON_ID, "Consume Product", textSize, margin).setOnClickListener(new OnClickListener()
    		{
    		// define on click listener to button
		public void onClick(View view)
			{
			consumeProduct();
			return;
			}});
    
	// create message box
	msgBox = createTextView(MESSAGE_BOX_ID, "Click either\nBuy Product button or\nConsume Product button", msgTextSize,  margin);
	// add layout to activity
	setContentView(layout);
	return;
	}
5.2. createTextView
在 onCreate 期间创建 TextView。
private TextView createTextView(int id, String text, float textSize, int margin)
	{
	TextView textView = new TextView(this);
	textView.setId(id);
   	textView.setText(text);
   	textView.setTextSize(TypedValue.COMPLEX_UNIT_MM, textSize);
	RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(
		RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
	lp.addRule(RelativeLayout.CENTER_HORIZONTAL);
	if(id > 0) lp.addRule(RelativeLayout.BELOW, id - 1);
	lp.setMargins(0, 0, 0, margin);
	layout.addView(textView, lp);
	return(textView);
	}
5.3. createButton
在 onCreate 期间创建 Button。
private Button createButton(int id, String text, float textSize, int margin)
	{
	// create buy button
	Button button = new Button(this);
	button.setId(id);
	button.setText(text);
	button.setTextSize(TypedValue.COMPLEX_UNIT_MM, textSize);
	int padding = (int) (4.0F * mmToPixels);
	button.setPadding(padding, 0, padding, 0);
	RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(
		RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
	lp.addRule(RelativeLayout.CENTER_HORIZONTAL);
	if(id > 0) lp.addRule(RelativeLayout.BELOW, id - 1);
	lp.setMargins(0, 0, 0, margin);
	layout.addView(button, lp);
	return(button);
	}
5.4. buyProduct
这是“购买产品”按钮的点击监听器。它将启动购买过程的方法。
// user clicked the buy button
private void buyProduct()
	{
	// first time
	if(inAppBilling == null)
		{
		// create in-app billing object
		inAppBilling = new InAppBilling(this, this, applicationPublicKey, PURCHASE_REQUEST_CODE);
		}
	
	// InAppBilling initialization
	// NOTE: if inAppBilling is already active, the call is ignored
	inAppBilling.startServiceConnection(InAppBilling.ITEM_TYPE_ONE_TIME_PURCHASE,
				PRODUCT_SKU, InAppBilling.ACTIVITY_TYPE_PURCHASE);
	
	// exit
	return;
	}
5.5. consumeProduct
这是“消耗产品”按钮的点击监听器。它将启动消耗产品过程的方法。
// user clicked the consume button
private void consumeProduct()
	{
	// first time
	if(inAppBilling == null)
		{
		// create in-app billing object
		inAppBilling = new InAppBilling(this, this, applicationPublicKey, PURCHASE_REQUEST_CODE);
		}
	
	// InAppBilling initialization
	// NOTE: if inAppBilling is already active, the call is ignored
	inAppBilling.startServiceConnection(InAppBilling.ITEM_TYPE_ONE_TIME_PURCHASE,
			PRODUCT_SKU, InAppBilling.ACTIVITY_TYPE_CONSUME);
	// exit
	return;
	}
5.6. inAppBillingBuySuccsess
监听器回调方法。购买过程成功。
@Override
public void inAppBillingBuySuccsess()
	{
	msgBox.setText("In App purchase successful");
	return;
	}
5.7. inAppBillingItemAlreadyOwned
监听器回调方法。购买过程未完成。客户已拥有此产品。
@Override
public void inAppBillingItemAlreadyOwned()
	{
	msgBox.setText("Product is already owned.\nPurchase was not initiated.");
	return;
	}
5.8. inAppBillingCanceled
监听器回调方法。购买过程已取消。
@Override
public void inAppBillingCanceled()
	{
	msgBox.setText("Purchase was canceled by user");
	return;
	}
5.9. inAppBillingConsumeSuccess
监听器回调方法。客户拥有此产品,并且现在已被消耗。他/她可以再次购买。
@Override
public void inAppBillingConsumeSuccsess()
	{
	msgBox.setText("In App consume product successful");
	return;
	}
5.10. inAppBillingItemNotOwned
监听器回调方法。消耗过程未完成。该商品未被拥有。
@Override
public void inAppBillingItemNotOwned()
	{
	msgBox.setText("Product is not owned.\nConsume failed.");
	return;
	}
5.11. inAppBillingBillingFailure
监听器回调方法。在购买或消耗过程中发生意外错误。
@Override
public void inAppBillingFailure(String errorMessage)
	{
	msgBox.setText("Purchase or consume process failed.\n" + errorMessage);
	return;
	}
5.12. onActivityResult
当 Google Play 收到客户授权继续购买时,将激活此回调。
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
	{
	// purchase request code
	if(inAppBilling != null && requestCode == PURCHASE_REQUEST_CODE)
		{
	    	// Pass on the activity result to inAppBilling for handling
		inAppBilling.onActivityResult(resultCode, data);
		}
	else
		{
		// default onActivityResult
		super.onActivityResult(requestCode, resultCode, data);
		}
	return;
	}
5.13. onBackPressed
用户按下了后退按钮。终止应用程序。
@Override
public void onBackPressed()
	{
	// terminate activity and return RESULT_CANCELED
	if(inAppBilling != null) inAppBilling.dispose();
	finish();
	return;
	}
5.14. onDestroy
应用程序即将销毁。请确保 InAppBilling 已被处理。
@Override
public void onDestroy()
	{
	if(inAppBilling != null) inAppBilling.dispose();
	super.onDestroy();
	return;
	}
6. 参考文献
Google 为软件开发者提供了广泛的网站。以下链接专门针对应用内购买:准备您的应用内购买应用程序。
7. 作者发布的其他开源软件
- 面向程序员的 Android Color Selector 是 Google Play 上免费提供的应用程序。 
- PDF 文件写入器 C# 类库 
 2013 年 4 月“最佳整体文章”竞赛获奖作品
 2013 年 4 月“最佳 C# 文章”竞赛获奖作品
 PDF 文件写入器是一个 C# 类库,允许 .NET 应用程序创建 PDF 文件。
- PDF 文件分析器(含 C# 解析类) 
 PDF 文件分析器旨在读取、解析和显示 PDF 文件的内部结构。
- 使用 C# 压缩/解压缩类处理标准 Zip 文件 
 本项目将提供使用 Deflate 压缩方法进行文件压缩和解压缩的工具,以及读写标准 Zip 文件。
8. 历史
2014/01/16:版本 1.0 原始修订


