5

Ho implementato la fatturazione in-app (v3) in base alla guida di Android Implementing In-app Billing.Nella fatturazione di app - Orientamento rapido del dispositivo - provoca un arresto anomalo (IllegalStateException)

Tutto funziona correttamente, fino a quando non ruoto il dispositivo, quindi lo ruoto immediatamente al suo orientamento originale. In realtà, a volte funziona, a volte si blocca con:

java.lang.IllegalStateException: IabHelper was disposed of, so it cannot be used.

Sembra che questa è legata alla natura asincrona di IAB, anche se non sono positivi.

Qualche idea?

risposta

6

Probabilmente stai ricevendo l'eccezione perché da qualche parte nel ciclo di vita di attività, hai chiamato mHelper.dispose(), poi ha cercato di utilizzare lo stesso istanza dismessa in seguito. La mia raccomandazione è di disporre solo di mHelper in onDestroy() e ricrearlo in onCreate().

Tuttavia, si verificherà un altro problema con IabHelper e rotazione del dispositivo. Il problema è simile a questo: nella tua attività onCreate(), crei l'istanza di IHHelper mHelper e la configuri. Successivamente, chiami mHelper.launchPurchaseFlow(...) e la finestra di dialogo IAB appare fluttuante sopra la tua attività. Quindi si ruota il dispositivo e l'istanza di IabHelper viene eliminata in onDestroy(...) quindi ricreata in onCreate(...). La finestra di dialogo IAB continua a essere visualizzata, si preme il pulsante di acquisto e l'acquisto viene completato. onActivityResult() viene quindi chiamato sulla tua attività, e naturalmente chiama mHelper.handleActivityResult(...). Il problema è che launchPurchaseFlow(...) non è mai stato chiamato nell'istanza ricreata di IabHelper. IabHelper gestisce solo il risultato dell'attività in handleActivityResult(...) se in precedenza è stato chiamato launchPurchaseFlow(...) nell'istanza corrente. Il tuo OnIabPurchaseFinishedListener non verrà mai chiamato.

La mia soluzione a questo era di modificare IabHelper per consentire di dire che aspettarsi handleActivityResult(...) senza chiamare launchPurchaseFlow(...). Ho aggiunto il testo seguente IabHelper.java

public void expectPurchaseFinished(int requestCode, OnIabPurchaseFinishedListener listener) 
{ 
    mRequestCode = requestCode; 
    mPurchaseListener = listener; 
} 

Questo farà sì che IabHelper chiamare onIabPurchaseFinished(...) sull'ascoltatore quando handleActivityResult(...) si chiama. Quindi, si esegue questa operazione:

@Override 
protected void onActivityResult(int requestCode, int resultCode, Intent data) 
{ 
    mHelper.expectPurchaseFinished(requestCode, mPurchaseFinishedListener); 
    mHelper.handleActivityResult(requestCode, resultCode, data); 
} 

Tutta la mia copia di IabHelper può essere trovato qui https://gist.github.com/benhirashima/7917645. Nota che ho aggiornato la mia copia di IabHelper con la versione trovata in this commit, che corregge alcuni bug e non è stata pubblicata in Android SDK Manager. Si noti inoltre che ci sono newer commits, ma contengono new bugs e non devono essere utilizzati.

+0

Soluzione pulita per quanto riguarda la gestione dell'evento di rotazione. Ho avuto qualche problema a capire cosa implica "expectPurchaseFinished", ma alla fine mi sono reso conto che stai impostando una nuova istanza di purchaseFinishedListener appena prima di gestire il risultato, che garantisce che ci sia un listener per gestire l'evento SelectedFinished anche se l'attività e iabHelper erano ricreato. –

+0

Possiamo estendere la stessa idea per gestire mPurchasingItemType (importa solo se si utilizzano sia subs e inapp, ad esempio use onSaveInstanceState e onRestoreInstanceState, quindi impostare il tipo di elemento corretto se non è impostato). –

0

Ho provato a rendere statico MHelper, e solo istanziato se (mHelper == null), e NON distruggerlo nel metodo onDestroy() dell'attività. Inoltre, passare il contesto Application a IabHelper. In questo modo, una volta impostato, si blocca e non è più necessario preoccuparsi delle operazioni asincrone (cause dell'orientamento del dispositivo).

Ecco un profilo del mio codice:

static IabHelper mHelper; 
public void onCreate(Bundle savedInstanceState) { 
    ... 
    if (mHelper == null) { 
     mHelper = new IabHelper(getApplicationContext(), base64EncodedPublicKey); 
     mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() { 
      ... 
     }); 
    } 
    ... 
} 

