J'ai essayé d'inclure la facturation via l'application dans mon application et à des fins de test, j'ai basé toute la procédure sur l'exemple "TrivialDrive" pour la version 3 de la facturation via l'application (et en implémentant les versions non modifiées des fichiers IAB fournies dans le sous-répertoire "util" de la démo), mais cela ne fonctionne pas pour moi - sur LogCat, juste avant que l'application se termine avec une erreur, il affiche le message "Erreur de facturation dans l'application: état illégal pour le fonctionnement ( launchPurchaseFlow): IAB Helper n'est pas configuré. "(juste après que la fonction startRegistered () a été déclenchée et m'a donné le message LOG" Bouton d'enregistrement cliqué; lancement du flux d'achat pour la mise à niveau. ") ...
Une idée de ce qui ne va pas ici?
Voici les parties pertinentes de mon code:
package com.mytest;
(..)
import com.mytest.iab.IabHelper; // the originals from the demo example, unmodified
import com.mytest.iab.IabResult;
import com.mytest.iab.Inventory;
import com.mytest.iab.Purchase;
public class Result3 extends Activity implements OnClickListener {
private static final String TAG = "BillingService";
private Context mContext;
boolean mIsRegistered = false;
// this has already been set up for my app at the publisher's console
static final String IS_REGISTERED = "myregistered";
static final int RC_REQUEST = 10001;
// The helper object
IabHelper mHelper;
/** Call when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.result3);
mContext = this;
String base64EncodedPublicKey = "[my public key]"; // (from publisher's console for my app)
// Create the helper, passing it our context and the public key to verify signatures with
Log.d(TAG, "Creating IAB helper.");
mHelper = new IabHelper(this, base64EncodedPublicKey);
// enable debug logging (for a production application, you should set this to false).
mHelper.enableDebugLogging(true);
// Start setup. This is asynchronous and the specified listener
// will be called once setup completes.
Log.d(TAG, "Starting setup.");
mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
public void onIabSetupFinished(IabResult result) {
Log.d(TAG, "Setup finished.");
if (!result.isSuccess()) {
complain("Problem setting up in-app billing: " + result);
return;
}
// Hooray, IAB is fully set up. Now, let's get an inventory of stuff we own.
Log.d(TAG, "Setup successful. Querying inventory.");
mHelper.queryInventoryAsync(mGotInventoryListener);
}
});
// Set the onClick listeners
findViewById(R.id.btnPurchase).setOnClickListener(this);
}
// 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) {
Log.d(TAG, "Query inventory finished.");
if (result.isFailure()) {
complain("Failed to query inventory: " + result);
return;
}
Log.d(TAG, "Query inventory was successful.");
// Do we have the premium upgrade?
mIsRegistered = inventory.hasPurchase(IS_REGISTERED);
Log.d(TAG, "User is " + (mIsRegistered ? "REGISTERED" : "NOT REGISTERED"));
setWaitScreen(false);
Log.d(TAG, "Initial inventory query finished; enabling main UI.");
}
};
// User clicked the "Register" button.
private void startRegistered() {
Log.d(TAG, "Register button clicked; launching purchase flow for upgrade.");
setWaitScreen(true);
mHelper.launchPurchaseFlow(this, IS_REGISTERED, RC_REQUEST, mPurchaseFinishedListener);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
Log.d(TAG, "onActivityResult(" + requestCode + "," + resultCode + "," + data);
// 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.");
}
}
// Callback for when a purchase is finished
IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener = new IabHelper.OnIabPurchaseFinishedListener() {
public void onIabPurchaseFinished(IabResult result, Purchase purchase) {
Log.d(TAG, "Purchase finished: " + result + ", purchase: " + purchase);
if (result.isFailure()) {
// Oh noes!
complain("Error purchasing: " + result);
setWaitScreen(false);
return;
}
Log.d(TAG, "Purchase successful.");
if (purchase.getSku().equals(IS_REGISTERED)) {
Log.d(TAG, "User has registered..");
alert("Thank you.");
mIsRegistered = true;
setWaitScreen(false);
}
}
};
// We're being destroyed. It's important to dispose of the helper here!
@Override
public void onDestroy() {
// very important:
Log.d(TAG, "Destroying helper.");
if (mHelper != null) mHelper.dispose();
mHelper = null;
}
void complain(String message) {
Log.e(TAG, "**** Register Error: " + message);
alert("Error: " + message);
}
void setWaitScreen(boolean set) {
// just a dummy for now
}
void alert(String message) {
AlertDialog.Builder bld = new AlertDialog.Builder(this);
bld.setMessage(message);
bld.setNeutralButton("OK", null);
Log.d(TAG, "Showing alert dialog: " + message);
bld.create().show();
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btnPurchase:
startRegistered();
break;
default:
break;
}
}
}
Voici plus de lignes de Logcat:
12-20 01:06:36.701: D/dalvikvm(299): GC_FOR_MALLOC freed 4262 objects / 308592 bytes in 84ms
12-20 01:06:36.701: D/webviewglue(299): nativeDestroy view: 0x2ea718
12-20 01:06:36.771: W/webcore(299): Can't get the viewWidth after the first layout
12-20 01:07:07.111: W/webcore(299): Can't get the viewWidth after the first layout
12-20 01:07:18.510: D/webviewglue(299): nativeDestroy view: 0x2dd458
12-20 01:07:18.510: D/dalvikvm(299): GC_FOR_MALLOC freed 6042 objects / 544504 bytes in 50ms
12-20 01:07:18.530: D/webviewglue(299): nativeDestroy view: 0x2ea8d0
12-20 01:07:18.660: D/BillingService(299): Creating IAB helper.
12-20 01:07:18.660: D/BillingService(299): Starting setup.
12-20 01:07:18.660: D/IabHelper(299): Starting in-app billing setup.
12-20 01:07:19.621: W/webcore(299): Can't get the viewWidth after the first layout
12-20 01:07:20.160: W/webcore(299): Can't get the viewWidth after the first layout
12-20 01:07:32.481: D/webviewglue(299): nativeDestroy view: 0x3f88e8
12-20 01:07:32.491: D/dalvikvm(299): GC_FOR_MALLOC freed 5798 objects / 513640 bytes in 50ms
12-20 01:07:32.511: D/BillingService(299): Register button clicked; launching purchase flow for upgrade.
12-20 01:07:32.511: E/IabHelper(299): In-app billing error: Illegal state for operation (launchPurchaseFlow): IAB helper is not set up.
12-20 01:07:32.521: D/AndroidRuntime(299): Shutting down VM
12-20 01:07:32.521: W/dalvikvm(299): threadid=1: thread exiting with uncaught exception (group=0x4001d800)
12-20 01:07:32.541: E/AndroidRuntime(299): FATAL EXCEPTION: main
12-20 01:07:32.541: E/AndroidRuntime(299): Java.lang.IllegalStateException: IAB helper is not set up. Can't perform operation: launchPurchaseFlow
12-20 01:07:32.541: E/AndroidRuntime(299): at com.test_ed.iab.IabHelper.checkSetupDone(IabHelper.Java:673)
12-20 01:07:32.541: E/AndroidRuntime(299): at com.test_ed.iab.IabHelper.launchPurchaseFlow(IabHelper.Java:315)
12-20 01:07:32.541: E/AndroidRuntime(299): at com.test_ed.iab.IabHelper.launchPurchaseFlow(IabHelper.Java:294)
12-20 01:07:32.541: E/AndroidRuntime(299): at com.test_ed.Result3.startRegistered(Result3.Java:157)
12-20 01:07:32.541: E/AndroidRuntime(299): at com.test_ed.Result3.onClick(Result3.Java:248)
12-20 01:07:32.541: E/AndroidRuntime(299): at Android.view.View.performClick(View.Java:2408)
12-20 01:07:32.541: E/AndroidRuntime(299): at Android.view.View$PerformClick.run(View.Java:8816)
12-20 01:07:32.541: E/AndroidRuntime(299): at Android.os.Handler.handleCallback(Handler.Java:587)
12-20 01:07:32.541: E/AndroidRuntime(299): at Android.os.Handler.dispatchMessage(Handler.Java:92)
12-20 01:07:32.541: E/AndroidRuntime(299): at Android.os.Looper.loop(Looper.Java:123)
12-20 01:07:32.541: E/AndroidRuntime(299): at Android.app.ActivityThread.main(ActivityThread.Java:4627)
12-20 01:07:32.541: E/AndroidRuntime(299): at Java.lang.reflect.Method.invokeNative(Native Method)
12-20 01:07:32.541: E/AndroidRuntime(299): at Java.lang.reflect.Method.invoke(Method.Java:521)
12-20 01:07:32.541: E/AndroidRuntime(299): at com.Android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.Java:868)
12-20 01:07:32.541: E/AndroidRuntime(299): at com.Android.internal.os.ZygoteInit.main(ZygoteInit.Java:626)
12-20 01:07:32.541: E/AndroidRuntime(299): at dalvik.system.NativeStart.main(Native Method)
A eu le même problème lors de l'exécution de la fonction PurchaseFlow. Jetez un œil à la classe Activity dans l'exemple de Google et plus précisément à la méthode protected void onActivityResult(int requestCode, int resultCode, Intent data)
. Vous avez probablement oublié d'implémenter celui-ci. Cette fonction est vitale pour que l'ensemble du mécanisme fonctionne sans problème.
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
Log.i(TAG, "onActivityResult(" + requestCode + "," + resultCode + "," + data);
// Pass on the activity result to the helper for handling
if (!inappBillingHelper.handleActivityResult(requestCode, resultCode, data)) {
super.onActivityResult(requestCode, resultCode, data);
}
else {
Log.i(TAG, "onActivityResult handled by IABUtil.");
}
}
EDIT: De plus, le problème existe également lorsque vous avez un mauvais mot de passe associé à votre compte gmail sur votre téléphone (cela m'est arrivé aujourd'hui). Bien sûr, toutes les fonctionnalités de facturation Inapp doivent être testées sur le téléphone, mais cela est évident, je pense.
Le problème fondamental est que startRegistered () est invoqué en réponse directe à un clic d'utilisateur de l'interface utilisateur, tandis que la configuration de votre objet IabHelper est déclenchée de manière asynchrone et ne peut donc pas être connue comme terminée jusqu'à ce qu'une réponse asynchrone est reçu via onIabSetupFinished ().
Votre méthode startRegistered () est déclenchée par un clic de l'utilisateur, et qui appelle launchPurchaseFlow (), qui à son tour nécessite que l'objet IabHelper ait déjà terminé la configuration, mais si l'utilisateur clique pour déclencher un achat avant que la confirmation ne soit reçue (soit parce que la configuration échoué ou parce que l'utilisateur est exceptionnellement rapide sur le tirage), la configuration n'aura pas été terminée et launchPurchaseFlow () signalera l'erreur que vous voyez. Dans le cas de votre logcat, le délai est de 14 secondes, ce qui serait généralement suffisant, mais ... peut-être pas dans ce cas. Ou, peut-être que quelque chose s'est mal passé et que vous ne vous seriez jamais connecté, peu importe combien de temps vous aviez attendu.
Dans votre logcat, aucun message n'indique "Service de facturation connecté", ce qui est l'une des premières choses qui doivent se produire si votre configuration est terminée. Comme cela ne se produit pas, vous ne voyez également aucun message (de succès ou d'échec) de onIabSetupFinished ().
C'est une tâche délicate en raison des réponses asynchrones requises. Une approche serait de désactiver le bouton utilisé pour déclencher un achat jusqu'à ce que votre onIabSetupFinished () revienne avec succès. Cela empêcherait le déclenchement de l'achat jusqu'à ce que l'objet IabHelper soit correctement configuré. Bien sûr, si la configuration échoue, vous aurez un bouton qui ne fonctionne pas, mais au moins vous pouvez dire à l'utilisateur ce qui se passe (en affichant un message indiquant que vous attendez la fin de la configuration - par exemple, dans le cadre de la texte du bouton).
Même dans ce cas, une fois que votre achat est lancé et que la boîte de dialogue de paiement apparaît à l'utilisateur, il est possible que votre application passe par un cycle onStop () qui vide votre application de la mémoire pendant que l'utilisateur réfléchit à son achat (car la boîte de dialogue d'achat fait partie de Google Play, ne fait pas partie de votre application, et le système d'exploitation peut nécessiter de la mémoire pour l'exécuter, et cette mémoire peut être obtenue en arrêtant votre application). Cela détruirait votre objet IabHelper (), qui devrait alors être créé et configuré de manière asynchrone encore. Et encore une fois, comme cela est déclenché de manière asynchrone dans votre méthode onCreate (), onActivityResult () peut être invoqué par le service Google Play pour signaler l'action d'achat de l'utilisateur avant la configuration de l'objet IabHelper est terminée, et depuis dans onActivityResult (), vous devrez tiliser votre instance IabHelper, cela pourrait entraîner une erreur. Il semble que vous devez être prêt à tout.
Cela devrait vous donner une idée de ce à quoi vous avez affaire. IAB est difficile pour exactement ces raisons - plusieurs threads de choses asynchrones (par exemple, configuration vs achats vs Android actions du système d'exploitation qui empêchent votre application de récupérer de la mémoire pour une utilisation par, très probablement, le très Opération d'achat d'une application Google Play pour laquelle votre application attend d'obtenir les résultats de l'achat). Une grande partie de ce qui est implémenté (y compris par l'exemple TrivialDrive) est flou car il repose implicitement sur le fait que votre application reste en mémoire alors qu'en fait être recyclé, ou parce qu'il repose sur une étape d'une condition de compétition (par exemple, la configuration) terminée avant une autre étape (par exemple, le lancement d'un achat), etc.
Je viens de finir d'enrouler ma tête autour du même problème. IabHelper-Setup démarre, mais après cela, rien d'autre ne se produit. Et cliquer sur un achat intégré renvoie exactement la même erreur que vous aviez.
Voici ce que j'ai compris: je n'ai utilisé que des émulateurs d'Eclipse. Une fois que j'ai lu qu'une certaine version de Google Play est requise, j'ai remarqué que Google Play manquait entièrement sur mes lecteurs d'émulation de test.
Quand j'ai ensuite utilisé un vrai téléphone, cela a fonctionné parfaitement! Donc, si vous êtes toujours bloqué sur ce problème, essayez d'utiliser un véritable appareil (si vous en avez un de disponible). Cela devrait faire l'affaire.
Une autre chose que j'ai rencontrée; bien que vous puissiez disposer de la dernière version de google play sur votre appareil, qui prend en charge la dernière version de la facturation via l'application, d'autres utilisateurs ne le peuvent pas. Et même si les plantages provoqués par cela devraient en théorie apparaître dans la console du développeur, je ne pouvais pas voir ces plantages avant d'avoir implémenté Firebase ... et puis j'en ai vu beaucoup. Ce que j'ai fini par faire, c'était d'utiliser un catch catch et de relier les utilisateurs qui n'avaient pas la dernière version de google play ou qui ont rencontré un problème sur le google Play Store à la fin de cette page https://support.google.com/googleplay/answer/1050566? hl = en
try {
mHelper.launchPurchaseFlow(this, SKU_PRO_LT, RC_REQUEST,
mPurchaseFinishedListener, payload);
} catch (Exception e) { //with IabHelper.IabAsyncInProgressException the code still fatally crashes for some reason
//complain("Error launching purchase flow. Another async operation in progress.");
alert2("[error msg]");
setWaitScreen(false);
}
alert2 est juste une boîte de dialogue avec un lien vers la page Web ci-dessus.
Mais tout d'abord, je recommanderais de tester les achats d'applications sur quelques téléphones d'amis juste pour vous assurer qu'il s'agit d'un problème de mise à jour Play Store et non d'un code.