web-dev-qa-db-fra.com

Android In-App Billing v3: "Impossible d'effectuer l'opération: queryInventory"

J'ai configuré la facturation intégrée pour la première fois à l'aide de la nouvelle API v3. Cela fonctionne correctement sur mes appareils, mais j'ai reçu beaucoup de rapports d'erreur d'autres utilisateurs.

L'un d'eux est: 

Java.lang.IllegalStateException: IAB helper is not set up. Can't perform operation: queryInventory
    at my.package.util.iab.IabHelper.checkSetupDone(IabHelper.Java:673)
    at my.package.util.iab.IabHelper.queryInventory(IabHelper.Java:462)
    at my.package.util.iab.IabHelper$2.run(IabHelper.Java:521)
    at Java.lang.Thread.run(Thread.Java:1019)

Et un autre est:

Java.lang.NullPointerException
    at my.package.activities.MainActivity$4.onIabSetupFinished(MainActivity.Java:159)
    at my.package.util.iab.IabHelper$1.onServiceConnected(IabHelper.Java:242)

La mise en œuvre de mon activité suit l'exemple de code de Google (toutes les classes référencées ne sont pas modifiées de l'exemple):

IabHelper mHelper;

public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    //...

    mHelper = new IabHelper(this, IAB_PUBLIC_KEY);
    mHelper.enableDebugLogging(true);

    mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
        public void onIabSetupFinished(IabResult result) {
            if (!result.isSuccess()) {
                // Oh noes, there was a problem.
                return;
            }

            // Hooray, IAB is fully set up. Now, let's get an inventory of
            // stuff we own.
            mHelper.queryInventoryAsync(mGotInventoryListener); //***(1)***
        }
    });
}

// Listener that's called when we finish querying the items we own
IabHelper.QueryInventoryFinishedListener mGotInventoryListener = new IabHelper.QueryInventoryFinishedListener() {
    public void onQueryInventoryFinished(IabResult result,
            Inventory inventory) {
        if (!result.isFailure()) {
            if (inventory.hasPurchase(SoundsGlobals.IAB_SKU_PREMIUM)){
                //we are premium, do things
            }
        }
        else{
            //oops
        }
    }
};

@Override
protected void onDestroy() {
    if (mHelper != null) {
        mHelper.dispose();
        mHelper = null;
    }
    super.onDestroy();
}

Je suppose que les deux erreurs proviennent de la ligne marquée ***(1)***

Quelle est la cause de ces erreurs? Si j'appelle queryInventoryAsync uniquement dans onIabSetupFinished, comment est-il possible que mHelper soit null ou que mHelper ne soit pas configuré?

Est-ce que quelqu'un connaît une solution à cela?

29
Ereza

Comme @Martin l'a expliqué, l'exemple de facturation intégrée aux applications de Google était à l'origine d'un problème. 

Cependant, après l'avoir corrigé, je continuais à recevoir des erreurs dans les appels internes (queryInventory à l'intérieur du fil créé dans queryInventoryAsync dans de rares cas, il est signalé que l'assistant n'est pas configuré). J'ai résolu ce problème en ajoutant une prise supplémentaire dans ce cas:

try {
    inv = queryInventory(querySkuDetails, moreSkus);
}
catch (IabException ex) {
    result = ex.getResult();
}
catch(IllegalStateException ex){ //ADDED THIS CATCH
    result = new IabResult(BILLING_RESPONSE_RESULT_ERROR, "Helper is not setup.");
}

J'ai aussi eu un crash sur mHelper.dispose() que j'ai corrigé de la même manière:

try{
    if (mContext != null) mContext.unbindService(mServiceConn);
}
catch(IllegalArgumentException ex){ //ADDED THIS CATCH
    //IGNORE IT - somehow, the service was already unregistered
}

Bien sûr, au lieu d'ignorer ces erreurs, vous pouvez les enregistrer en mode silencieux dans ACRA, par exemple :)

Merci pour tous vos commentaires.

19
Ereza

Il y a un bug dans IABHelper. La ligne de retour dans le gestionnaire d'exceptions est manquante, ce qui signifie qu'elle passe au-dessus et appelle le revendeur à succès. Toutefois, mSetupDone n'a pas été défini. Par conséquent, les autres appels à l'API échouent. Ajoutez l'instruction de retour comme ci-dessous - cela échouera toujours, mais l'échec sera correctement signalé à votre application afin que vous puissiez prendre les mesures appropriées. 

                catch (RemoteException e) {
                if (listener != null) {
                    listener.onIabSetupFinished(new IabResult(IABHELPER_REMOTE_EXCEPTION,
                                                "RemoteException while setting up in-app billing."));
                }
                e.printStackTrace();
                return;  // This return line is missing
            }

            if (listener != null) {
                listener.onIabSetupFinished(new IabResult(BILLING_RESPONSE_RESULT_OK, "Setup successful."));
            }
14
Martin

Je crois qu'il y a toujours deux bogues dans le code Android, ce qui explique pourquoi vous voyez toujours l'erreur. Notez que la pile d'appels se trouve sur un thread autonome. Mais le code qui définit mSetupDone (IabHelper) sur true s'exécute sur le thread d'interface utilisateur principal. Java ne garantit pas que les données modifiées par un thread seront visibles par l'autre thread en raison de la mise en cache du processeur, à moins que vous ne déclariez la variable avec le mot clé volatile. Ainsi, il est possible qu'il ait été configuré (mSetupDone == true), mais que la nouvelle valeur de mSetupDone soit mise en cache sur le thread de l'interface utilisateur, non visible pour ce thread dans votre pile d'appels. Donc, ce fil voit toujours mSetupDone == false.

J'ai essayé de résoudre ce problème en déclarant mSetupDone avec volatile, ainsi que tous les autres champs non finaux de IabHelper, pour être sûr.

