IllegalStateException: impossibile eseguire questa azione dopo onSaveInstanceState con ViewPager


496

Ricevo i rapporti degli utenti dalla mia app sul mercato, offrendo la seguente eccezione:

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1109)
at android.app.FragmentManagerImpl.popBackStackImmediate(FragmentManager.java:399)
at android.app.Activity.onBackPressed(Activity.java:2066)
at android.app.Activity.onKeyUp(Activity.java:2044)
at android.view.KeyEvent.dispatch(KeyEvent.java:2529)
at android.app.Activity.dispatchKeyEvent(Activity.java:2274)
at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1803)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchKeyEvent(PhoneWindow.java:1855)
at com.android.internal.policy.impl.PhoneWindow.superDispatchKeyEvent(PhoneWindow.java:1277)
at android.app.Activity.dispatchKeyEvent(Activity.java:2269)
at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1803)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.widget.TabHost.dispatchKeyEvent(TabHost.java:297)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1112)
at com.android.internal.policy.impl.PhoneWindow$DecorView.superDispatchKeyEvent(PhoneWindow.java:1855)
at com.android.internal.policy.impl.PhoneWindow.superDispatchKeyEvent(PhoneWindow.java:1277)
at android.app.Activity.dispatchKeyEvent(Activity.java:2269)
at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1803)
at android.view.ViewRoot.deliverKeyEventPostIme(ViewRoot.java:2880)
at android.view.ViewRoot.handleFinishedEvent(ViewRoot.java:2853)
at android.view.ViewRoot.handleMessage(ViewRoot.java:2028)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:132)
at android.app.ActivityThread.main(ActivityThread.java:4028)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:491)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:844)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:602)
at dalvik.system.NativeStart.main(Native Method)

Apparentemente ha qualcosa a che fare con un FragmentManager, che non uso. Lo stacktrace non mostra nessuna delle mie classi, quindi non ho idea di dove si verifichi questa eccezione e come prevenirla.

Per la cronaca: ho un tabhost e in ogni scheda è presente un gruppo attività che passa da un'attività all'altra.


2
Ho trovato questa domanda discutere lo stesso problema, ma nessuna soluzione neanche lì .. stackoverflow.com/questions/7469082/...
nhaarman

3
Mentre non stai usando FragmentManager, Honeycomb lo è sicuramente. Succede su tablet a nido d'ape reali? O potrebbe essere che qualcuno stia eseguendo un Honeycomb hackerato su un telefono o qualcosa del genere ed è questa edizione hackerata che sta avendo difficoltà?
CommonsWare,

1
Non ne ho idea. Queste sono le uniche informazioni che ottengo nella Console per gli sviluppatori di mercato, il messaggio dell'utente non contiene neanche informazioni utili ..
nhaarman,

Sto usando Flurry, che mi mostra 11 sessioni con Android 3.0.1 e ho 11 segnalazioni di questa eccezione. Potrebbe essere una coincidenza però. Android 3.1 e 3.2 hanno rispettivamente 56 e 38 sessioni.
nhaarman,

Il rapporto sugli errori di mercato ha una sezione "Piattaforma", a volte contiene la versione Android del dispositivo.
Nikolay Elenkov,

Risposte:


720

Si prega di controllare la mia risposta qui . Fondamentalmente dovevo solo:

@Override
protected void onSaveInstanceState(Bundle outState) {
    //No call for super(). Bug on API Level > 11.
}

Non effettuare la chiamata super()sulsaveInstanceState metodo. Questo stava rovinando le cose ...

Questo è un bug noto nel pacchetto di supporto.

Se devi salvare l'istanza e aggiungere qualcosa al tuo outState Bundlepuoi usare quanto segue:

@Override
protected void onSaveInstanceState(Bundle outState) {
    outState.putString("WORKAROUND_FOR_BUG_19917_KEY", "WORKAROUND_FOR_BUG_19917_VALUE");
    super.onSaveInstanceState(outState);
}

Alla fine la soluzione corretta era (come si vede nei commenti) di usare:

transaction.commitAllowingStateLoss();

quando si aggiungeva o si eseguiva quello FragmentTransactionche causava il file Exception.


370
Dovresti usare commitAllowingStateLoss () invece di commit ()
meh

18
Questo commento su commitAllowingStateLoss () è una risposta a sé stante: dovresti pubblicarlo come tale.
Risadinha,

