应用内购买 - 快速设备方向变化 - 导致崩溃(IllegalStateException)

5

我根据Android的实现应用内计费指南,实现了应用内计费(v3)。

一切都很好,直到我旋转设备,然后立即将其旋转回原始方向。实际上,有时它可以正常工作,有时会崩溃并显示以下错误:

java.lang.IllegalStateException: IabHelper已被处理,因此无法使用。

似乎这与IAB的异步性质有关,但我不确定。

有什么想法吗?

4个回答

6
您可能因为在activity生命周期的某个地方调用了mHelper.dispose(),然后尝试在稍后使用同一已处理的实例而导致此异常。我的建议是只在onDestroy()中处理mHelper并在onCreate()中重新创建它。
但是,您将遇到另一个与IabHelper和设备旋转相关的问题。问题如下:在您的activity的onCreate()中,您创建IabHelper实例mHelper并设置它。稍后,您调用mHelper.launchPurchaseFlow(...),IAB弹出对话框出现在您的activity上方。然后您旋转设备,IabHelper实例在onDestroy(...)中被处理,然后在onCreate(...)中重新创建。IAB对话框仍然显示,您按下购买按钮,购买完成。然后在您的activity上调用onActivityResult(),您自然会调用mHelper.handleActivityResult(...)。问题是,在重新创建的IabHelper实例上从未调用过launchPurchaseFlow(...),因此IabHelper仅在handleActivityResult(...)中处理活动结果,如果之前已在当前实例上调用了launchPurchaseFlow(...)。您的OnIabPurchaseFinishedListener永远不会被调用。
我对此的解决方法是修改IabHelper以允许您告诉它在不调用launchPurchaseFlow(...)的情况下期望handleActivityResult(...)。我在IabHelper.java中添加了以下内容。
public void expectPurchaseFinished(int requestCode, OnIabPurchaseFinishedListener listener)
{
    mRequestCode = requestCode;
    mPurchaseListener = listener;
}

这将导致IabHelper在调用handleActivityResult(...)时,在监听器上调用onIabPurchaseFinished(...)。然后,您需要执行以下操作:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) 
{
    mHelper.expectPurchaseFinished(requestCode, mPurchaseFinishedListener);
    mHelper.handleActivityResult(requestCode, resultCode, data);
}

我的整个IabHelper副本可以在此处找到https://gist.github.com/benhirashima/7917645。请注意,我使用此提交版本更新了我的IabHelper副本,该版本修复了一些错误,尚未发布在Android SDK Manager中。还要注意,有更新的提交版本,但它们包含新错误,不应使用。


关于处理旋转事件的整洁解决方案。我曾经有些困惑于expectPurchaseFinished的含义,但最终我意识到,在处理结果之前,您正在设置一个新的purchaseFinishedListener实例,这确保了即使重新创建了活动和iabHelper,也有一个监听器来处理已购买完成的事件。 - Kevin Lee
我们可以将相同的思路扩展到处理mPurchasingItemType(仅在同时使用subs和inapp时才有影响,例如使用onSaveInstanceState和onRestoreInstanceState,然后设置正确的项目类型,如果未设置)。 - Kevin Lee

2
这是我所做的:
在onCreate()中实例化IabHelper并调用startSetup()的代码,因此只要您没有自己处理配置更改,它将在设备旋转时重新创建。
此外,请确保在onActivityResult()的开头调用.handleActivityResult()。这将确保购买对话框关闭后正确清除您的IabHelper引用。
有了这两个东西,您就不应该再看到任何崩溃了。但您会注意到另一件事:
如果您使用调用launchPurchaseFlow()来启动购买对话框,然后旋转设备,对话框将保持打开状态,但此时您的ActivityIabHelper引用已被覆盖,因为设备旋转时会调用onCreate()。因此,当您关闭对话框时,新的IabHelperhandleActivityResult()方法会被调用,但它与您之前传递给launchPurchaseFlow()requestCode不匹配,因此您的onPurchaseFinishedListener将无法收到通知。为了处理这种情况(即在对话框打开时旋转设备),您需要在onActivityResult()中自己处理requestCode。由于对话框已关闭,您需要模拟您在onPurchaseFinishedListener中所做的操作(查找用户是否实际购买了某些内容)。我只是调用了queryInventoryAsync()来查找这一点。
我不确定那是否是理想的解决方案,但对我来说效果很好。我尝试像你那样保留 IabHelper 的引用,但我遇到了一些奇怪的问题,它会失去设置状态,但又不允许我重新设置它。
最后我更新了计费工具类,使用了最新的安卓源代码。有一些错误修复还没有推送到 SDK 管理器中。其中大部分都是可疑的空值检查,但也有一些改进可以防止崩溃: 最新变更集

0

在 Stack Overflow 上尝试了很多建议都没有解决这个问题之后,我尝试了这个简单的 null 检查,它解决了这个问题:

首先我检查了 mHelper 是否为 null,然后创建一个新实例:

onCreate

if (mHelper == null) {
    mHelper = new IabHelper(this, base64EncodedPublicKey);
}

我在 mHelper 的 null 检查内再次添加了其他实现:

//noinspection ConstantConditions
if (mHelper != null){

    mGotInventoryListener = new IabHelper.QueryInventoryFinishedListener() {
        public void onQueryInventoryFinished(IabResult result, Inventory inventory) {
        }
    };
    mPurchaseFinishedListener = new IabHelper.OnIabPurchaseFinishedListener() {
        public void onIabPurchaseFinished(IabResult result, Purchase purchase) {
        }
    };
    mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
        public void onIabSetupFinished(IabResult result) {
        }
    });
}

当然,你应该处理辅助程序:

@Override
public void onDestroy() {
    super.onDestroy();
    if (mHelper != null)
        mHelper.dispose();
    mHelper = null;
}

如果问题仍然存在,请更新您的IabHelper类。

0
我尝试将mHelper设置为静态变量,并且只在(mHelper == null)的情况下实例化它,而不是在activity的onDestroy()方法中销毁它。同时,将上下文传递给IabHelper。这样一来,一旦设置完成,它就会一直存在,就不必再担心异步操作(由设备方向引起)了。
以下是我的代码概述:
static IabHelper mHelper;
public void onCreate(Bundle savedInstanceState) {
    ...
    if (mHelper == null) {
        mHelper = new IabHelper(getApplicationContext(), base64EncodedPublicKey);
        mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
            ...
        });
    }
    ...
}

protected void onDestroy() {
    ...
    // Don't do ANYTHING to mHelper, so it will stick around on orientation change
}

不确定这是否是正确的解决方法,但我想提及它以帮助其他人。


我也在考虑做同样的事情,但我不知道是否会有不良反应。保留IabHelper意味着我们将保持与InAppBillingService的永久服务连接。你有没有遇到过任何问题? - Catalin Morosan
这是一个老问题,但是...如果你看源代码,他们声明可以使用活动或应用程序上下文,因此传递应用程序上下文是可以的。https://code.google.com/p/marketbilling/source/browse/v3/src/com/example/android/trivialdrivesample/util/IabHelper.java?r=5f6b7abfd0534acd5bfc7c14436f4500c99e0358 - Darussian
2
这不是基本上一个内存泄漏吗,因为静态IabHelper会通过监听器持有对Activity的引用? - Steve M

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接