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?
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.
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:
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.
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();
}
}
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");
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
// ...
}
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..)
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
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);
}
}
}
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);
}
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();
}
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;
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 .
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
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);
}
}
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
.
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(...);
...