20
Riguardo a 'commitAllowingStateLoss' - /> "Questo è pericoloso perché il commit può essere perso se l'attività deve essere successivamente ripristinata dal suo stato, quindi dovrebbe essere usata solo nei casi in cui è giusto che lo stato dell'interfaccia utente cambi in modo imprevisto su l'utente."
Codeversed

10
Se guardo la fonte v4 per popBackStackImmediatefallisce immediatamente se lo stato è stato salvato. L'aggiunta precedente del frammento con commitAllowingStateLossnon ha alcun ruolo. I miei test mostrano che questo è vero. Non ha alcun effetto su questa specifica eccezione. Ciò di cui abbiamo bisogno è un popBackStackImmediateAllowingStateLossmetodo.
Synesso

3
@DanieleB sì, ho pubblicato una risposta qui. Ma in realtà ho trovato una soluzione ancora migliore usando il bus dei messaggi Otto: registra il frammento come abbonato e ascolta il risultato asincrono dal bus. Annullare la registrazione in pausa e registrare nuovamente al riavvio. L'asincrono ha anche bisogno di un metodo Produce per i tempi in cui viene completato e il frammento viene messo in pausa. Quando avrò tempo aggiornerò la mia risposta con questo in modo più dettagliato.
Synesso

130

Esistono molti problemi correlati con un messaggio di errore simile. Controlla la seconda riga di questa particolare traccia dello stack. Questa eccezione è specificamente correlata alla chiamata a FragmentManagerImpl.popBackStackImmediate.

Questa chiamata al metodo, come popBackStack, fallirà sempreIllegalStateException se lo stato della sessione è già stato salvato. Controlla la fonte. Non c'è niente che tu possa fare per impedire che questa eccezione venga generata.

  • Rimuovere la chiamata a super.onSaveInstanceStatenon aiuta.
  • La creazione del frammento con commitAllowingStateLossnon sarà di aiuto.

Ecco come ho osservato il problema:

  • C'è un modulo con un pulsante di invio.
  • Quando si fa clic sul pulsante, viene creata una finestra di dialogo e viene avviato un processo asincrono.
  • L'utente fa clic sul tasto Home prima che il processo sia terminato - onSaveInstanceStateviene chiamato.
  • Il processo viene completato, viene effettuata una richiamata e popBackStackImmediateviene tentato.
  • IllegalStateException viene lanciato.

Ecco cosa ho fatto per risolverlo:

Poiché non è possibile evitare IllegalStateExceptionil callback, catturarlo e ignorarlo.

try {
    activity.getSupportFragmentManager().popBackStackImmediate(name);
} catch (IllegalStateException ignored) {
    // There's no way to avoid getting this if saveInstanceState has already been called.
}

Questo è sufficiente per impedire il crash dell'app. Ma ora l'utente ripristinerà l'app e vedrà che il pulsante che pensava di aver premuto non è stato premuto affatto (pensano). Il frammento di forma è ancora visibile!

Per risolvere questo problema, quando viene creata la finestra di dialogo, impostare alcuni stati per indicare che il processo è stato avviato.

progressDialog.show(fragmentManager, TAG);
submitPressed = true;

E salva questo stato nel bundle.

@Override
public void onSaveInstanceState(Bundle outState) {
    ...
    outState.putBoolean(SUBMIT_PRESSED, submitPressed);
}

Non dimenticare di ricaricarlo nuovamente onViewCreated

Quindi, quando si riprende, ripristinare i frammenti se era stato precedentemente tentato di inviare. Ciò impedisce all'utente di tornare a quello che sembra un modulo non inviato.

@Override
public void onResume() {
    super.onResume();
    if (submitPressed) {
        // no need to try-catch this, because we are not in a callback
        activity.getSupportFragmentManager().popBackStackImmediate(name);
        submitPressed = false;
    }
}

5
Lettura interessante a riguardo qui: androiddesignpatterns.com/2013/08/…
Pascal

Se usi DialogFragment, ho fatto un'alternativa qui: github.com/AndroidDeveloperLB/DialogShard
sviluppatore Android

E se il popBackStackImmediatefosse chiamato da Android stesso?
Kimi Chiu,

1
Assolutamente fantastico. Questa dovrebbe essere la risposta accettata. Grazie mille! Forse aggiungerei submitPressed = false; dopo popBackStackInmediate.
Neonigma,

Non ho usato il metodo void pubblico onSaveInstanceState (Bundle outState). Devo impostare un metodo vuoto per il vuoto pubblico onSaveInstanceState (Bundle outState)?
Faxriddin Abdullayev,

55

