web-dev-qa-db-fra.com

Facturation In-App Android: impossible de démarrer une opération async. Une autre opération async (en cours).

J'utilise les classes d'utilitaire IabHelper, comme recommandé par le didacticiel de Google, et cette erreur me frappe durement. Apparemment, IabHelper ne peut pas exécuter plusieurs opérations asynchrones en même temps. J'ai même réussi à le frapper en essayant de commencer un achat alors que la prise d'inventaire était toujours en cours.

J'ai déjà essayé d'implémenter onActivityResult dans ma classe principale comme suggéré ici , mais je ne reçois même pas d'appel à cette méthode avant que l'erreur ne survienne. Ensuite, j'ai trouvé ceci mais je ne sais pas où trouver cette méthode flagEndAsync - ce n'est pas dans la classe IabHelper.

Maintenant, je cherche un moyen de contourner ce problème (sans réimplémenter toute la bangue). La seule solution à laquelle je puisse penser est de créer un champ booléen asyncActive qui est vérifié avant le lancement d'une tâche asynchrone, et non de le faire si une autre tâche est active. Mais cela pose de nombreux autres problèmes et ne fonctionne pas pour toutes les activités. De plus, je préférerais qu'une file d'attente asynchrone soit mise en place et exécutée dès qu'elle est autorisée, au lieu de ne pas être exécutée du tout.

Des solutions à ce problème?

68
Wouter

Une solution simple et délicate

avant d'appeler purchaseItem method, ajoutez simplement cette ligne 

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

alors votre code a cette apparence 

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

Remarque: n'oubliez pas de rendre publique la méthode flagEndAsync () dans IabHelper si vous l'appelez à partir d'un autre package.

103
Khan

Assurez-vous d'appeler la variable handleActivityResult de IabHelper dans la variable onActivityResult de l'activité et NOT dans la variable onActivityResult du fragment.

L'extrait de code suivant provient de TrivialDrive's 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.");
    }
}

Mettre à jour: 

79
Jonathan Lin

Ce n'était pas facile à craquer mais j'ai trouvé les solutions de contournement nécessaires. Récemment déçus par Google, leurs sites Web Android sont devenus un désordre (il est très difficile de trouver des informations utiles) et leur code exemple est médiocre. Lorsque je faisais du développement sur Android il y a quelques années, tout était tellement plus facile! Ceci est encore un autre exemple de cela ...

En effet, IabUtil est un buggy, il n'appelle pas correctement ses propres tâches asynchrones. L'ensemble complet de solutions de contournement nécessaires pour stabiliser cette chose:

1) rendre la méthode flagEndAsync publique. C'est là, mais pas visible.

2) demandez à chaque auditeur d’appeler iabHelper.flagEndAsync pour s’assurer que la procédure est correctement marquée; il semble être nécessaire chez tous les auditeurs.

3) entourez les appels avec un try/catch pour intercepter la IllegalStateException qui peut survenir, et gérez-le de cette façon.

40
Wouter

J'ai fini par faire quelque chose de similaire à Kintaro. Mais ajouté mHelper.flagEndAsync () à la fin de la capture. L’utilisateur a toujours le pain grillé, mais lorsqu’il appuie sur le bouton d’achat, l’opération asynchrone est terminée et le bouton d’achat est prêt à fonctionner. 

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

Recherchez flagEndAsync() dans le fichier IabHelper.Java et rendez-le public function. 

Avant d'essayer d'acheter, appelez flagEndAsync() pour votre IabHelper.

vous devez faire quelque chose comme ce code: 

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

J'avais le même problème jusqu'à ce que je tombe sur un autre fil SO . J'inclus une version retouchée du code trouvé dans l'autre fil que vous devez inclure dans votre activité et qui initialise l'achat.

@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

Un truc simple qui l’a fait pour moi a été de créer une méthode dans IabHelper:

public Boolean getAsyncInProgress() {
    return mAsyncInProgress;
}

et ensuite dans votre code, vérifiez simplement:

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

Vraiment ennuyeux. Voici une solution rapide et délicate qui n’est pas parfaite du point de vue du code, mais qui est conviviale et qui évite les mauvaises évaluations et les blocages:

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

De cette façon, il suffit à l'utilisateur de taper une autre fois (2 fois au pire) et d'obtenir la fenêtre de facturation

J'espère que ça aide

6
Don

