Android应用内结算:由于其他异步操作(正在进行中),因此无法启动异步操作


70

我正在使用IabHelperGoogle教程推荐的实用程序类,但这个错误对我造成了沉重的打击。显然IabHelper不能同时运行多个异步操作。我什至设法通过在库存盘点过程中开始购买来实现目标。

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

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

这个问题有解决方案吗?


5
对于每个阅读此问题的人,[b]向下滚动![/ b]并在其中使用“ onActivityResult()”代码段,这就是答案
Boy

在onActivityResult()中调用mHelper.handleActivityResult(),以便调用flagAsync()。请参阅Google的TrivialDrive示例代码。
2014年

这些答案都不是真正的解决方案。我建议使用单线程执行程序(Executor mExec = Executors.newSingleThreadExectuors()),然后构建一个包装器类,该包装器类使每个IAB调用一个可阻塞的可运行程序,并在此执行程序上适当排队。
gkanwar

Answers:


106

一个简单棘手的解决方案

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

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

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

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

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


12
您的意思是在调用“ launchPurchaseFlow”方法之前,不是吗?
Fran Marzoa

5
我对这个库感到非常失望code.google.com/p/marketbilling看到所有未解决的问题。Google似乎无法修复。我觉得这很重要...
powder366 2014年

:(我能知道您面对哪个问题吗?@ powder366
Khan Khan

7
它的工作原理非常好,只是如果您从另一个包中调用它,别忘了在IabHelper中公开public flagEndAsync()方法
Gennadiy Ryabkin 2014年

1
这实际上并不能解决一次发生重叠异步调用的问题……这就是为什么库首先要进行此检查的原因。我实际上使用了在包装类中维护我自己的异步标志的方法,并启动了一个等待该标志的线程,然后启动IABHelper的异步方法。这很丑陋,但实际上可以完成排队工作。
gkanwar

79

确保handleActivityResult在Activity的IabHelper中调用onActivityResult,而不是在Fragment的IabHelper中调用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。上面的代码段经过编辑以反映当前样本,但在逻辑上与以前相同。

我真的在这首大声笑上拉了我的头发谢谢
Asad Khan

1
这是正确的答案。所有其他解决方案都是黑客。即使破坏活动等,此方法也有效。这是正确的流程。
Meanman

1
有关包含片段的应用内结算的信息,请参见以下答案:http
//stackoverflow.com/a/22434995/529663

太棒了。应标记为正确答案。
Daniel Wood

添加if(mHelper == null)返回;解决了我的问题。谢谢!
尤利安

41

这很难破解,但是我找到了所需的解决方法。最近对Google感到非常失望,他们的Android网站变得一团糟(很难找到有用的信息),并且其示例代码很差。几年前,当我进行一些Android开发时,一切变得如此简单!这是另一个例子。

的确,IabUtil确实存在错误,它没有正确地调用自己的异步任务。稳定此问题的完整必要解决方案集:

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

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

3)用a环绕呼叫try/catch以捕获IllegalStateException可能发生的呼叫,并以这种方式处理。


2
或者,您可以使用继承自的新异常类来替换IllegalStateException抛出的flagEndAsync异常,Exception以了解应在何处放置try/catch块。此强制代码静态分析器会在您未处理自定义异常的地方生成错误。
Timur Gilfanov

如user2574426下文所述,该仓库包含针对IabHelper.java的多个修复程序,以解决此问题。无论出于何种原因,我的SDK(API 17)示例代码均不包含这些修复程序。有关详细信息,请访问code.google.com/p/marketbilling/source/browse

MutantXenu,我要闭着眼睛继续进行user2574426解决方案:)请指教。
哈桑

现在我在flagStartAsync上有此异常...这是怎么回事?
Android开发人员

1
我也感到失望,直到我发现我没有正确地修改代码。下面进一步指出的“ OnActivityResult()”为我修复了该问题。我们的编码员只是不善于遵循以下指示;)
男孩

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

喜欢这种简单的方法,尽管它不是最好的。
AndroidMechanic-Viral Patel 2015年

11

查找flagEndAsync()内部IabHelper.java文件并将其公开

尝试购买之前flagEndAsync(),请致电给您IabHelper

你必须像这样的代码做somthig:

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

9

在遇到另一个SO线程之前,我一直遇到同样的问题。我将在另一个线程中找到修改后的代码版本,您需要将其包含在初始化购买的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
    // ...
}

7

一个对我有用的简单技巧是在IabHelper中创建一个方法:

public Boolean getAsyncInProgress() {
    return mAsyncInProgress;
}

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

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

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次),即可弹出帐单

希望能帮助到你


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

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

1

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

3月15日的版本为我修复了此问题。(请注意,其他未更改的文件已于15日提交)

当发送“ android.test.canceled”作为SKU时,我仍然必须修复由于无效意向附加导致的测试期间发生的崩溃。我变了:

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;

嗨,只是重新检查?您是否使用此解决方案上传了一个应用程序,并且对此感到满意?我可以闭着眼睛继续您的解决方案吗?请咨询:)
hasan

1

我偶尔会遇到此问题,在我的情况下,我可以追溯到以下事实:如果基础服务断开连接并重新连接(例如,由于网络连接中断),则可以多次调用IabHelper中的onServiceConnected方法。

在我的情况下,具体操作是“由于另一个异步操作(launchPurchaseFlow)正在进行中,因此无法启动异步操作(刷新清单)”。

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

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

用于onServiceDisconnected的Android文档说:

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

这解释了问题。

可以说,IabHelper不应多次调用onIabSetupFinished侦听器函数,但另一方面,通过在我的应用程序中简单地不调用queryInventory(如果我已经完成并获得结果)来解决问题就很简单。 。


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

这是一个非常好的解决方案!我建议您提出一些系统的异常处理方法,而不仅仅是打印堆栈跟踪。
gkanwar

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

0

是的,我也遇到了这个问题,但是我解决了这个问题,但是我解决了

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

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


0

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

onActivityResult()正如许多人所说,这确实是必须触发的。但是,错误是onActivityResult()在某些情况下(例如,在运行应用程序的调试版本时,您两次按下[购买]按钮时)不会触发Google的代码。

另外,一个主要问题是用户可能处在摇晃的环境中(例如,公共汽车或地铁),并按了两次[BUY]按钮...突然间,您感到异常了!

至少Google修复了这个令人尴尬的异常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年了,仍然是一个持续存在的问题:(


0

我的解决方案很简单

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

public boolean isAsyncInProgress() {
    return mAsyncInProgress;
}

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

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

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全班公开。


-2

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

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

无需在IabHelper上进行任何修改,只需在UI线程上运行launchPurchaseFlow!
李国国2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.