Controlla se l'attività isFinishing()prima di mostrare il frammento e presta attenzione commitAllowingStateLoss().

Esempio:

if(!isFinishing()) {
FragmentManager fm = getSupportFragmentManager();
            FragmentTransaction ft = fm.beginTransaction();
            DummyFragment dummyFragment = DummyFragment.newInstance();
            ft.add(R.id.dummy_fragment_layout, dummyFragment);
            ft.commitAllowingStateLoss();
}

1
! isFinishing () &&! isDestroyed () non funziona per me.
Allen Vork,

! isFinishing () &&! isDestroyed () ha funzionato per me, ma richiede API 17. Ma semplicemente non mostra a DialogFragment. Vedi stackoverflow.com/questions/15729138/… per altre buone soluzioni, stackoverflow.com/a/41813953/2914140 mi ha aiutato.
CoolMind

29

È ottobre 2017 e Google crea la libreria di supporto Android con le nuove cose chiamate componente Ciclo di vita. Fornisce alcune nuove idee per questo problema "Impossibile eseguire questa azione dopo onSaveInstanceState".

In breve:

  • Usa il componente del ciclo di vita per determinare se è il momento giusto per spuntare il tuo frammento.

Versione più lunga con spiegare:

  • perché questo problema viene fuori?

    È perché stai provando a utilizzare la FragmentManagertua attività (che supporterà il tuo frammento? Suppongo?) Per eseguire una transazione per te frammento. Di solito sembra che tu stia provando a fare qualche transazione per un frammento emergente, nel frattempo l'attività dell'host chiama già il savedInstanceStatemetodo (l'utente può toccare il pulsante home in modo che l'attività chiami onStop(), nel mio caso è il motivo)

    Di solito questo problema non dovrebbe verificarsi: proviamo sempre a caricare il frammento in attività all'inizio, come se il onCreate()metodo fosse un posto perfetto per questo. Ma a volte questo accade , specialmente quando non riesci a decidere quale frammento caricherai su quell'attività, o stai provando a caricare frammento da un AsyncTaskblocco (o qualsiasi cosa richiederà un po 'di tempo). Il tempo, prima che avvenga effettivamente la transazione del frammento, ma dopo il onCreate()metodo dell'attività , l'utente può fare qualsiasi cosa. Se l'utente preme il pulsante Home, che attiva il onSavedInstanceState()metodo dell'attività , si verificherà un can not perform this actionarresto anomalo.

    Se qualcuno vuole approfondire questo problema, consiglio loro di dare un'occhiata a questo post sul blog . Sembra profondo all'interno del livello del codice sorgente e spiega molto al riguardo. Inoltre, dà la ragione per cui non dovresti usare il commitAllowingStateLoss()metodo per aggirare questo crash (fidati di me, non offre nulla di buono per il tuo codice)

  • Come risolvere questo?

    • Devo usare il commitAllowingStateLoss()metodo per caricare il frammento? No, non dovresti ;

    • Dovrei ignorare il onSaveInstanceStatemetodo, ignorare il supermetodo al suo interno? No, non dovresti ;

    • Devo usare la magica isFinishingattività interna per verificare se l'attività dell'host è nel momento giusto per la transazione di frammenti? Sì, questo sembra il modo giusto di fare.

  • Dai un'occhiata a cosa può fare il componente Ciclo di vita .

    Fondamentalmente, Google esegue un'implementazione all'interno della AppCompatActivityclasse (e diverse altre classi di base che dovresti usare nel tuo progetto), il che rende più semplice determinare lo stato del ciclo di vita corrente . Dai uno sguardo al nostro problema: perché dovrebbe verificarsi questo problema? È perché facciamo qualcosa nel momento sbagliato. Quindi cerchiamo di non farlo, e questo problema sparirà.

    Scrivo un po 'di codice per il mio progetto, ecco cosa faccio usando LifeCycle. Codifico in Kotlin.

val hostActivity: AppCompatActivity? = null // the activity to host fragments. It's value should be properly initialized.

fun dispatchFragment(frag: Fragment) {
    hostActivity?.let {
       if(it.lifecyclecurrentState.isAtLeast(Lifecycle.State.RESUMED)){
           showFragment(frag)
       }
    }
}

private fun showFragment(frag: Fragment) {
    hostActivity?.let {
        Transaction.begin(it, R.id.frag_container)
                .show(frag)
                .commit()
    }

Come mostro sopra. Controllerò lo stato del ciclo di vita dell'attività dell'host. Con il componente Ciclo di vita all'interno della libreria di supporto, questo potrebbe essere più specifico. Il codice lifecyclecurrentState.isAtLeast(Lifecycle.State.RESUMED)significa, se lo stato corrente è almeno onResume, non oltre? Il che assicura che il mio metodo non verrà eseguito durante alcuni altri stati di vita (come onStop).

  • È tutto fatto?

    Ovviamente no. Il codice che ho mostrato indica un nuovo modo per impedire l'arresto anomalo dell'applicazione. Ma se va allo stato di onStop, quella riga di codice non farà cose e quindi non mostrerà nulla sullo schermo. Quando gli utenti tornano all'applicazione, vedranno uno schermo vuoto, ovvero l'attività dell'host vuota che non mostra alcun frammento. È una brutta esperienza (sì, un po 'meglio di un incidente).

    Quindi qui vorrei che ci potesse essere qualcosa di più bello: l'app non si arresterà in modo anomalo se lo stato di vita è successivo onResume, il metodo di transazione è consapevole dello stato di vita; inoltre, l'attività proverà a portare a termine quell'operazione di frammentazione delle transazioni, dopo che l'utente tornerà alla nostra app.

    Aggiungo qualcosa in più a questo metodo:

class FragmentDispatcher(_host: FragmentActivity) : LifecycleObserver {
    private val hostActivity: FragmentActivity? = _host
    private val lifeCycle: Lifecycle? = _host.lifecycle
    private val profilePendingList = mutableListOf<BaseFragment>()

    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    fun resume() {
        if (profilePendingList.isNotEmpty()) {
            showFragment(profilePendingList.last())
        }
    }

    fun dispatcherFragment(frag: BaseFragment) {
        if (lifeCycle?.currentState?.isAtLeast(Lifecycle.State.RESUMED) == true) {
            showFragment(frag)
        } else {
            profilePendingList.clear()
            profilePendingList.add(frag)
        }
    }

    private fun showFragment(frag: BaseFragment) {
        hostActivity?.let {
            Transaction.begin(it, R.id.frag_container)
                    .show(frag)
                    .commit()
        }
    }
}

Mantengo un elenco all'interno di questa dispatcherclasse, per archiviare quel frammento non ho la possibilità di completare l'azione della transazione. E quando l'utente torna dalla schermata principale e scopre che c'è ancora un frammento in attesa di essere lanciato, passerà al resume()metodo sotto l' @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)annotazione. Ora penso che dovrebbe funzionare come mi aspettavo.


8
Sarebbe bello usare Java invece di Kotlin
Shchvova,

1
Perché l'implementazione FragmentDispatcherdell'elenco utilizza un elenco per archiviare frammenti in sospeso se verrà ripristinato un solo frammento?
fraherm,

21

Ecco una soluzione diversa a questo problema.

Utilizzando una variabile membro privata è possibile impostare i dati restituiti come intento che può quindi essere elaborato dopo super.onResume ();

Così:

private Intent mOnActivityResultIntent = null; 

@Override
protected void onResume() {
    super.onResume();
    if(mOnActivityResultIntent != null){
        ... do things ...
        mOnActivityResultIntent = null;
    }
 }

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data){
    if(data != null){
        mOnActivityResultIntent = data;
    }
}

