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 应用内计费:无法启动异步操作,因为另一个异步操作(正在进行中)的主要内容,如果未能解决你的问题,请参考以下文章