In-App-Abrechnung für Android: Der asynchrone Vorgang kann nicht gestartet werden, da ein anderer asynchroner Vorgang ausgeführt wird.

70

Ich verwende die IabHelperDienstprogrammklassen, wie im Tutorial von Google empfohlen, und bin von diesem Fehler schwer betroffen. Anscheinend IabHelperkönnen nicht mehrere asynchrone Vorgänge gleichzeitig ausgeführt werden. Ich habe es sogar geschafft, einen Kauf zu starten, während die Inventur noch lief.

Ich habe bereits versucht, onActivityResultwie hier vorgeschlagen in meiner Hauptklasse zu implementieren , aber ich erhalte nicht einmal einen Aufruf dieser Methode, bevor der Fehler auftritt. Dann fand ich das , aber ich habe keine Ahnung , wo diese finden flagEndAsyncMethode - es ist nicht in der IabHelperKlasse.

Jetzt suche ich nach einem Weg, um dies zu umgehen (ohne den ganzen Knall neu zu implementieren). Die einzige Lösung, die mir in den Sinn kommt, besteht darin, ein boolesches Feld zu erstellen asyncActive, das vor dem Start einer asynchronen Aufgabe überprüft wird, und dies nicht zu tun, wenn eine andere Aufgabe aktiv ist. Aber das hat viele andere Probleme und funktioniert nicht über Aktivitäten hinweg. Außerdem würde ich es vorziehen, eine asynchrone Task-Warteschlange einzurichten und auszuführen, sobald dies zulässig ist, anstatt überhaupt nicht ausgeführt zu werden.

Irgendwelche Lösungen für dieses Problem?

Wouter
quelle
5
Für alle, die diese Frage lesen, [b] scrollen Sie nach unten! [/ B] und verwenden Sie dort das Snippet 'onActivityResult ()', das ist die Antwort
Boy
Rufen Sie mHelper.handleActivityResult () in onActivityResult () auf, damit flagAsync () aufgerufen wird. Siehe den TrivialDrive-Beispielcode von Google.
Amit
Keine dieser Antworten ist wirklich eine saubere Lösung. Ich würde empfehlen, einen einzelnen Thread-Executor (Executor mExec = Executors.newSingleThreadExectuors ()) zu verwenden und dann eine Wrapper-Klasse zu erstellen, die jeden IAB-Aufruf zu einem blockierbaren Runnable macht, der auf diesem Executor entsprechend in die Warteschlange gestellt wird.
Gkanwar

Antworten:

106

Eine einfache knifflige Lösung

Fügen Sie vor dem Aufrufen der purchaseItem- Methode einfach diese Zeile hinzu

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

Ihr Code sieht also so aus

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

Hinweis: Vergessen Sie nicht, die Methode public flagEndAsync () in IabHelper zu erstellen, wenn Sie sie von einem anderen Paket aus aufrufen.

Khan
quelle
12
Sie meinen, bevor Sie die Methode "launchPurchaseFlow" aufrufen, nicht wahr?
Fran Marzoa
5
Ich bin in dieser Bibliothek sehr enttäuscht. Code.google.com/p/marketbilling sieht alle offenen Fragen. Google scheint nicht zu beheben. Ich finde es wichtig ...
Pulver366
:( Darf ich wissen, mit welchem ​​Problem Sie konfrontiert sind? @ Powder366
Khan
7
Es funktioniert großartig, vergessen Sie jedoch nicht, die öffentliche flagEndAsync () -Methode in IabHelper zu erstellen, wenn Sie sie aus einem anderen Paket
aufrufen
1
Dies löst nicht das Problem überlappender asynchroner Aufrufe, die gleichzeitig auftreten. Deshalb hat die Bibliothek diese Prüfung überhaupt erst. Ich habe tatsächlich die Methode verwendet, mein eigenes asynchrones Flag in einer Wrapper-Klasse beizubehalten und einen Thread auszulösen, der auf dieses Flag wartet, bevor die asynchronen Methoden des IABHelper gestartet werden. Es ist hässlich, macht aber tatsächlich die Warteschlange.
Gkanwar
79

Stellen Sie sicher, dass Sie die IabHelper handleActivityResultin der Aktivität onActivityResultund NICHT in den Fragmenten aufrufen onActivityResult.

Das folgende Codefragment stammt aus der MainActivity von TrivialDrive :

@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.");
    }
}

Aktualisieren:

  • Es gibt jetzt eine In-App-Abrechnungs-API für Version 3 (wie war die Version 2013?)
  • Das Codebeispiel wurde nach Github verschoben . Das obige Snippet wurde bearbeitet, um das aktuelle Beispiel wiederzugeben, ist jedoch logisch das gleiche wie zuvor.