7
A seconda dell'azione che stavi facendo che non era consentita, potrebbe essere necessario passare a un momento anche più tardi di onResume (). Per insatcne, se il problema è FragmentTransaction.commit (), è necessario andare su onPostResume ().
pjv,

1
Questa è la risposta a questa domanda per me. Dato che avevo bisogno di inoltrare un tag NFC ricevuto all'attività precedente, questo è quello che ha fatto per me.
Janis Peisenieks,

7
Per me stava succedendo perché non stavo chiamando super.onActivityResult().
Sufian,

20

Soluzione breve e funzionante:

Segui semplici passaggi

passi

Passaggio 1: sovrascrivere lo onSaveInstanceStatestato nel rispettivo frammento. E rimuovi il metodo super da esso.

 @Override
public void onSaveInstanceState( Bundle outState ) {

}  

Passaggio 2: utilizzare fragmentTransaction.commitAllowingStateLoss( );

invece di fragmentTransaction.commit( ); mentre operazioni sui frammenti.


La risposta non viene copiata o referenziata da nessun'altra parte in cui è stata pubblicata per aiutare le persone dalla mia soluzione di lavoro ottenuta da numerosi tentativi ed errori
Vinayak,

12

ATTENZIONE , l'utilizzo transaction.commitAllowingStateLoss()potrebbe comportare una brutta esperienza per l'utente. Per ulteriori informazioni sul motivo per cui viene generata questa eccezione, vedere questo post .