Maintenant, l’autre problème est la fonction .dispose (). Cela n'arrête pas les discussions en cours. Cela signifie qu'il peut affecter la valeur false à mSetupDone lors de l'exécution d'un des threads de travail. Si vous regardez queryInventoryAsync (), vous verrez qu'il vérifie que mSetupDone est true. Et en fonction de votre pile d’appels, il a été dépassé. Ensuite, il s'est écrasé plus tard avec mSetupDone == false. Le seul moyen qui pourrait arriver est de disposer de dispose () alors que votre thread était en vol. La solution est que dispose () a besoin de signaler aux threads de simplement renvoyer en silence au lieu de continuer et de générer des erreurs quand il voit mSetupDone == false. Cela évite également un autre problème avec IabHelper où les instances disposées appellent des rappels d'écouteur même après avoir été supprimées! C'est un peu compliqué d'expliquer ligne par ligne ici, mais j'espère donc que vous serez ainsi dirigé dans la bonne direction.

10
David M

J'ai découvert! Il s'agit de la version de l'application Google Play Store de l'utilisateur. La facturation In-App V3 nécessite la version 3.9.16 ou supérieure ( http://developer.Android.com/google/play/billing/versions.html ) . J'ai utilisé une version plus ancienne et j'ai reçu cette erreur. , maintenant sur 4.4.21 son ok!

6
edwell

Je reçois cette erreur EXACT même avec à peu près exactement le même code.

Il semble que cela ne se produise que sur certains combinés (en fait, les derniers rapports d'erreur récents semblent presque exclusivement liés à la tablette Acer Iconia!) - et je traite onActivityResult ...

Il existe un certain nombre d’erreurs dans l’échantillon de facturation Google v3 qui peuvent provoquer des ANR/FC - j’imagine qu’il s’agit juste d’une autre (le code de mauvaise qualité et la documentation de mauvaise qualité deviennent une marque Google, malheureusement).

Mon hypothèse est - pour le moment - que nous devons permettre à mHelper ou à mGotInventoryListener d'être nul et simplement désactiver la facturation In-App dans ce cas (comme si result.isSuccess () était false, en gros)

p.s. édité à ajouter - il se pourrait simplement que l'utilisateur dispose d'une version obsolète du Play Store - qui ne se met à jour que s'il le permet de s'exécuter !?

2
user834595

Assurez-vous que vous implémentez la méthode IabHelper.han.handleActivityResult(requestCode, resultCode, data) dans vos méthodes onActivityResult.

   @Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {

// Pass on the activity result to the helper for handling
  if (!mIabHelper.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.i(TAG, "onActivityResult handled by IABUtil.");
  }
}
2
KevinM

En plus de @DavidM et @Ereza.

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 bouillonner et faire planter 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

Si tout ce qui précède ne vous aide pas, essayez d'analyser un peu votre code - votre IabHelper est-elle vraiment configurée au moment où vous l'appelez?

Je me suis retrouvé à le faire un peu faux sans m'en rendre compte. Un exemple simple d'utilisation mal dans Activity.onCreate()

m_iabHelper = new IabHelper(this, base64EncodedPublicKey); // Declare

m_iabHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() { // Setup
    public void onIabSetupFinished(IabResult result) {
        // Setup code
    }
}

// Don't do this, will produce an error
List additionalSkuList = new ArrayList(); 
additionalSkuList.add(SKU_MYSKU);
m_iabHelper.queryInventoryAsync(true, additionalSkuList, m_queryFinishedListener);
// Don't do this, will produce an error

.

Ci-dessus vous honorera avec l'erreur "L'assistant IAB n'est pas configuré" car, tandis que votre application tente d'exécuter la fonction m_iabHelper.queryInventoryAsync(), l'IabHelper n'est pas encore configuré. Pensez à utiliser ces fonctions dans onIabSetupFinished() ou quelque part après que cette fonction a été appelée (par exemple, en dehors de la onCreate())

1
Voy

Vous pouvez vous tenir au courant du développement de l'API inapp v3 à l'adresse suivante: https://code.google.com/p/marketbilling/

Le code est plus récent que celui disponible via Android SDK Manager.

1
Catalin Morosan

Il y avait beaucoup de problèmes avec IABHelper.Java.

Premièrement, la version téléchargée par le gestionnaire de SDK n'est pas mise à jour. Utilisez la version disponible ici: https://code.google.com/p/marketbilling/source/detail?r=15946261ec9ae5f7c664d720f392f7787e3ee6c7 Il s’agit de la version la plus récente de publier cette réponse. De nombreux problèmes semblent avoir été résolus avec cette version par rapport à la version initiale fournie par le SDK Manager.

La seule chose que j'ai dû changer dans cette version est d'ajouter flagEndAsync(); après la ligne 404, qui corrige une exception IllegalStateException lorsque deux flux d'achat IAB sont lancés en succession rapide.

Avec cette version, vous n'avez plus besoin d'utiliser flagEndAsync(); dans vos fichiers et vous pouvez laisser la méthode non publique.

0
Uwais A

Je reçois les mêmes erreurs. J'ai aussi rencontré d'autres problèmes ...

Avoir plusieurs comptes Google sur un appareil désactivé La facturation intégrée à l'application sur mon Galaxy Tab 7. Supprimer un compte l'a réactivé.

J'ai rencontré des problèmes avec l'exemple de code de facturation intégré à l'application sur un GT-P5110, un LGL75C et un GT-S5839i, entre autres.

(J'utilise le code dans une application avec ACRA installé ... donc chaque fois qu'il se plante, je reçois des informations)

La version Android des appareils va de 2.3.3 à 4.0.4.

C'est vraiment agaçant.

0
orb360