obteniendo la excepción "IllegalStateException: no se puede realizar esta acción después de onSaveInstanceState"
Tengo una aplicación Live Android y del mercado recibí el siguiente seguimiento de pila y no tengo idea de por qué sucede, ya que no sucede en el código de la aplicación, sino que es causado por algún evento de la aplicación (suposición).
No estoy usando Fragmentos, todavía hay una referencia de FragmentManager. Si alguien puede arrojar algo de luz sobre algunos hechos ocultos para evitar este tipo de problemas:
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.onKeyDown(Activity.java:1962)
at android.view.KeyEvent.dispatch(KeyEvent.java:2482)
at android.app.Activity.dispatchKeyEvent(Activity.java:2274)
at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1668)
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.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:1720)
at com.android.internal.policy.impl.PhoneWindow.superDispatchKeyEvent(PhoneWindow.java:1258)
at android.app.Activity.dispatchKeyEvent(Activity.java:2269)
at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchKeyEvent(PhoneWindow.java:1668)
at android.view.ViewRoot.deliverKeyEventPostIme(ViewRoot.java:2851)
at android.view.ViewRoot.handleFinishedEvent(ViewRoot.java:2824)
at android.view.ViewRoot.handleMessage(ViewRoot.java:2011)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:132)
at android.app.ActivityThread.main(ActivityThread.java:4025)
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:841)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:599)
at dalvik.system.NativeStart.main(Native Method)
Este es el error más estúpido que he encontrado hasta ahora. Tenía una Fragment
aplicación que funcionaba perfectamente para API <11 y Force Closing
para API> 11 .
Realmente no pude entender qué cambiaron dentro del Activity
ciclo de vida en la llamada a saveInstance
, pero así es como resolví esto:
@Override
protected void onSaveInstanceState(Bundle outState) {
//No call for super(). Bug on API Level > 11.
}
Simplemente no hago la llamada .super()
y todo funciona muy bien. Espero que esto te ahorre algo de tiempo.
EDITAR: después de investigar un poco más, este es un error conocido en el paquete de soporte.
Si necesita guardar la instancia y agregar algo, outState
Bundle
puede usar lo siguiente:
@Override
protected void onSaveInstanceState(Bundle outState) {
outState.putString("WORKAROUND_FOR_BUG_19917_KEY", "WORKAROUND_FOR_BUG_19917_VALUE");
super.onSaveInstanceState(outState);
}
EDITAR2: esto también puede ocurrir si está intentando realizar una transacción después de que ya Activity
no esté en segundo plano. Para evitar esto debes usarcommitAllowingStateLoss()
EDITAR3: Las soluciones anteriores solucionaban problemas en las primeras bibliotecas support.v4, por lo que puedo recordar. Pero si aún tiene problemas con esto, DEBE leer también el blog de @AlexLockwood : Transacciones de fragmentos y pérdida de estado de actividad.
Resumen de la publicación del blog (pero te recomiendo encarecidamente que lo leas):
- NUNCA realice
commit()
transacciones despuésonPause()
de antes yonStop()
después de Honeycomb - Tenga cuidado al realizar transacciones dentro de
Activity
métodos de ciclo de vida. usoonCreate()
yonResumeFragments()
_onPostResume()
- Evite realizar transacciones dentro de métodos de devolución de llamada asíncronos
- Úselo
commitAllowingStateLoss()
solo como último recurso
Al buscar en el código fuente de Android las causas de este problema, el indicador mStateSaved en FragmentManagerImpl
la clase (instancia disponible en Actividad) tiene el valor verdadero. Se establece en verdadero cuando se guarda la pila de actividades (saveAllState) en una llamada desde Activity#onSaveInstanceState
. Posteriormente, las llamadas de ActivityThread no restablecen este indicador utilizando los métodos de restablecimiento disponibles desde FragmentManagerImpl#noteStateNotSaved()
y dispatch()
.
A mi modo de ver, hay algunas correcciones disponibles, dependiendo de lo que esté haciendo y usando su aplicación:
buenas maneras
Antes que nada: anunciaría el artículo de Alex Lockwood . Entonces, por lo que he hecho hasta ahora:
Para fragmentos y actividades que no necesitan conservar ninguna información de estado, llame a commitAllowStateLoss . Tomado de la documentación:
Permite que la confirmación se ejecute después de guardar el estado de una actividad. Esto es peligroso porque la confirmación se puede perder si la actividad necesita ser restaurada posteriormente desde su estado, por lo que esto solo debe usarse en casos en los que está bien que el estado de la interfaz de usuario cambie inesperadamente en el usuario. Supongo que está bien usarlo si el fragmento muestra información de solo lectura. O incluso si muestran información editable, utilice los métodos de devolución de llamada para conservar la información editada.
Justo después de confirmar la transacción (acabas de llamar
commit()
), haz una llamada aFragmentManager.executePendingTransactions()
.
Formas no recomendadas:
Como Ovidiu Latcu mencionó anteriormente, no llames
super.onSaveInstanceState()
. Pero esto significa que perderá todo el estado de su actividad junto con el estado de los fragmentos.Anular
onBackPressed
y allí llamar sólofinish()
. Esto debería estar bien si su aplicación no usa Fragments API; como ensuper.onBackPressed
hay una llamada aFragmentManager#popBackStackImmediate()
.Si está utilizando la API de Fragmentos y el estado de su actividad es importante/vital, entonces podría intentar llamar usando la API de reflexión
FragmentManagerImpl#noteStateNotSaved()
. Pero esto es un truco, o se podría decir que es una solución alternativa. No me gusta, pero en mi caso es bastante aceptable ya que tengo un código de una aplicación heredada que usa código obsoleto (TabActivity
e implícitamenteLocalActivityManager
).
A continuación se muestra el código que utiliza la reflexión:
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
invokeFragmentManagerNoteStateNotSaved();
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private void invokeFragmentManagerNoteStateNotSaved() {
/**
* For post-Honeycomb devices
*/
if (Build.VERSION.SDK_INT < 11) {
return;
}
try {
Class cls = getClass();
do {
cls = cls.getSuperclass();
} while (!"Activity".equals(cls.getSimpleName()));
Field fragmentMgrField = cls.getDeclaredField("mFragments");
fragmentMgrField.setAccessible(true);
Object fragmentMgr = fragmentMgrField.get(this);
cls = fragmentMgr.getClass();
Method noteStateNotSavedMethod = cls.getDeclaredMethod("noteStateNotSaved", new Class[] {});
noteStateNotSavedMethod.invoke(fragmentMgr, new Object[] {});
Log.d("DLOutState", "Successful call for noteStateNotSaved!!!");
} catch (Exception ex) {
Log.e("DLOutState", "Exception on worka FM.noteStateNotSaved", ex);
}
}
¡Salud!
Se producirá una excepción de este tipo si intenta realizar una transición de fragmento después de que onSaveInstanceState()
se llame a su actividad de fragmento.
Una de las razones por las que esto puede suceder es si deja un AsyncTask
(o Thread
) en ejecución cuando se detiene una actividad.
Cualquier transición posterior onSaveInstanceState()
a la llamada podría perderse si el sistema reclama la actividad para obtener recursos y la recrea más tarde.
Simplemente llame a super.onPostResume() antes de mostrar su fragmento o mueva su código en el método onPostResume() después de llamar a super.onPostResume(). ¡Esto resuelve el problema!
Esto también puede suceder cuando se llama dismiss()
a un fragmento de diálogo después de que la pantalla se haya bloqueado/blanqueado y se haya guardado el estado de la instancia del diálogo Actividad +. Para evitar esta llamada:
dismissAllowingStateLoss()
Literalmente, cada vez que descarto un diálogo ya no me importa su estado, así que está bien hacerlo: en realidad no estás perdiendo ningún estado.