Android应用内结算:无法启动异步操作,因为另一个异步操作(正在进行中)

时间:2013-03-22 16:42:12

标签: android android-billing

我正在使用Google教程推荐的IabHelper实用程序类,而且我受到此错误的严重打击。显然IabHelper无法同时运行多个异步操作。我甚至设法通过尝试开始购买,同时库存盘点仍在进行中。

我已经尝试按照建议here在我的主类中实现onActivityResult,但在错误发生之前我甚至没有调用该方法。然后我找到了this,但我不知道在哪里找到这个flagEndAsync方法 - 它不在IabHelper类中。

现在我正在寻找解决这个问题的方法(没有重新实现整个她 - )。我能想到的唯一解决方案是创建一个在异步任务启动之前检查的布尔字段asyncActive,如果有另一个任务处于活动状态,则不执行此操作。但是这有许多其他问题,并且不适用于各种活动。此外,我希望有一个异步任务队列,并在允许的情况下尽快运行,而不是根本不运行。

针对此问题的任何解决方案?

19 个答案:

答案 0 :(得分:104)

一个简单的棘手解决方案

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

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

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

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

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

答案 1 :(得分:79)

确保您在活动handleActivityResult中调用IabHelper onActivityResult,在片段onActivityResult中调用 NOT

以下代码段来自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.");
    }
}

更新:

  • 现在有In-app Billing Version 3 API(2013年的版本是什么?)
  • 代码示例已移至Github。编辑上面的代码段以反映当前样本,但在逻辑上与以前相同。

答案 2 :(得分:41)

这不容易破解,但我找到了所需的解决方法。谷歌最近很失望,他们的Android网站变得一团糟(很难找到有用的信息),他们的示例代码很差。几年前,当我做一些Android开发时,它变得更加容易!这是另一个例子...

事实上,IabUtil是错误的,它没有正确地调用它自己的异步任务。完整的必要解决方法来稳定这件事:

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

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

3)使用try/catch进行环绕声调用,以捕捉可能发生的IllegalStateException,并以此方式处理。

答案 3 :(得分:13)

我最终做了类似金太郎的事情。但是将mHelper.flagEndAsync()添加到catch的末尾。用户仍然可以获得祝酒,但是下次他们按下购买按钮时,异步操作已经被杀死,购买按钮已准备就绪。

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();
    }
}

答案 4 :(得分:11)

flagEndAsync()文件中查找IabHelper.java并将其设为公开功能。

在为flagEndAsync()尝试购买IabHelper之前。

你必须像这段代码那样做somthig:

mHelper.flagEndAsync();
mHelper.launchPurchaseFlow(AboutActivity.this, SKU_PREMIUM, RC_REQUEST, mPurchaseFinishedListener, "payload-string");

答案 5 :(得分:9)

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

@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
    // ...
}

答案 6 :(得分:7)

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

public Boolean getAsyncInProgress() {
    return mAsyncInProgress;
}

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

if (!mHelper.getAsyncInProgress())
    //launch purchase
else
    Log.d(TAG, "Async in progress already..)

答案 7 :(得分: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次)并获取结算弹出窗口

希望有所帮助

答案 8 :(得分: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);
        }
    }
}

答案 9 :(得分: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);
}

答案 10 :(得分:1)

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

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

我的应用程序的编写方式,在我有一个完整的queryInventory后,我才能调用launchPurchaseFlow,而且我只从我的onIabSetupFinished处理函数调用queryInventory。

IabHelper代码将在调用onServiceConnected时调用此处理函数,这可能会多次发生。

onServiceDisconnected的Android documentation说:

  

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

解释了这个问题。

可以说,IabHelper不应该多次调用onIabSetupFinished监听器函数,但另一方面,如果我已经完成它并且不通过在此函数中调用queryInventory来修复我的应用程序中的问题是微不足道的。得到了结果。

答案 11 :(得分: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();
}

答案 12 :(得分:1)

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

3月15日的版本为我解决了这个问题。 (注意15日未提交任何更改的其他文件)

当“android.test.canceled”是发送的SKU时,我仍然需要修复在测试期间由null intent intent附加引起的崩溃。我改变了:

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;

答案 13 :(得分: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);
    }
}

答案 14 :(得分:0)

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

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

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

答案 15 :(得分: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
    }
}

我的[购买]按钮调用此launchPurchaseWorkflow()并传递SKU和按钮所在的活动(或者如果您是片段中的封闭活动)

注意:请务必公开IabHelper.flagEndAsync()

希望Google能够在不久的将来改进此代码;这个问题大概3岁了,它仍然是一个持续存在的问题:(

答案 16 :(得分:0)

我的解决方案很简单

1。)使mAsyncInProgress变量在IabHelper

之外可见
public boolean isAsyncInProgress() {
    return mAsyncInProgress;
}

2。)在你的活动中使用它,如:

...
if (mIabHelper.AsyncInProgress()) return;
mIabHelper.queryInventoryAsync(...);
...

答案 17 :(得分: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

答案 18 :(得分:-2)

我有同样的问题,但它已经解决了! 我想你一定不能跑步" launchPurchaseFlow"在UI线程上,尝试在UI线程上运行launchPurchaseFlow, 它会工作正常!

mActivity.runOnUiThread(new Runnable(){
            public void run(){
                mHelper.launchPurchaseFlow(mActivity, item, 10001, mPurchaseFinishedListener,username);
            }
        });