70

我正在使用IabHelperGoogle 教程推荐的实用程序类,但我受到了这个错误的严重打击。显然IabHelper不能同时运行多个异步操作。我什至设法在盘点仍在进行时尝试开始购买。

我已经尝试按照这里onActivityResult的建议在我的主类中实现,但在错误发生之前我什至没有调用该方法。然后我找到了这个,但我不知道在哪里可以找到这个方法——它不在课堂上。flagEndAsyncIabHelper

现在我正在寻找解决这个问题的方法(不重新实现整个she-bang)。我能想到的唯一解决方案是创建一个asyncActive在异步任务启动之前检查的布尔字段,如果有另一个任务处于活动状态,则不要这样做。但这还有许多其他问题,并且不适用于跨活动。此外,我更希望有一个异步任务队列并在允许时立即运行,而不是根本不运行。

这个问题有什么解决方案吗?

4

19 回答 19

106

一个简单的棘手的解决方案

在调用purchaseItem方法之前只需添加此行

  if (billingHelper != null) billingHelper.flagEndAsync();

所以你的代码看起来是这样的

 if (billingHelper != null) billingHelper.flagEndAsync();
 purchaseItem("android.test.purchased");

注意:如果你从另一个包中调用它,不要忘记在 IabHelper 中创建 public flagEndAsync() 方法。

于 2013-05-03T12:35:05.500 回答
80

确保handleActivityResult在 Activity 中调用 IabHelper onActivityResult,而不是在 Fragment 中onActivityResult

以下代码片段来自 TrivialDrive 的MainActivity

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    Log.d(TAG, "onActivityResult(" + requestCode + "," + resultCode + "," + data);
    if (mHelper == null) return;

    // Pass on the activity result to the helper for handling
    if (!mHelper.handleActivityResult(requestCode, resultCode, data)) {
        // not handled, so handle it ourselves (here's where you'd
        // perform any handling of activity results not related to in-app
        // billing...
        super.onActivityResult(requestCode, resultCode, data);
    }
    else {
        Log.d(TAG, "onActivityResult handled by IABUtil.");
    }
}

更新:

  • 现在有一个应用内结算版本 3 API(2013 年的版本是什么?)
  • 代码示例已移至Github。上面的片段经过编辑以反映当前样本,但在逻辑上与以前相同。
于 2013-12-10T02:59:45.960 回答
41

这不容易破解,但我找到了所需的解决方法。最近对 Google 很失望,他们的 Android 网站变得一团糟(很难找到有用的信息),而且他们的示例代码很差。几年前我在做一些 Android 开发时,一切都变得容易多了!这又是一个例子……

事实上,IabUtil 是有问题的,它没有正确地取消自己的异步任务。一套完整的必要变通办法来稳定这个东西:

1)flagEndAsync公开方法。它在那里,只是不可见。

2) 让每个侦听器调用iabHelper.flagEndAsync以确保程序被正确标记为完成;似乎所有听众都需要它。

3) 用 a 包围调用try/catch以捕获IllegalStateException可能发生的情况,并以这种方式处理。

于 2013-03-23T03:12:02.590 回答
15

我最终做了类似于金太郎的事情。但是将 mHelper.flagEndAsync() 添加到捕获的末尾。用户仍然会收到吐司,但下次他们按下购买按钮时,异步操作已被终止,购买按钮已准备好再次使用。

if (mHelper != null) {
    try {
    mHelper.launchPurchaseFlow(this, item, RC_REQUEST, mPurchaseFinishedListener, "");
    }       
    catch(IllegalStateException ex){
        Toast.makeText(this, "Please retry in a few seconds.", Toast.LENGTH_SHORT).show();
        mHelper.flagEndAsync();
    }
}
于 2014-02-02T00:56:59.723 回答
11

flagEndAsync()IabHelper.java文件中找到并将其更改为公共函数。

flagEndAsync()在尝试为您的IabHelper购买电话之前

您必须像以下代码一样执行 somthig:

mHelper.flagEndAsync();
mHelper.launchPurchaseFlow(AboutActivity.this, SKU_PREMIUM, RC_REQUEST, mPurchaseFinishedListener, "payload-string");
于 2016-09-19T02:30:09.083 回答
9

在我偶然发现另一个 SO 线程之前,我遇到了同样的问题。我包括在另一个线程中找到的代码的修改版本,您需要将其包含在初始化购买的活动中。

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    // Pass on the activity result to the helper for handling
    // NOTE: handleActivityResult() will update the state of the helper,
    // allowing you to make further calls without having it exception on you
    if (billingHelper.handleActivityResult(requestCode, resultCode, data)) {
        Log.d(TAG, "onActivityResult handled by IABUtil.");
        handlePurchaseResult(requestCode, resultCode, data);
        return;
    }

    // What you would normally do
    // ...
}
于 2013-08-16T10:14:05.177 回答
7

为我做的一个简单技巧是在 IabHelper 中创建一个方法:

public Boolean getAsyncInProgress() {
    return mAsyncInProgress;
}