Jonathan Lin
quelle
Ich zog buchstäblich meine Haare auf diesem einen LOL Danke
Asad Khan
1
Dies ist die richtige Antwort. Alle anderen Lösungen sind ein Hack. Dies funktioniert auch, wenn Aktivitäten usw. zerstört werden. Dies ist der richtige Ablauf.
Meanman
1
Informationen
Dies ist perfekt. Sollte als richtige Antwort markiert werden.
Daniel Wood
Hinzufügen von if (mHelper == null) return; mein Problem behoben. Vielen Dank!
Iulian
41

Dies war nicht leicht zu knacken, aber ich fand die erforderlichen Problemumgehungen. In letzter Zeit ziemlich enttäuscht von Google, sind ihre Android-Websites zu einem Chaos geworden (sehr schwer zu findende nützliche Informationen) und ihr Beispielcode ist schlecht. Als ich vor ein paar Jahren eine Android-Entwicklung machte, ging alles so viel einfacher! Dies ist ein weiteres Beispiel dafür ...

In der Tat ist IabUtil fehlerhaft, es ruft seine eigenen asynchronen Aufgaben nicht richtig ab. Der komplette Satz notwendiger Problemumgehungen, um dieses Ding zu stabilisieren:

1) Methode flagEndAsyncöffentlich machen. Es ist da, nur nicht sichtbar.

2) Lassen Sie jeden Listener anrufen iabHelper.flagEndAsync, um sicherzustellen, dass die Prozedur ordnungsgemäß als abgeschlossen markiert ist. es scheint bei allen Zuhörern nötig zu sein.

3) Surround-Anrufe mit einem try/catch, um das zu erfassen, IllegalStateExceptionwas auftreten kann, und behandeln Sie es auf diese Weise.

Wouter
quelle
2
Oder Sie können IllegalStateExceptioneingeworfen flagEndAsyncdurch eine neue Ausnahmeklasse ersetzen , von der geerbt wurde, um Exceptionzu verstehen, wo Sie try/catchBlöcke platzieren sollten. Dieser Force Code Static Analyzer generiert Fehler, bei denen Ihre benutzerdefinierte Ausnahme nicht behandelt wurde.
Timur Gilfanov
Wie unten von user2574426 erwähnt, enthält das Repo mehrere Korrekturen an IabHelper.java, die dies beheben. Aus irgendeinem Grund enthält mein SDK-Beispielcode (API 17) diese Korrekturen NICHT. Weitere Informationen finden Sie unter code.google.com/p/marketbilling/source/browse .
MutantXenu, soll ich mit der Lösung user2574426 mit geschlossenen Augen fortfahren :) Bitte um Rat.
Hasan
Jetzt habe ich diese Ausnahme auf flagStartAsync ... was ist los?
Android-Entwickler
1
Ich war auch enttäuscht, bis ich herausfand, dass ich den Code einfach nicht richtig angepasst habe. Das weiter unten erwähnte 'OnActivityResult ()' hat es für mich behoben. Wir Programmierer sind einfach nicht gut darin, Anweisungen zu folgen;)
Junge
15

Am Ende habe ich etwas Ähnliches wie Kintaro gemacht. Aber mHelper.flagEndAsync () am Ende des Catch hinzugefügt. Der Benutzer erhält weiterhin den Toast, aber wenn er das nächste Mal die Kauftaste drückt, wurde der asynchrone Vorgang abgebrochen und die Kauftaste ist wieder einsatzbereit.

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();
    }
}
NadtheVlad
quelle
Gefiel dieser einfache Ansatz, obwohl es nicht der beste ist.
AndroidMechanic - Viral Patel
11

Suchen Sie flagEndAsync()in der IabHelper.javaDatei und machen Sie sie öffentlich .

Bevor Sie den Kauf versuchen, rufen Sie flagEndAsync()Ihren an IabHelper.

Sie müssen etwas wie diesen Code tun:

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

Ich hatte das gleiche Problem, bis ich auf einen anderen SO-Thread stieß . Ich füge eine überarbeitete Version des Codes hinzu, der in dem anderen Thread enthalten ist, den Sie in Ihre Aktivität aufnehmen müssen, um den Kauf zu initialisieren.

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

Ein einfacher Trick, der es für mich getan hat, war das Erstellen einer Methode in IabHelper:

public Boolean getAsyncInProgress() {
    return mAsyncInProgress;
}

und dann in Ihrem Code überprüfen Sie einfach:

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

Wirklich nerviges Problem. Hier ist eine schnelle und schmutzige Lösung, die in Bezug auf den Code nicht perfekt ist, aber benutzerfreundlich ist und schlechte Bewertungen und Abstürze vermeidet:

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