protected void onDestroy() { 
    ... 
    // Don't do ANYTHING to mHelper, so it will stick around on orientation change 
} 

Non sono sicuro se questa è la soluzione giusta o no, ma ho pensato di parlarne nel caso in cui aiutare gli altri.

+0

Stavo pensando di fare la stessa cosa, ma non so se ci sono effetti collaterali indesiderati a questo. Mantenere un IabHelper significa mantenere una connessione di servizio permanente a InAppBillingService. Hai avuto problemi con questo? –

+0

Questo è un vecchio problema, ma ... Va bene passare nel contesto dell'applicazione, Se si guarda all'origine, si afferma che è possibile utilizzare un contesto di attività o applicazione https://code.google.com/p/marketbilling /source/browse/v3/src/com/example/android/trivialdrivesample/util/IabHelper.java?r=5f6b7abfd0534acd5bfc7c14436f4500c99e0358 – Darussian

+0

Fare mHelper static funziona per me. – deko

2

Ecco quello che ho fatto:

Il codice per istanziare il IabHelper e chiamare startSetup() è dentro onCreate(), quindi sarà ricreato quando viene ruotato il dispositivo, a condizione che non sta gestendo le modifiche di configurazione sul proprio .

Inoltre, assicurarsi che si chiami .handleActivityResult() all'inizio di onActivityResult(). Ciò garantirà che il tuo riferimento IabHelper venga ripulito correttamente dopo la chiusura della finestra di dialogo dell'acquisto.

Con queste due cose in atto, non dovresti vedere altri arresti anomali. Ma si noterà di più una cosa:

Se si avvia la finestra di acquisto con una chiamata a launchPurchaseFlow() e poi ruotare il dispositivo, la finestra rimarrà aperta, ma ora si chiama 's tuoi ActivityIabHelper di riferimento è stato sovrascritto dal onCreate() sulla rotazione del dispositivo. Per questo motivo, quando chiudi la finestra di dialogo, viene chiamato il nuovo metodo IabHelperhandleActivityResult(), ma non corrisponde allo requestCode passato a launchPurchaseFlow() in precedenza, quindi il tuo onPurchaseFinishedListener non verrà avvisato. Per gestire questo caso (rotazioni del dispositivo quando la finestra di dialogo è aperta), è necessario gestire lo requestCode all'interno di onActivityResult(). Dal momento che la finestra di dialogo è stata chiusa, ti consigliamo di simulare ciò che hai fatto all'interno del tuo onPurchaseFinishedListener (scopri se l'utente ha effettivamente acquistato qualcosa). Ho appena effettuato una chiamata allo queryInventoryAsync() per scoprirlo.

Non sono sicuro che sia la soluzione ideale, ma funziona bene per me. Ho provato ad aggrapparmi al riferimento IabHelper come hai fatto tu, ma ho visto strani problemi in cui avrebbe perso il suo stato di installazione ma non mi avrebbe permesso di reimpostarlo.

Un'ultima cosa che ho fatto è stato aggiornare le classi di fatturazione util con le ultime dalla fonte Android. Ci sono alcune correzioni di bug che non sono state inviate al gestore SDK.La maggior parte di loro sono nulli controlli discutibili, ma ci sono alcuni miglioramenti per prevenire incidenti:

latest changeset

0

Dopo ho provato molti suggerimenti in SO che non hanno risolto il problema, ho provato questo semplice null controlli che ha risolto il problema:

Per prima cosa ho controllato se mHelper è nullo, quindi creare una nuova istanza:

In onCreate

if (mHelper == null) { 
    mHelper = new IabHelper(this, base64EncodedPublicKey); 
} 

E ho aggiunto altre implementazioni all'interno di un controllo su nulla mHelper ancora:

//noinspection ConstantConditions 
if (mHelper != null){ 

    mGotInventoryListener = new IabHelper.QueryInventoryFinishedListener() { 
     public void onQueryInventoryFinished(IabResult result, Inventory inventory) { 
     } 
    }; 
    mPurchaseFinishedListener = new IabHelper.OnIabPurchaseFinishedListener() { 
     public void onIabPurchaseFinished(IabResult result, Purchase purchase) { 
     } 
    }; 
    mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() { 
     public void onIabSetupFinished(IabResult result) { 
     } 
    }); 
} 

E, naturalmente, si dovrebbe disporre di supporto:

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

Se il problema resiste ancora, aggiornare la classe IabHelper.

Problemi correlati