6
Questo non fornisce una risposta alla domanda, è necessario fornire una risposta valida per la domanda
Umar Ata,

10

Ho trovato una soluzione sporca per questo tipo di problema. Se vuoi comunque mantenerlo ActivityGroupsper qualsiasi motivo (ho avuto motivi di limitazione del tempo), devi solo implementarlo

public void onBackPressed() {}

nel tuo Activitye fai un po 'di backcodice lì dentro. anche se non esiste tale metodo su dispositivi più vecchi, questo metodo viene chiamato da quelli più recenti.


6

Non utilizzare commitAllowingStateLoss (), dovrebbe essere utilizzato solo nei casi in cui è corretto che lo stato dell'interfaccia utente cambi in modo imprevisto sull'utente.

https://developer.android.com/reference/android/app/FragmentTransaction.html#commitAllowingStateLoss ()

Se la transazione si verifica in ChildFragmentManager di parentFragment, utilizzare invece parentFragment.isResume () all'esterno per verificare.

if (parentFragment.isResume()) {
    DummyFragment dummyFragment = DummyFragment.newInstance();
    transaction = childFragmentManager.BeginTransaction();
    trans.Replace(Resource.Id.fragmentContainer, startFragment);
}

5

Ho avuto un problema simile, lo scenario era così:

  • La mia attività sta aggiungendo / sostituendo frammenti di elenco.
  • Ogni frammento di elenco ha un riferimento all'attività, per notificare l'attività quando si fa clic su un elemento dell'elenco (modello osservatore).
  • Ogni frammento di elenco chiama setRetainInstance (true); nel suo metodo onCreate .

Il metodo onCreate dell'attività era così:

mMainFragment = (SelectionFragment) getSupportFragmentManager()
                .findFragmentByTag(MAIN_FRAGMENT_TAG);
        if (mMainFragment == null) {
            mMainFragment = new SelectionFragment();

            mMainFragment.setListAdapter(new ArrayAdapter<String>(this,
                    R.layout.item_main_menu, getResources().getStringArray(
                            R.array.main_menu)));
mMainFragment.setOnSelectionChangedListener(this);
            FragmentTransaction transaction = getSupportFragmentManager()
                    .beginTransaction();
            transaction.add(R.id.content, mMainFragment, MAIN_FRAGMENT_TAG);
            transaction.commit();
        }

L'eccezione è stata generata perché quando si modifica la configurazione (dispositivo ruotato), viene creata l'attività, il frammento principale viene recuperato dalla cronologia del gestore frammenti e allo stesso tempo il frammento ha già un riferimento OLD all'attività distrutta

cambiando l'implementazione in questo risolto il problema:

mMainFragment = (SelectionFragment) getSupportFragmentManager()
                .findFragmentByTag(MAIN_FRAGMENT_TAG);
        if (mMainFragment == null) {
            mMainFragment = new SelectionFragment();

            mMainFragment.setListAdapter(new ArrayAdapter<String>(this,
                    R.layout.item_main_menu, getResources().getStringArray(
                            R.array.main_menu)));
            FragmentTransaction transaction = getSupportFragmentManager()
                    .beginTransaction();
            transaction.add(R.id.content, mMainFragment, MAIN_FRAGMENT_TAG);
            transaction.commit();
        }
        mMainFragment.setOnSelectionChangedListener(this);

è necessario impostare i listener ogni volta che l'attività viene creata per evitare la situazione in cui i frammenti hanno riferimenti a vecchie istanze distrutte dell'attività.


5

Se erediti da FragmentActivity, devi chiamare la superclasse in onActivityResult():

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
    super.onActivityResult(requestCode, resultCode, intent);
    ...
}

Se non lo fai e provi a mostrare una finestra di dialogo frammento in quel metodo, potresti ottenere OP IllegalStateException. (Ad essere sincero, non capisco bene perché la super chiamata risolva il problema. onActivityResult()Viene chiamata prima onResume(), quindi non dovrebbe ancora essere consentito mostrare una finestra di dialogo dei frammenti.)


1
Mi piacerebbe sapere perché questo risolve il problema.
Big McLarge: enorme

3

