web-dev-qa-db-ja.com

Androidアプリ内課金:別の非同期操作が進行中のため、非同期操作を開始できません)

Googleのチュートリアルで推奨されているように、IabHelperユーティリティクラスを使用していますが、このエラーに苦しんでいます。明らかにIabHelperは同時に複数の非同期操作を実行できません。棚卸しがまだ進行中の間に購入を開始しようとすることで、なんとか成功しました。

here のように、メインクラスにonActivityResultを実装しようとしましたが、エラーが発生する前にそのメソッドを呼び出すこともできません。それから、私は this を見つけましたが、このflagEndAsyncメソッドがどこにあるのかわかりません-IabHelperクラスにはありません。

今、私はこれを回避する方法を探しています(シバン全体を再実装することなく)。私が考えることができる唯一の解決策は、非同期タスクが開始される前にチェックされるブールフィールドasyncActiveを作成することであり、アクティブな別のタスクがある場合は実行されません。しかし、これには他にも多くの問題があり、アクティビティ間で機能しません。また、まったく実行しないのではなく、許可されたらすぐに非同期タスクをキューに入れて実行したいです。

この問題の解決策はありますか?

69
Wouter

簡単なトリッキーなソリューション

purchaseItemメソッドを呼び出す前に、この行を追加してください

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

あなたのコードはこのように見えます

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

注:別のパッケージから呼び出す場合は、IabHelperでpublic flagEndAsync()メソッドを作成することを忘れないでください。

104
Khan

必ずアクティビティのhandleActivityResultでIabHelperのonActivityResultを呼び出し、[〜#〜] not [〜#〜]フラグメントの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 に移動しました。上記のスニペットは現在のサンプルを反映するように編集されていますが、論理的には以前と同じです。
79
Jonathan Lin

これをクラックするのは簡単ではありませんでしたが、必要な回避策が見つかりました。最近Googleに失望し、Androidウェブサイトは混乱し(有用な情報を見つけるのが非常に困難)、サンプルコードは貧弱です。数年前にAndroidの開発を行っていたとき、すべてがとても簡単になりました!これはさらに別の例です...

実際、IabUtilはバグが多く、独自の非同期タスクを適切に呼び出しません。このことを安定させるために必要な回避策の完全なセット:

1)メソッドflagEndAsyncをパブリックにします。そこにあり、見えないだけです。

2)すべてのリスナーがiabHelper.flagEndAsyncを呼び出して、プロシージャが適切に終了とマークされていることを確認します。すべてのリスナーで必要なようです。

3)呼び出しをtry/catchで囲んで、発生する可能性のあるIllegalStateExceptionをキャッチし、そのように処理します。

41
Wouter

私は金太郎に似た何かをすることになりました。ただし、キャッチの最後に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();
    }
}
13
NadtheVlad

_IabHelper.Java_ファイル内でflagEndAsync()を見つけてpublic functionにします。

購入を試みる前に、IabHelperflagEndAsync()を呼び出します。

あなたはこのコードのようなsomthigをしなければなりません:

_mHelper.flagEndAsync();
mHelper.launchPurchaseFlow(AboutActivity.this, SKU_PREMIUM, RC_REQUEST, mPurchaseFinishedListener, "payload-string");
_
11
abas nikzad

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
    // ...
}
9
nyaray

私のためにそれをした簡単なトリックは、IabHelperでメソッドを作成することでした:

public Boolean getAsyncInProgress() {
    return mAsyncInProgress;
}

そして、あなたのコードで、単にチェックしてください:

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

本当に迷惑な問題。完全なコードではないが、それはユーザーフレンドリーで、悪い評価とクラッシュを回避する迅速で汚いソリューションです:

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回)をタップするだけで、請求ポップアップが表示されます。

それが役に立てば幸い

6
Don

アクティビティの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);
        }
    }
}
3
Roberto B.

フラグメントにコーディングする場合、このコードは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);
}
2
tej shah

IabHelprクラスのもう1つの大きな問題は、複数のメソッドでRuntimeExcptions(IllegalStateException)をスローする選択が悪いことです。ほとんどの場合、独自のコードからRuntimeExeptionsをスローすることは、チェックされていない例外であるため、望ましくありません。これは、独自のアプリケーションを妨害するようなものです。キャッチされない場合、これらの例外はバブルアップし、アプリケーションをクラッシュさせます。

これに対する解決策は、独自のチェック済み例外を実装し、IllegalStateExceptionではなくIabHelperクラスを変更してスローすることです。これにより、コンパイル時にコードでスローされる可能性のあるすべての場所でこの例外を処理する必要があります。

ここに私のカスタム例外があります:

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

または、ここから最新のIabHelper.Javaファイルを入手できます。 https://code.google.com/p/marketbilling/source/browse/

3月15日のバージョンでこれが修正されました。 (変更のない他のファイルは15日にコミットされたことに注意してください)

「Android.test.canceled」が送信されたSKUである場合に、nullインテントの追加によりテスト中に発生した1つのクラッシュを修正する必要がありました。私が変更され:

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;
1
user2574426

私は時々この問題を抱えていましたが、私の場合、基礎となるサービスが切断および再接続した場合に断続的なネットワーク接続のためにIabHelperのonServiceConnectedメソッドを複数回呼び出すことができるという事実まで追跡しました。

私の場合の具体的な操作は、「別の非同期操作(launchPurchaseFlow)が進行中のため、非同期操作を開始できない(インベントリを更新する)」でした。

アプリの作成方法では、queryInventoryが完了するまでlaunchPurchaseFlowを呼び出すことができず、onIabSetupFinishedハンドラー関数からqueryInventoryのみを呼び出します。

IabHelperコードは、onServiceConnectedが呼び出されるたびにこのハンドラー関数を呼び出します。これは複数回発生する可能性があります。

Androidドキュメント onServiceDisconnectedの場合:

サービスへの接続が失われたときに呼び出されます。これは通常、サービスをホストしているプロセスがクラッシュまたは強制終了されたときに発生します。これはServiceConnection自体を削除しません。サービスへのこのバインディングはアクティブなままであり、サービスの次回実行時にonServiceConnected(ComponentName、IBinder)の呼び出しを受け取ります。

問題を説明します。

おそらく、IabHelperはonIabSetupFinishedリスナー関数を2回以上呼び出すべきではありませんが、一方で、既に関数を実行して結果を取得している場合は、この関数内からqueryInventoryを呼び出さずにアプリの問題を修正するのは簡単でした。

1
sheltond

はい、私もこの問題に直面していますが、これを解決しましたが、

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

上記のメソッドは、すべてのフラグを停止します。私の仕事は確認する必要があります

0
sharma_kunal

私は同じ問題を抱えていましたが、問題は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
Dominique

チャームのように機能する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()を公開する必要があります。

0
cht

この回答は、@ Wouterが見た問題に直接対処しています...

多くの人が言っているように、onActivityResult()をトリガーする必要があるのは事実です。ただし、バグは、特定の状況、つまりアプリのデバッグビルドを実行しているときに[購入]ボタンを2回押すと、GoogleのコードがonActivityResult()をトリガーしないことです。

さらに、1つの大きな問題は、ユーザーが不安定な環境(バスや地下鉄など)にいる可能性があり、[購入]ボタンを2回押すことです...突然、例外が発生します!

少なくとも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
braintrapp