Auf diese Weise muss der Benutzer nur ein weiteres Mal tippen (im schlimmsten Fall zweimal) und erhält das Abrechnungs-Popup

Ich hoffe es hilft

Don
quelle
3

Überprüfen Sie einfach den onActivityResult requestCode für die Aktivität und übergeben Sie ihn einfach an das Fragment, wenn er mit dem PURCHASE_REQUEST_CODE übereinstimmt, den Sie beim Kauf verwendet haben.

Wenn Sie das Fragment in der FragmentTransaction hinzufügen oder ersetzen, legen Sie einfach ein Tag fest:

fTransaction.replace(R.id.content_fragment, fragment, fragment.getClass().getName());

Dann auf onActivityResult Ihrer Aktivität

@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);
        }
    }
}
Roberto B.
quelle
2

Wenn Sie in Fragment codieren, dann Sie diesen Code in 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);
}
tej shah
quelle
1

Oder Sie können die neueste IabHelper.java-Datei hier herunterladen: https://code.google.com/p/marketbilling/source/browse/

Die Version vom 15. März hat dies für mich behoben. (Beachten Sie, dass am 15. andere Dateien ohne Änderungen festgeschrieben wurden.)

Ich musste immer noch einen Absturz beheben, der während des Tests durch Extras mit Nullabsicht verursacht wurde, als "android.test.canceled" die gesendete SKU war. Ich habe mich verändert:

int getResponseCodeFromIntent(Intent i) {
    Object o = i.getExtras().get(RESPONSE_CODE);

zu:

int getResponseCodeFromIntent(Intent i) {
    Object o = i.getExtras() != null ? i.getExtras().get(RESPONSE_CODE) : null;
user2574426
quelle
Hallo, nur nochmal prüfen? Haben Sie eine App mit dieser Lösung hochgeladen und sind damit zufrieden? Soll ich mit geschlossenen Augen mit Ihrer Lösung fortfahren? Bitte um Rat :)
Hasan
1

Ich hatte dieses Problem gelegentlich und in meinem Fall habe ich es darauf zurückgeführt, dass die onServiceConnected-Methode in IabHelper mehrmals aufgerufen werden kann, wenn der zugrunde liegende Dienst die Verbindung trennt und wieder herstellt (z. B. aufgrund einer intermittierenden Netzwerkverbindung).

In meinem Fall lauteten die spezifischen Vorgänge "Asynchroner Vorgang kann nicht gestartet werden (Inventar aktualisieren), da ein anderer asynchroner Vorgang (launchPurchaseFlow) ausgeführt wird."

So wie meine App geschrieben ist, kann ich launchPurchaseFlow erst aufrufen, nachdem ich ein abgeschlossenes queryInventory hatte, und ich rufe QueryInventory nur über meine onIabSetupFinished-Handlerfunktion auf.

Der IabHelper-Code ruft diese Handlerfunktion immer dann auf, wenn onServiceConnected aufgerufen wird. Dies kann mehrmals vorkommen.

Die Android-Dokumentation für onServiceDisconnected lautet:

Wird aufgerufen, wenn eine Verbindung zum Dienst unterbrochen wurde. Dies geschieht normalerweise, wenn der Prozess, der den Dienst hostet, abgestürzt ist oder abgebrochen wurde. Dadurch wird die ServiceConnection selbst nicht entfernt. Diese Bindung an den Service bleibt aktiv und Sie erhalten einen Anruf an onServiceConnected (ComponentName, IBinder), wenn der Service das nächste Mal ausgeführt wird.

das erklärt das Problem.

Möglicherweise sollte IabHelper die Listener-Funktion onIabSetupFinished nicht mehr als einmal aufrufen, aber andererseits war es trivial, das Problem in meiner App zu beheben, indem einfach nicht queryInventory aus dieser Funktion heraus aufgerufen wurde, wenn ich dies bereits getan und die Ergebnisse erhalten habe .

Sheltond
quelle
1

Ein weiteres wichtiges Problem bei der IabHelpr-Klasse ist die schlechte Wahl, RuntimeExcptions (IllegalStateException) in mehreren Methoden auszulösen. Das Auslösen von RuntimeExeptions aus Ihrem eigenen Code ist in den meisten Fällen nicht wünschenswert, da es sich um nicht aktivierte Ausnahmen handelt . Das ist wie das Sabotieren Ihrer eigenen Anwendung. Wenn sie nicht abgefangen werden, sprudeln diese Ausnahmen und stürzen Ihre App ab.

Die Lösung hierfür besteht darin, eine eigene aktivierte Ausnahme zu implementieren aktivierte und die IabHelper-Klasse so zu ändern, dass sie anstelle der IllegalStateException ausgelöst wird. Dadurch werden Sie gezwungen, diese Ausnahme überall dort zu behandeln, wo sie beim Kompilieren in Ihren Code geworfen werden könnte.

Hier ist meine benutzerdefinierte Ausnahme:

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

Sobald wir die Änderungen in der IabHelper-Klasse vorgenommen haben, können wir unsere aktivierte Ausnahme in unserem Code behandeln, in dem wir die Klassenmethoden aufrufen. Zum Beispiel:

try {
   setUpBilling(targetActivityInstance.allData.getAll());
} catch (MyIllegalStateException ex) {
    ex.printStackTrace();
}
Dimitar Darazhanski
quelle
Dies ist ein Teil einer wirklich guten Lösung! Ich würde empfehlen, eine systematische Methode zur Behandlung der Ausnahme zu finden, die nicht nur den Stack-Trace druckt.
Gkanwar
0

Ich hatte das gleiche Problem und das Problem war, dass ich die Methode onActivityResult nicht implementiert habe.

@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);
    }
}
Dominique
quelle
0