Stavo ottenendo questa eccezione quando stavo premendo il pulsante Indietro per annullare la selezione dell'intento sulla mia attività di frammentazione della mappa. Ho risolto questo problema sostituendo il codice di onResume (dove stavo inizializzando il frammento) in onstart () e l'app funziona bene. Spero che sia d'aiuto.


2

Penso che l'utilizzo transaction.commitAllowingStateLoss();non sia la soluzione migliore. Questa eccezione verrà generata quando la configurazione dell'attività viene modificata e onSavedInstanceState()viene chiamato il frammento e successivamente il metodo di callback asincrono tenta di eseguire il commit del frammento.

La soluzione semplice potrebbe essere verificare se l'attività sta cambiando la configurazione o meno

ad es. controllo isChangingConfigurations()

vale a dire

if(!isChangingConfigurations()) { //commit transaction. }

Dai un'occhiata anche a questo link


In qualche modo ho ottenuto questa eccezione quando l'utente fa clic su qualcosa (il clic è il trigger per eseguire il commit della transazione). Come può essere? La tua soluzione qui?
sviluppatore Android

@androiddeveloper cos'altro stai facendo sul clic dell'utente. in qualche modo il frammento sta salvando il suo stato prima di eseguire la transazione
Amol Desai,

L'eccezione è stata generata sulla riga esatta del commit della transazione. Inoltre, ho avuto un errore di battitura strano: invece di "qui qui" intendevo "lavorare qui".
Sviluppatore Android

@androiddeveloper hai ragione! ma prima di effettuare una transazione stai generando qualche thread in background o qualcosa del genere?
Amol Desai,

Non la penso così (scusa se sono fuori dall'ufficio), ma perché dovrebbe importare? Sono tutte cose dell'interfaccia utente qui ... Se mai realizzassi qualcosa su un thread in background, avrei delle eccezioni lì, inoltre non inserisco roba correlata all'interfaccia utente sui thread in background, poiché è troppo rischioso.
sviluppatore Android

2

Forse la soluzione più liscia e più semplice che ho trovato nel mio caso è stata quella di evitare di far fuoriuscire il frammento offensivo dallo stack in risposta al risultato dell'attività. Quindi cambiando questa chiamata nel mio onActivityResult():

popMyFragmentAndMoveOn();

a questo:

new Handler(Looper.getMainLooper()).post(new Runnable() {
    public void run() {
        popMyFragmentAndMoveOn();
    }
}

aiutato nel mio caso.


2

Se stai eseguendo un po 'di FragmentTransaction in onActivityResult cosa puoi fare puoi impostare un valore booleano in onActivityResult, quindi in onResume puoi fare la tua FragmentTransaction sulla base del valore booleano. Si prega di fare riferimento al codice qui sotto.

@Override
protected void onResume() {
    super.onResume;
    if(isSwitchFragment){
        isSwitchFragment=false;
        bottomNavigationView.getTabAt(POS_FEED).select();
    }
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == FilterActivity.FILTER_REQUEST_EVENT && data != null) {
        isSwitchFragment=true;
    }
}

Si prega di non pubblicare codice come immagine, utilizzare invece la formattazione del codice.
Micer,

1
Sì, mi ha aiutato .., Questa è la soluzione esatta e corretta per il dispositivo marshmallow ho ottenuto questa eccezione e risolto con questo semplice trucco .. !! Quindi votato.
sandhya sasane,

2

Per gentile concessione: soluzione per IllegalStateException

Questo problema mi ha infastidito per molto tempo ma fortunatamente sono arrivato con una soluzione concreta per questo. Una spiegazione dettagliata di questo è qui .

L'uso di commitAllowStateloss () potrebbe impedire questa eccezione, ma porterebbe a irregolarità dell'interfaccia utente. Finora abbiamo capito che IllegalStateException si incontra quando proviamo a commettere un frammento dopo la perdita dello stato Attività, quindi dovremmo semplicemente ritardare la transazione fino a quando lo stato non viene ripristinato Può essere semplicemente fatto in questo modo

Dichiarare due variabili booleane private

 public class MainActivity extends AppCompatActivity {

    //Boolean variable to mark if the transaction is safe
    private boolean isTransactionSafe;

    //Boolean variable to mark if there is any transaction pending
    private boolean isTransactionPending;

Ora in onPostResume () e onPause impostiamo e disinseriamo la nostra variabile booleana isTransactionSafe. L'idea è quella di contrassegnare la trasnazione sicura solo quando l'attività è in primo piano, quindi non c'è possibilità di perdita di dati.

/*
onPostResume is called only when the activity's state is completely restored. In this we will
set our boolean variable to true. Indicating that transaction is safe now
 */
public void onPostResume(){
    super.onPostResume();
    isTransactionSafe=true;
}
/*
onPause is called just before the activity moves to background and also before onSaveInstanceState. In this
we will mark the transaction as unsafe
 */

public void onPause(){
    super.onPause();
    isTransactionSafe=false;

}

private void commitFragment(){
    if(isTransactionSafe) {
        MyFragment myFragment = new MyFragment();
        FragmentManager fragmentManager = getFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        fragmentTransaction.add(R.id.frame, myFragment);
        fragmentTransaction.commit();
    }
}

-Che cosa abbiamo fatto finora salverà da IllegalStateException ma le nostre transazioni andranno perse se vengono eseguite dopo che l'attività si sposta in background, un po 'come commitAllowStateloss (). Per aiutarci abbiamo la variabile booleana isTransactionPending

public void onPostResume(){
   super.onPostResume();
   isTransactionSafe=true;
/* Here after the activity is restored we check if there is any transaction pending from
the last restoration
*/
   if (isTransactionPending) {
      commitFragment();
   }
}


private void commitFragment(){

 if(isTransactionSafe) {
     MyFragment myFragment = new MyFragment();
     FragmentManager fragmentManager = getFragmentManager();
     FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
     fragmentTransaction.add(R.id.frame, myFragment);
     fragmentTransaction.commit();
     isTransactionPending=false;
 }else {
     /*
     If any transaction is not done because the activity is in background. We set the
     isTransactionPending variable to true so that we can pick this up when we come back to
foreground
     */
     isTransactionPending=true;
 }
}

2

Le transazioni di frammenti non dovrebbero essere eseguite dopo Activity.onStop()! Verifica di non avere alcun callback che potrebbe eseguire la transazione dopo onStop(). È meglio risolvere il motivo invece di cercare di aggirare il problema con approcci simili.commitAllowingStateLoss()


1

A partire dalla libreria di supporto versione 24.0.0 è possibile chiamare il FragmentTransaction.commitNow()metodo che esegue il commit di questa transazione in modo sincrono invece di chiamare commit()seguito da executePendingTransactions(). Come la documentazione dice ancora meglio questo approccio:

Chiamare commitNow è preferibile chiamare commit () seguito da executePendingTransactions () poiché quest'ultimo avrà l'effetto collaterale di tentare di eseguire il commit di tutte le transazioni attualmente in sospeso, indipendentemente dal fatto che si tratti del comportamento desiderato.


1

Ogni volta che si sta tentando di caricare un frammento nella propria attività, assicurarsi che l'attività sia in ripresa e non vada in pausa. In pausa, si potrebbe finire per perdere l'operazione di commit eseguita.

È possibile utilizzare transazione.commitAllowingStateLoss () invece diaction.commit () per caricare il frammento

o

Crea un valore booleano e controlla se l'attività non verrà interrotta

@Override
public void onResume() {
    super.onResume();
    mIsResumed = true;
}

@Override
public void onPause() {
    mIsResumed = false;
    super.onPause();
}

quindi durante il caricamento del controllo frammento

if(mIsResumed){
//load the your fragment
}

1

Per aggirare questo problema, possiamo utilizzare The Navigation Architecture Component , introdotto in Google I / O 2018. Il Navigation Architecture Component semplifica l'implementazione della navigazione in un'app Android.


Non è abbastanza buono e bugy (scarsa gestione dei deeplink, impossibile salvare i frammenti di show / nascondi stato e qualche problema importante che è ancora aperto)
do01

1

Per quanto riguarda la grande risposta di @Anthonyeef, ecco un codice di esempio in Java:

private boolean shouldShowFragmentInOnResume;

private void someMethodThatShowsTheFragment() {

    if (this.getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) {
        showFragment();
    } else {
        shouldShowFragmentInOnResume = true;
    }
}

private void showFragment() {
    //Your code here
}

@Override
protected void onResume() {
    super.onResume();

    if (shouldShowFragmentInOnResume) {
        shouldShowFragmentInOnResume = false;
        showFragment();
    }
}

1

Se si verifica un arresto anomalo con il metodo popBackStack () o popBackStackImmediate (), provare fixt con:

        if (!fragmentManager.isStateSaved()) {
            fragmentManager.popBackStackImmediate();
        }

Questo ha funzionato anche per me.


Si noti che richiede API 26 e successive
Itay Feldman,

1

Nel mio caso ho riscontrato questo errore in un metodo di sostituzione chiamato onActivityResult. Dopo aver scavato ho appena capito che forse avevo bisogno di chiamare " super " prima.
L'ho aggiunto e ha funzionato

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data); //<--- THIS IS THE SUPPER CALL
    if (resultCode == Activity.RESULT_OK && requestCode == 0) {
        mostrarFragment(FiltroFragment.newInstance())
    }

}

Forse devi solo aggiungere un "super" a qualsiasi override che stai facendo prima del tuo codice.


1

Estensione Kotlin

fun FragmentManager?.replaceAndAddToBackStack(
    @IdRes containerViewId: Int,
    fragment: () -> Fragment,
    tag: String
) {
    // Find and synchronously remove a fragment with the same tag.
    // The second transaction must start after the first has finished.
    this?.findFragmentByTag(tag)?.let {
        beginTransaction().remove(it).commitNow()
    }
    // Add a fragment.
    this?.beginTransaction()?.run {
        replace(containerViewId, fragment, tag)
        // The next line will add the fragment to a back stack.
        // Remove if not needed.
        // You can use null instead of tag, but tag is needed for popBackStack(), 
        // see https://stackoverflow.com/a/59158254/2914140
        addToBackStack(tag)
    }?.commitAllowingStateLoss()
}

Uso:

val fragment = { SomeFragment.newInstance(data) }
fragmentManager?.replaceAndAddToBackStack(R.id.container, fragment, SomeFragment.TAG)

Puoi rimuovere il () -> prima del frammento
Claire

@Claire, grazie! Intendi cambiare in fragment: Fragment? Sì, ho provato questa variante, ma in questo caso sarebbe stato creato un frammento in tutti i casi (anche quando fragmentManager == null, ma non ho riscontrato questa situazione). Ho aggiornato la risposta e modificato null per taggare addToBackStack().
CoolMind


0

Aggiungi questo nella tua attività

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    if (outState.isEmpty()) {
        // Work-around for a pre-Android 4.2 bug
        outState.putBoolean("bug:fix", true);
    }
}

