70 votes

Facturation in-app Android : Impossible de lancer une opération asynchrone car une autre opération asynchrone est en cours.

J'utilise le IabHelper comme le recommande le tutoriel de Google, et je suis durement frappé par cette erreur. Apparemment, IabHelper ne peut pas exécuter plusieurs opérations asynchrones en même temps. J'ai même réussi à le toucher en essayant de lancer un achat alors que l'inventaire était toujours en cours.

J'ai déjà essayé de mettre en œuvre onActivityResult dans ma classe principale comme suggéré aquí mais je n'arrive même pas à appeler cette méthode avant que l'erreur ne survienne. Ensuite, j'ai trouvé este mais je n'ai aucune idée d'où trouver ça flagEndAsync il n'est pas dans la méthode IabHelper classe.

Je cherche maintenant un moyen de contourner ce problème (sans réimplémenter tout le système). La seule solution à laquelle je pense est de créer un champ booléen asyncActive qui est vérifié avant qu'une tâche asynchrone ne soit lancée, et ne pas le faire s'il y a une autre tâche 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 tâche asynchrone soit mise en file d'attente et exécutée dès qu'elle est autorisée à le faire, plutôt que de ne pas s'exécuter du tout.

Des solutions pour ce problème ?

5 votes

Pour tous ceux qui lisent cette question, [b]faites défiler vers le bas[/b] et utilisez l'extrait 'onActivityResult()', c'est la réponse.

0 votes

Appelez mHelper.handleActivityResult() dans onActivityResult() pour que flagAsync() soit appelé. Voir l'exemple de code TrivialDrive de Google.

0 votes

Aucune de ces réponses n'est vraiment une solution propre. Je recommanderais d'utiliser un exécuteur à thread unique (Executor mExec = Executors.newSingleThreadExectuors()) et de construire une classe d'enveloppe qui fait de chaque appel IAB un runnable bloquant qui est mis en file d'attente de manière appropriée sur cet exécuteur.

105voto

Khan Points 3274

Une solution simple et délicate

avant d'appeler article d'achat ajoutez simplement cette ligne

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

donc votre code ressemble à ceci

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

Note : n'oubliez pas de rendre publique la méthode flagEndAsync() dans IabHelper si vous l'appelez depuis un autre paquet.

12 votes

Vous voulez dire avant d'appeler la méthode "launchPurchaseFlow", n'est-ce pas ?

5 votes

Je suis très déçu par cette bibliothèque. code.google.com/p/marketbilling voir tous les problèmes ouverts. Google ne semble pas vouloir les résoudre. Je trouve ça important...

0 votes

Je peux savoir quel problème vous rencontrez ? @powder366

80voto

Jonathan Lin Points 2498

Assurez-vous que vous appelez la fonction de l'IabHelper handleActivityResult dans le cadre de l'activité onActivityResult y PAS dans le Fragment onActivityResult .

L'extrait de code suivant est tiré de l'application TrivialDrive Activité principale :

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

Mise à jour :

  • Il existe désormais un API de facturation in-app Version 3 (quelle était la version en 2013 ?)
  • L'échantillon de code a été déplacé vers Github . L'extrait ci-dessus a été modifié pour refléter l'échantillon actuel, mais il est logiquement le même qu'avant.

0 votes

Je me suis littéralement arraché les cheveux sur ce coup-là. Merci.

1 votes

C'est le correct réponse. Toutes les autres solutions sont des pirates. Cela fonctionne même si on détruit des activités, etc. C'est le flux correct.

1 votes

Pour la facturation in-app avec les fragments, voir cette réponse : http://stackoverflow.com/a/22434995/529663

41voto

Wouter Points 832

Ce n'était pas facile à résoudre mais j'ai trouvé les solutions de contournement nécessaires. Je suis assez déçu par Google ces derniers temps, leurs sites web Android sont devenus un véritable fouillis (il est très difficile de trouver des informations utiles) et leurs exemples de code sont médiocres. Quand je faisais du développement Android il y a quelques années, tout était tellement plus facile ! C'est encore un autre exemple de cela...

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

1) méthode de fabrication flagEndAsync public. Il est là, mais pas visible.

2) que chaque auditeur appelle iabHelper.flagEndAsync pour s'assurer que la procédure est correctement marquée à la fin ; cela semble être nécessaire dans tous les auditeurs.

3) entourer les appels d'un try/catch pour attraper le IllegalStateException qui peuvent se produire, et de les gérer de cette façon.

2 votes

Ou vous pouvez remplacer IllegalStateException jeté dans flagEndAsync avec une nouvelle classe d'exception héritée de Exception pour comprendre où vous devez placer try/catch blocs. Cela force l'analyseur statique du code à générer des erreurs lorsque votre exception personnalisée n'a pas été traitée.

0 votes

Comme mentionné ci-dessous par l'utilisateur 2574426, le dépôt comprend plusieurs corrections à IabHelper.java qui traitent ce problème. Pour une raison quelconque, le code d'exemple de mon SDK (API 17) n'inclut PAS ces corrections. Visitez code.google.com/p/marketbilling/source/browse pour les détails.

0 votes

MutantXenu, Dois-je procéder avec la solution de user2574426 avec les yeux fermés :) s'il vous plaît conseillez.

15voto

user33724 Points 26

J'ai fini par faire quelque chose de similaire à Kintaro. Mais j'ai ajouté mHelper.flagEndAsync() à la fin du catch. L'utilisateur reçoit toujours le toast, mais la prochaine fois qu'il appuie sur le bouton d'achat, l'opération asynchrone a été tuée et le bouton d'achat est à nouveau 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();
    }
}

0 votes

J'ai aimé cette approche simple, même si elle n'est pas la meilleure.

9voto

nyaray Points 467

J'étais confronté au même problème jusqu'à ce que je tombe sur un autre fil de SO . J'ai inclus une version retouchée du code trouvé dans l'autre fil de discussion que vous devez inclure dans votre activité 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
    // ...
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X