Ja, ich bin auch mit diesem Problem konfrontiert, aber ich habe es gelöst, aber ich habe es mit gelöst

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

Die obige Methode stoppt alle Flags. Seine Arbeit für mich muss überprüfen

sharma_kunal
quelle
0

Diese Antwort spricht direkt das Problem an, das @Wouter gesehen hat ...

Es ist wahr, onActivityResult()dass ausgelöst werden muss, wie viele Leute gesagt haben. Der Fehler ist jedoch, dass der Google-Code nicht ausgelöst wirdonActivityResult() bestimmten Umständen , dh wenn Sie beim Ausführen des Debug-Builds Ihrer App zweimal auf die Schaltfläche [KAUFEN] drücken.

Ein großes Problem ist außerdem, dass sich der Benutzer möglicherweise in einer wackeligen Umgebung befindet (z. B. Bus oder U-Bahn) und zweimal die [KAUFEN] -Taste drückt ... plötzlich haben Sie eine Ausnahme!

Zumindest Google hat diese peinliche Ausnahme https://github.com/googlesamples/android-play-billing/commit/07b085b32a62c7981e5f3581fd743e30b9adb4ed#diff-b43848e47f8a93bca77e5ce95b1c2d66 behoben

Im Folgenden ist aufgeführt, was ich in derselben Klasse implementiert habe, in der IabHelperinstanziiert wird (für mich in der Anwendungsklasse):

/**
 * 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
    }
}

Meine Schaltfläche [KAUFEN] ruft dies auf launchPurchaseWorkflow()und übergibt die SKU und die Aktivität, in der sich die Schaltfläche befindet (oder, wenn Sie sich in einem Fragment befinden, die umschließende Aktivität).

HINWEIS: Stellen Sie sicher, dass Sie IabHelper.flagEndAsync()öffentlich machen .

Hoffentlich wird Google diesen Code in naher Zukunft verbessern. Dieses Problem ist ungefähr 3 Jahre alt und es ist immer noch ein anhaltendes Problem :(

Irgendwer irgendwo
quelle
0

Meine Lösung ist einfach

1.) Machen Sie die Variable mAsyncInProgress außerhalb von IabHelper sichtbar

public boolean isAsyncInProgress() {
    return mAsyncInProgress;
}

2.) Verwenden Sie dies in Ihrer Aktivität wie folgt:

...
if (mIabHelper.AsyncInProgress()) return;
mIabHelper.queryInventoryAsync(...);
...
braintrapp
quelle
0

Eine wenig modifizierte Version von NadtheVlads Antwort, die wie Charme wirkt

private void makePurchase() {

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

Die Logik ist einfach: Fügen Sie das launchPurchaseFlow()Ding einfach in eine Methode ein und verwenden Sie die Rekursion im catch-Block. Sie müssen noch flagEndAsync()aus der IabHelperKlasse veröffentlichen.

cht
quelle
-2

Ich habe das gleiche Problem, aber es wurde behoben! Ich denke, Sie dürfen nicht "launchPurchaseFlow" auf dem UI-Thread ausführen, versuchen Sie, launchPurchaseFlow auf dem UI-Thread auszuführen, es würde gut funktionieren!

mActivity.runOnUiThread(new Runnable(){
            public void run(){
                mHelper.launchPurchaseFlow(mActivity, item, 10001, mPurchaseFinishedListener,username);
            }
        });
Shen Guow Lee
quelle
Sie müssen nichts an IabHelper ändern. Führen Sie einfach launchPurchaseFlow auf dem UI-Thread aus!
Shen Guow Lee