Il suffit de rechercher le code de requête onActivityResult sur l'activité et s'il correspond au code PURCHASE_REQUEST que vous avez utilisé lors de l'achat, transmettez-le simplement au fragment.

Lorsque vous ajoutez ou remplacez le fragment dans FragmentTransaction, définissez une balise:

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

Puis sur votre activité 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.

si vous code dans fragment alors vous ce code dans 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

Un autre problème majeur de la classe IabHelpr est le choix peu judicieux de lancer RuntimeExcptions (IllegalStateException) dans plusieurs méthodes. Lancer les RuntimeExeptions à partir de votre propre code dans la plupart des cas n'est pas souhaitable car il s'agit de exceptions non contrôlées . C'est comme si vous sabotiez votre propre application. Si elles ne sont pas détectées, ces exceptions vont exploser et bloquer votre application.

La solution consiste à implémenter votre propre exception vérifiée et à changer la classe IabHelper pour la lancer, au lieu de IllegalStateException. Cela vous obligera à gérer cette exception partout où elle pourrait être insérée dans votre code au moment de la compilation.

Voici mon exception personnalisée:

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

Une fois les modifications apportées à la classe IabHelper, nous pouvons gérer l’exception vérifiée dans notre code, où nous appelons les méthodes de classe. Par exemple:

try {
   setUpBilling(targetActivityInstance.allData.getAll());
} catch (MyIllegalStateException ex) {
    ex.printStackTrace();
}
1

Vous pouvez également obtenir le dernier fichier IabHelper.Java ici: https://code.google.com/p/marketbilling/source/browse/

La version du 15 mars a corrigé cela pour moi. (Notez que d'autres fichiers sans modifications ont été validés le 15)

Il me restait encore à réparer un blocage survenu au cours des tests, provoqué par des extras d'intention nulle lorsque "Android.test.canceled" était le SKU envoyé. J'ai changé:

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

J'ai eu ce problème de temps en temps, et dans mon cas, je l'ai compris, si la méthode onServiceConnected dans IabHelper peut être appelée plusieurs fois si le service sous-jacent se déconnecte et se reconnecte (par exemple, à cause d'une connexion réseau intermittente).

Les opérations spécifiques dans mon cas étaient "Impossible de démarrer l'opération asynchrone (actualiser l'inventaire) car une autre opération asynchrone (launchPurchaseFlow) est en cours".

De la manière dont mon application est écrite, je ne peux pas appeler launchPurchaseFlow tant que je n'ai pas terminé un queryInventory et j'appelle uniquement queryInventory à partir de ma fonction de gestionnaire onIabSetupFinished.

Le code IabHelper appellera cette fonction de gestionnaire chaque fois que son onServiceConnected est appelé, ce qui peut se produire plusieurs fois.

La documentation Android pour onServiceDisconnected dit:

Appelé lorsqu'une connexion au service a été perdue. Cela se produit généralement lorsque le processus d'hébergement du service s'est écrasé ou a été tué. Cela ne supprime pas le ServiceConnexion lui-même - cette liaison au service restera active et vous le ferez recevoir un appel à onServiceConnected (ComponentName, IBinder) lorsque le service est le suivant fonctionnement.

ce qui explique le problème.

On peut soutenir que IabHelper ne devrait pas appeler la fonction d'écoute onIabSetupFinished plus d'une fois, mais d'un autre côté, il était trivial de résoudre le problème dans mon application en n'appelant simplement pas queryInventory à partir de cette fonction si je l'ai déjà fait et obtenu les résultats .

1
sheltond

Oui, je suis également confronté à ce problème mais j'ai résolu ce problème, mais j'ai résolu d'utiliser 

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

La méthode ci-dessus arrête tous les drapeaux. Son travail pour moi doit vérifier

0
sharma_kunal

J'avais le même problème et le problème était que je n'avais pas implémenté la méthode 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

Une version légèrement modifiée de la réponse de NadtheVlad qui fonctionne à merveille

private void makePurchase() {

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

La logique est simple, il suffit de mettre la chose launchPurchaseFlow() dans une méthode et d’utiliser la récursion dans le bloc catch. Vous devez toujours rendre flagEndAsync() public à partir de la classe IabHelper.

0
cht

Ma solution est simple

1.) Rendre la variable mAsyncInProgress visible en dehors de IabHelper

public boolean isAsyncInProgress() {
    return mAsyncInProgress;
}

2.) Utilisez ceci dans votre activité comme:

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