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

Posted

技术标签:

【中文标题】Android 应用内计费:无法启动异步操作,因为另一个异步操作(正在进行中)【英文标题】:Android in-app billing: Can't start async operation because another async operation (is in progress) 【发布时间】:2013-03-12 14:53:13 【问题描述】:

按照 Google 教程的建议,我正在使用 IabHelper 实用程序类,但这个错误对我造成了严重打击。显然IabHelper 不能同时运行多个异步操作。我什至设法在盘点仍在进行时尝试开始购买。

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

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

有解决这个问题的办法吗?

【问题讨论】:

对于阅读此问题的每个人,[b]向下滚动![/b] 并在此处使用 'onActivityResult()' sn-p,这就是答案 在 onActivityResult() 中调用 mHelper.handleActivityResult() 以便调用 flagAsync()。请参阅 Google 的 TrivialDrive 示例代码。 这些答案都不是真正干净的解决方案。我建议使用单线程执行器(Executor mExec = Executors.newSingleThreadExectuors()),然后构建一个包装类,使每个 IAB 调用成为阻塞可运行对象,并在此执行器上适当排队。 【参考方案1】:

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

在尝试购买之前,请致电flagEndAsync() 为您的IabHelper

你必须像这段代码那样做一些事情:

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

【讨论】:

【参考方案2】:

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

private void makePurchase() 

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

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

【讨论】:

【参考方案3】:

我的解决方案很简单

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

public boolean isAsyncInProgress() 
    return mAsyncInProgress;

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

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

【讨论】:

【参考方案4】:

确保在 Activity 的 onActivityResult 中调用 IabHelper 的 handleActivityResult,在 Fragment 的 onActivityResult 中调用 NOT

以下代码sn-p来自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。上面的片段经过编辑以反映当前示例,但在逻辑上与以前相同。

【讨论】:

我真的把头发拉到了这个 LOL 谢谢 这是正确的答案。所有其他解决方案都是黑客。即使破坏活动等也有效。这是正确的流程。 对于带有片段的应用内计费,请参阅此答案:http://***.com/a/22434995/529663 这是完美的。应标记为正确答案。 添加 if (mHelper == null) return;解决了我的问题。谢谢!【参考方案5】:

这个答案直接解决了@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 年的历史了,它仍然是一个持续存在的问题:(

【讨论】:

【参考方案6】:

如果你在片段中编码,那么你在 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);

【讨论】:

【参考方案7】:

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

【讨论】:

这是一个非常好的解决方案!除了打印堆栈跟踪之外,我建议提出一些处理异常的系统方法。【参考方案8】:

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

public Boolean getAsyncInProgress() 
    return mAsyncInProgress;

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

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

【讨论】:

【参考方案9】:

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

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

【讨论】:

无需在 IabHelper 上修改任何内容,只需在 UI 线程上运行 launchPurchaseFlow!【参考方案10】:

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

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

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

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

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

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

【讨论】:

你的意思是在调用“launchPurchaseFlow”方法之前,不是吗? 我对这个库很失望 code.google.com/p/marketbilling 查看所有未解决的问题。谷歌似乎没有修复。我觉得很重要…… :(我可以知道你面临哪个问题吗?@powder366 它很好用,如果你从另一个包调用它,别忘了在 IabHelper 中创建 public flagEndAsync() 方法 这实际上并不能解决同时发生的重叠异步调用的问题......这就是库首先进行此检查的原因。我实际上使用了在包装类中维护我自己的异步标志的方法,并在启动 IABHelper 的异步方法之前触发等待该标志的线程。这很丑陋,但实际上可以完成排队的工作。【参考方案11】:

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

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

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

每当调用其 onServiceConnected 时,IabHelper 代码都会调用此处理函数,这可能会发生多次。

onServiceDisconnected 的 Android documentation 表示:

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

这解释了问题。

可以说,IabHelper 不应该多次调用 onIabSetupFinished 侦听器函数,但另一方面,如果我已经这样做了,只需不从该函数中调用 queryInventory 即可解决我的应用程序中的问题,并且得到了结果。

【讨论】:

【参考方案12】:

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

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

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

【讨论】:

【参考方案13】:

只需检查活动上的 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);
        
    

【讨论】:

【参考方案14】:

我遇到了同样的问题,问题是我没有实现 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);
    

【讨论】:

【参考方案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();
    

【讨论】:

喜欢这种简单的方法,虽然它不是最好的。【参考方案16】:

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

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

【讨论】:

【参考方案17】:

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

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 次)并获得计费弹出窗口

希望对你有帮助

【讨论】:

【参考方案18】:

或者,您可以在此处获取最新的 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;

【讨论】:

嗨,只是重新检查?您是否使用此解决方案上传了应用程序并且对它感到满意?我应该闭着眼睛继续你的解决方案吗?请指教:)【参考方案19】:

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

IabUtil 确实有问题,它不能正确地调用自己的异步任务。一套完整的必要解决方法来稳定这个东西:

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

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

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

【讨论】:

或者你可以用从Exception继承的一些新异常类替换flagEndAsync中抛出的IllegalStateException,以了解你应该在哪里放置try/catch块。此强制代码静态分析器会在您未处理自定义异常的地方生成错误。 正如下面 user2574426 提到的,repo 包括对 IabHelper.java 的几个修复,以解决这个问题。无论出于何种原因,我的 SDK (API 17) 示例代码不包含这些修复。详情请访问code.google.com/p/marketbilling/source/browse。 MutantXenu,我要不要闭上眼睛继续使用 user2574426 解决方案:) 请指教。 现在我在 flagStartAsync 上有这个异常......这是怎么回事? 我也很失望,直到我发现我只是没有正确调整代码。下面进一步指出的“OnActivityResult()”为我修复了它。我们编码人员只是不擅长遵循指示;)

以上是关于Android 应用内计费:无法启动异步操作,因为另一个异步操作(正在进行中)的主要内容,如果未能解决你的问题,请参考以下文章

适用于 android、ios 和 blackberry 的 Codenameone 应用内计费

Android计费库错误

Android 应用内计费示例中的严重问题?

android N 无法在应用计费 AIDL 中编译

Android:在应用计费增值税

如何在 Android 应用程序中实现应用内计费?