然后在您的代码中,只需检查:

if (!mHelper.getAsyncInProgress())
    //launch purchase
else
    Log.d(TAG, "Async in progress already..)
于 2014-10-04T15:35:10.217 回答
6

真是烦人的问题。这是一个快速而肮脏的解决方案,它在代码方面并不完美,但它对用户友好并且避免了差评和崩溃:

if (mHelper != null) {
        try {
        mHelper.launchPurchaseFlow(this, item, RC_REQUEST, mPurchaseFinishedListener, "");
        }       
        catch(IllegalStateException ex){
            Toast.makeText(this, "Please retry in a few seconds.", Toast.LENGTH_SHORT).show();
        }
    }

这样,用户只需再点击一次(最坏的情况是 2 次)并获得计费弹出窗口

希望能帮助到你

于 2013-07-23T12:20:59.213 回答
3

只需检查活动上的 onActivityResult requestCode,如果它与您在购买时使用的 PURCHASE_REQUEST_CODE 匹配,只需将其传递给片段。

当您在 FragmentTransaction 中添加或替换片段时,只需设置一个标签:

fTransaction.replace(R.id.content_fragment, fragment, fragment.getClass().getName());

然后在您的活动的 onActivityResult

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if(requestCode == PurchaseFragment.PURCHASE_REQUEST_CODE) {
        PurchaseFragment fragment = getSuportFragmentManager().findFragmentByTag(PurchaseFragment.class.getNAme());
        if(fragment != null) {
            fragment.onActivityResult(requestCode, resultCode, data);
        }
    }
}
于 2014-04-02T07:49:29.743 回答
2

如果你在片段中编码,那么你在 IabHelper.java 中的这段代码

void flagStartAsync(String operation) {
    if (mAsyncInProgress) {
        flagEndAsync();
    }
    if (mAsyncInProgress) throw new IllegalStateException("Can't start async operation (" +
            operation + ") because another async operation(" + mAsyncOperation + ") is in progress.");
    mAsyncOperation = operation;
    mAsyncInProgress = true;
    logDebug("Starting async operation: " + operation);
}
于 2016-01-21T13:05:05.480 回答
1

或者,您可以在此处获取最新的 IabHelper.java 文件:https ://code.google.com/p/marketbilling/source/browse/

3 月 15 日的版本为我解决了这个问题。(注意其他没有变化的文件是在 15 号提交的)

当“android.test.canceled”是发送的 SKU 时,我仍然必须修复在测试期间发生的一次崩溃,该崩溃是由 null intent extras 引起的。我变了:

int getResponseCodeFromIntent(Intent i) {
    Object o = i.getExtras().get(RESPONSE_CODE);

至:

int getResponseCodeFromIntent(Intent i) {
    Object o = i.getExtras() != null ? i.getExtras().get(RESPONSE_CODE) : null;
于 2013-07-11T21:37:10.590 回答
1

IabHelpr 类的另一个主要问题是在多个方法中抛出 RuntimeExcptions (IllegalStateException) 的错误选择。在大多数情况下,从您自己的代码中抛出 RuntimeExeptions 是不可取的,因为它们是未经检查的异常。这就像破坏您自己的应用程序 - 如果没有被捕获,这些异常将冒泡并导致您的应用程序崩溃。

对此的解决方案是实现您自己的已检查异常并更改 IabHelper 类以抛出它,而不是 IllegalStateException。这将迫使您在编译时可能在代码中抛出的任何地方处理此异常。

这是我的自定义异常:

public class MyIllegalStateException extends Exception {

    private static final long serialVersionUID = 1L;

    //Parameterless Constructor
    public MyIllegalStateException() {}

    //Constructor that accepts a message
    public MyIllegalStateException(String message)
    {
       super(message);
    }
}

一旦我们在 IabHelper 类中进行了更改,我们就可以在调用类方法的代码中处理已检查的异常。例如:

try {
   setUpBilling(targetActivityInstance.allData.getAll());
} catch (MyIllegalStateException ex) {
    ex.printStackTrace();
}
于 2014-11-02T01:02:15.050 回答
1

我偶尔会遇到这个问题,在我的情况下,我已经追踪到这样一个事实,即如果底层服务断开连接并重新连接(例如由于间歇性网络连接),是否可以多次调用 IabHelper 中的 onServiceConnected 方法。

我的具体操作是“无法启动异步操作(刷新库存),因为另一个异步操作(launchPurchaseFlow)正在进行中。”

根据我的应用程序的编写方式,在完成 queryInventory 之前,我无法调用 launchPurchaseFlow,并且我只能从我的 onIabSetupFinished 处理程序函数中调用 queryInventory。

每当调用它的 onServiceConnected 时,IabHelper 代码都会调用此处理函数,这可能不止一次发生。

onServiceDisconnected的Android 文档说:

当与服务的连接丢失时调用。这通常发生在托管服务的进程崩溃或被终止时。这不会删除 ServiceConnection 本身 - 与服务的绑定将保持活动状态,并且当服务下次运行时,您将收到对 onServiceConnected(ComponentName, IBinder) 的调用。

这解释了这个问题。

可以说,IabHelper 不应多次调用 onIabSetupFinished 侦听器函数,但另一方面,如果我已经完成它并得到结果,只需不从该函数中调用 queryInventory 来解决我的应用程序中的问题是微不足道的.

于 2014-06-19T11:18:15.560 回答
0

是的,我也面临这个问题,但我解决了这个问题,但我解决了使用

IabHelper mHelpermHelper = new IabHelper(inappActivity, base64EncodedPublicKey);
  mHelper.flagEndAsync();

上述方法停止所有标志。它对我的工作必须检查

于 2014-04-03T20:00:16.927 回答
0

我有同样的问题,问题是我没有实现 onActivityResult 方法。

@Override
protected void onActivityResult(int requestCode,
            int resultCode,
            Intent data)
{
    try
    {
        if (billingHelper == null)
        {
            return;
        } else if (!billingHelper.handleActivityResult(requestCode, resultCode, data))
        {
            super.onActivityResult(requestCode, resultCode, data);
        }
    } catch (Exception exception)
    {
        super.onActivityResult(requestCode, resultCode, data);
    }
}
于 2014-03-22T17:13:55.743 回答
0

NadtheVlad 答案的一个小修改版本,就像魅力一样

private void makePurchase() {

    if (mHelper != null) {
        try {
            mHelper.launchPurchaseFlow(getActivity(), ITEM_SKU, 10001, mPurchaseFinishedListener, "");
        }
        catch(IllegalStateException ex){
            mHelper.flagEndAsync();
            makePurchase();
        }
    }
}

逻辑很简单,只要把launchPurchaseFlow()东西放在一个方法中,在catch块中使用递归即可。你仍然需要在课堂上flagEndAsync()公开。IabHelper

于 2018-08-18T11:37:56.603 回答
0

我的解决方案很简单

1.) 使 mAsyncInProgress 变量在 IabHelper 之外可见

public boolean isAsyncInProgress() {
    return mAsyncInProgress;
}

2.)在您的活动中使用它,例如:

...
if (mIabHelper.AsyncInProgress()) return;
mIabHelper.queryInventoryAsync(...);
...
于 2018-02-04T13:23:30.820 回答
0

这个答案直接解决了@Wouter 看到的问题......

确实,onActivityResult()必须触发,就像许多人所说的那样。但是,错误在于 Google 的代码onActivityResult()在某些情况下没有触发,即当您在运行应用程序的调试版本时按两次 [BUY] 按钮时。

此外,一个主要问题是用户可能处于不稳定的环境中(例如公共汽车或地铁)并按下您的 [BUY] 按钮两次...突然间您遇到了一个例外!

至少谷歌修复了这个令人尴尬的异常https://github.com/googlesamples/android-play-billing/commit/07b085b32a62c7981e5f3581fd743e30b9adb4ed#diff-b43848e47f8a93bca77e5ce95b1c2d66

下面是我在IabHelper实例化的同一个类中实现的(对我来说,这是在 Application 类中):

/**
 * invokes the startIntentSenderForResult - which will call your activity's onActivityResult() when it's finished
 * NOTE: you need to override onActivityResult() in your activity.
 * NOTE2: check IAB code updates at https://github.com/googlesamples/android-play-billing/tree/master/TrivialDrive/app/src/main/java/com/example/android/trivialdrivesample/util
 * @param activity
 * @param sku
 */
protected boolean launchPurchaseWorkflow(Activity activity, String sku)
{
    if (mIabIsInitialized)
    {
        try
        {
            mHelper.launchPurchaseFlow(
                    activity,
                    sku,
                    Constants.PURCHASE_REQUEST_ID++,// just needs to be a positive number and unique
                    mPurchaseFinishedListener,
                    Constants.DEVELOPER_PAYLOAD);
            return true;//success
        }
        catch (IllegalStateException e)
        {
            mHelper.flagEndAsync();
            return launchPurchaseWorkflow(activity, sku);//recursive call
        }
    }
    else
    {
        return false;//failure - not initialized
    }
}

我的 [BUY] 按钮调用它launchPurchaseWorkflow()并传递 SKU 和按钮所在的活动(或者如果您在片段中,则传递封闭活动)

注意:一定要IabHelper.flagEndAsync()公开。

希望 Google 会在不久的将来改进此代码;这个问题大约有 3 年的历史了,它仍然是一个持续存在的问题 :(

于 2016-03-27T14:51:50.257 回答
-2

我有同样的问题,但它解决了!我认为您一定不要在 UI 线程上运行“launchPurchaseFlow”,尝试在 UI 线程上运行 launchPurchaseFlow,它会正常工作!

mActivity.runOnUiThread(new Runnable(){
            public void run(){
                mHelper.launchPurchaseFlow(mActivity, item, 10001, mPurchaseFinishedListener,username);
            }
        });
于 2014-03-15T02:49:52.520 回答