InAppBilling activity 应在何时调用 finish()?
明确结论
不要在 requestPurchase() 返回后马上调用 finish()。这个返回值只能说明购买请求已经提交,不能说明购买最终是成功、失败还是取消。
建议按以下方式处理:
- 让
InAppBillingActivity保持存活,等待购买结果回调。 - 在成功、取消或不可恢复的失败回调中调用
setResult()和finish()。 - 无论 Activity 以何种方式结束,都要注销
PurchaseObserver,并解除与 Billing Service 的绑定。 - 不要只依赖 Activity 接收购买结果。Activity 可能因屏幕旋转、后台回收等原因被销毁,最终订单状态应交给生命周期更长的组件处理,或通过恢复查询确认。
日志中的 ServiceConnectionLeaked 不是异步请求直接造成的。它表示 Activity 已经销毁,但绑定的 Service 没有执行 unbindService()。
为什么立即调用 finish() 会出问题
调用 finish() 后,Activity 会进入销毁流程。如果 BillingService 使用当前 Activity 作为 Context 调用了 bindService(),就必须在 Activity 销毁前解除绑定。
当前代码中有:
mBillingService.setContext(this);
这意味着 Service 连接与 InAppBillingActivity 的生命周期相关。立即调用 finish() 后,系统发现 Activity 已经销毁,而对应的 ServiceConnection 仍然存在,因此抛出:
android.app.ServiceConnectionLeaked
旧版 In-App Billing 的 requestPurchase() 也是异步流程。它的返回值最多只能说明请求是否被接受或成功发起,不能代表用户已经完成购买。
在购买结果回调中结束 Activity
购买观察者应当把最终状态通知给 Activity,再由 Activity 结束自身。下面是一个结构示例。具体的回调签名需要与项目所使用的旧版 In-App Billing 示例代码保持一致:
public class InAppBillingActivity extends Activity {
private Handler mHandler;
private BillingService mBillingService;
private InAppBillingPurchaseObserver mPurchaseObserver;
private boolean mCleanupDone;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mHandler = new Handler();
mPurchaseObserver =
new InAppBillingPurchaseObserver(mHandler) {
@Override
public void onPurchaseCompleted(String productId) {
Intent result = new Intent();
result.putExtra("product_id", productId);
setResult(RESULT_OK, result);
finish();
}
@Override
public void onPurchaseCancelled() {
setResult(RESULT_CANCELED);
finish();
}
@Override
public void onPurchaseFailed(int responseCode) {
Intent result = new Intent();
result.putExtra("response_code", responseCode);
setResult(RESULT_CANCELED, result);
finish();
}
};
mBillingService = new BillingService();
mBillingService.setContext(this);
ResponseHandler.register(mPurchaseObserver);
boolean supported = mBillingService.checkBillingSupported();
if (supported) {
boolean submitted = mBillingService.requestPurchase(
"android.test.purchased",
"10"
);
if (!submitted) {
setResult(RESULT_CANCELED);
finish();
}
} else {
setResult(RESULT_CANCELED);
finish();
}
}
@Override
protected void onDestroy() {
cleanupBilling();
super.onDestroy();
}
private void cleanupBilling() {
if (mCleanupDone) {
return;
}
mCleanupDone = true;
if (mPurchaseObserver != null) {
ResponseHandler.unregister(mPurchaseObserver);
}
if (mBillingService != null) {
mBillingService.unbind();
}
}
}
这里的 onPurchaseCompleted()、onPurchaseCancelled() 和 onPurchaseFailed() 只是用来说明处理流程,未必与项目中的实际接口完全相同。应把 finish() 放到现有 PurchaseObserver 所提供的最终状态回调中。
正确解除 Service 绑定
早期 Google In-App Billing 示例中的 BillingService 通常会提供类似方法:
public void unbind() {
try {
unbindService(this);
} catch (IllegalArgumentException e) {
// Service 当前可能没有绑定。
}
}
更稳妥的做法是记录绑定状态,避免重复解绑:
private boolean mBound;
private void bindBillingService() {
mBound = bindService(
billingIntent,
mServiceConnection,
Context.BIND_AUTO_CREATE
);
}
public void unbind() {
if (mBound) {
unbindService(mServiceConnection);
mBound = false;
}
}
基本要求是,调用 bindService() 的一方必须在生命周期结束时配对调用 unbindService()。
清理操作至少要放在 onDestroy() 中。如果注册和绑定发生在 onStart(),也可以在 onStop() 中注销和解绑,但两边必须使用一致的生命周期边界。
从主 Activity 获取购买结果
购买 Activity 需要返回结果,因此旧代码可以使用 startActivityForResult(),而不是普通的 startActivity():
private static final int REQUEST_PURCHASE = 1001;
Intent intent = new Intent(this, InAppBillingActivity.class);
startActivityForResult(intent, REQUEST_PURCHASE);
然后在主 Activity 中接收结果:
@Override
protected void onActivityResult(
int requestCode,
int resultCode,
Intent data
) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode != REQUEST_PURCHASE) {
return;
}
if (resultCode == RESULT_OK) {
String productId = data != null
? data.getStringExtra("product_id")
: null;
// 更新界面,但仍应根据已确认的订单状态授予权益。
} else {
// 用户取消、请求失败或购买未完成。
}
}
现代 Android 项目应优先使用 Activity Result API。不过,这段问题代码来自旧版 In-App Billing,使用 startActivityForResult() 更容易与现有结构配合。
不要把“请求响应”当成“购买成功”
旧版 Billing API 通常有多个层次的回调:
- 请求已被市场应用接受;
- 购买界面已经启动;
- 用户完成购买、取消购买或购买失败;
- 购买状态通过广播或恢复交易查询返回。
例如,名为 onRequestPurchaseResponse() 的回调可能只表示 REQUEST_PURCHASE 请求得到了响应,并不代表交易已经完成。只有收到明确的购买状态,并完成必要的订单验证或确认后,才能授予商品权益。
因此,不能在收到第一个响应回调时就直接执行:
finish();
应先确认该回调表示的是“请求提交结果”还是“最终购买结果”。
Activity 被提前销毁时怎么办
Activity 不一定能存活到异步结果返回。以下情况都可能让它提前销毁:
- 用户按返回键;
- 屏幕旋转或配置发生变化;
- 应用进入后台;
- 系统回收进程;
- 市场购买界面覆盖当前 Activity。
因此,不要只把购买状态保存在 Activity 字段中。更可靠的设计是:
- Activity 负责发起购买和显示状态;
- 广播接收器、Repository、Service 或 BillingClient 连接管理器负责接收并保存订单状态;
- Activity 恢复后重新查询已有购买;
- 确认订单真实存在后再发放权益;
- 清理 UI 观察者时,不要丢弃已经写入持久层的购买结果。
如果用户主动返回,可以结束 Activity,但要先注销观察者并解除 Service 绑定。用户再次进入页面时,应通过恢复购买或查询购买记录来确认最终状态。
还需要注意的几个问题
不要只根据 checkBillingSupported() 的同步返回值设计整个流程。某些旧版封装会在内部绑定 Service 并把请求放入队列,实际行为取决于项目使用的 BillingService 实现。
调用 ResponseHandler.register() 后,必须有对应的注销操作:
ResponseHandler.unregister(mPurchaseObserver);
否则,即使没有发生 Service 泄漏,也可能因为静态引用长期持有 Activity 而造成内存泄漏。
还要避免重复发起购买。配置变化会让 onCreate() 再次执行,当前代码可能因此再次调用 requestPurchase()。可以保存“购买是否已启动”的状态,也可以把购买流程移到能够跨配置变化保存状态的组件中。
这套 BillingService、PurchaseObserver 和 ResponseHandler 结构属于早期 Google In-App Billing API。新项目应使用当前版本的 Google Play Billing Library,通过 PurchasesUpdatedListener、购买查询和确认流程管理交易。无论使用哪一代 API,都应等待最终交易状态,让业务状态独立于 Activity 生命周期,并在 Activity 销毁时完整释放所有注册和连接。
备注:内容仅供参考。