0

Ho riscontrato anche questo problema e il problema si verifica ogni volta che FragmentActivityviene modificato il contesto del tuo (ad esempio, l'orientamento dello schermo viene modificato, ecc.). Quindi la soluzione migliore è aggiornare il contesto dal tuo FragmentActivity.


0

Ho finito con la creazione di un frammento di base e ho esteso tutti i frammenti nella mia app

public class BaseFragment extends Fragment {

    private boolean mStateSaved;

    @CallSuper
    @Override
    public void onSaveInstanceState(Bundle outState) {
        mStateSaved = true;
        super.onSaveInstanceState(outState);
    }

    /**
     * Version of {@link #show(FragmentManager, String)} that no-ops when an IllegalStateException
     * would otherwise occur.
     */
    public void showAllowingStateLoss(FragmentManager manager, String tag) {
        // API 26 added this convenient method
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            if (manager.isStateSaved()) {
                return;
            }
        }

        if (mStateSaved) {
            return;
        }

        show(manager, tag);
    }
}

Quindi quando provo a mostrare un frammento che uso al showAllowingStateLossposto dishow

come questo:

MyFragment.newInstance()
.showAllowingStateLoss(getFragmentManager(), MY_FRAGMENT.TAG);

Sono arrivato a questa soluzione da questo PR: https://github.com/googlesamples/easypermissions/pull/170/files


0

Un'altra possibile soluzione alternativa, che non sono sicuro se aiuta in tutti i casi (origine qui ):

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        final View rootView = findViewById(android.R.id.content);
        if (rootView != null) {
            rootView.cancelPendingInputEvents();
        }
    }
}

0

So che c'è una risposta accettata da @Ovidiu Latcu ma dopo qualche tempo, l'errore persiste ancora.

@Override
protected void onSaveInstanceState(Bundle outState) {
     //No call for super(). Bug on API Level > 11.
}

Crashlytics mi sta ancora inviando questo strano messaggio di errore.

Tuttavia, l'errore si è verificato solo nella versione 7+ (Nougat). La mia correzione era utilizzare commitAllowingStateLoss () invece di commit () su fragmentTransaction.

Questo post è utile per commitAllowingStateLoss () e non ha mai più avuto un problema di frammento.

Per riassumere, la risposta accettata qui potrebbe funzionare su versioni Android precedenti a Nougat.

Questo potrebbe far risparmiare a qualcuno qualche ora di ricerche. felice coding. <3 evviva

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.