Las cookies nos permiten ofrecer nuestros servicios. Al utilizar nuestros servicios, aceptas el uso que hacemos de las cookies. Más Información. Aceptar

FragmentTransaction y pérdidas de estado de Activities en Android

Antonio Domínguez Crespo
  • Escrito por Antonio Domínguez Crespo el 16 de Septiembre de 2013
  • <1 min de lectura | Mobile
FragmentTransaction y pérdidas de estado de Activities en Android

El siguiente mensaje de error  ha plagado los foros de consultas técnicas desde que se liberó Honeycomb:

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1341)
at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1352)
at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:595)
at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:574)
En este post te explicaremos cuando y por qué se produce esta excepción, y terminaremos con algunas sugerencias para que tus nuevas aplicaciones nunca más fallen.

¿Por qué se produce este error?

La excepción se produce porque se ha intentado cometer un FragmentTransaction después de que el estado de una activity se haya salvado, lo que resulta en un fenómeno conocido como la pérdida del estado de actividad. Antes de entrar en los detalles de lo que esto significa, sin embargo, primero vamos a echar un vistazo a lo que sucede cuando se llama a onSaveInstanceState() . Las aplicaciones Android tienen muy poco control sobre su destino en el entorno de ejecución. El sistema Android tiene la capacidad de terminar los procesos en cualquier momento para liberar memoria y las actividades en segundo plano pueden terminar con poca o ninguna advertencia como resultado. Para asegurar que este comportamiento a veces errático permanece oculta para el usuario, el framework le da a cada actividad una oportunidad de salvar su estado llamando a su método onSaveInstanceState() antes de realizar la actividad vulnerable a la destrucción. Cuando el estado guardado más tarde se restaura, el usuario tendrá la impresión de que están cambiando sin problemas entre el primer plano y las actividades en segundo plano, independientemente de si la actividad había sido matada por el sistema. Cuando el framework llama al método onSaveInstanceState() , pasa un objeto de tipo Bundle para la activity que se usa para salvar su estado, y la Activiy graba los registros en el estado que están sus diálogos, fragments y views. Cuando se devuelve el método, los paquetes de sistema del Bundle a través de una interfaz de vínculo para el proceso del sistema del servidor, donde se almacena de manera segura. Cuando el sistema más tarde decide volver a crear la activity, envía este mismo objeto Bundle de nuevo a la aplicación, para que lo pueda utilizar para restaurar el antiguo estado de la Activity. Así que ¿por qué entonces se lanza la excepción? Bueno, el problema radica en el hecho de que estos objetos Bundle representan una instantánea de una activity en el momento que onSaveInstanceState() fue llamado, y nada más. Esto significa que cuando llamemos a FragmentTransaction#commit () después de que onSaveInstanceState() fue llamada, la transacción no será grabada porque nunca se registró como parte del estado de la actividad. Desde el punto de vista del usuario, la transacción aparecerá como perdida, lo que resulta en la pérdida accidental del estado de la interfaz de usuario. Con el fin de proteger la experiencia del usuario, Android evita la pérdida del estado a toda costa, y simplemente lanza un IllegalStateException cada vez que se produce.

¿Cuando se lanza la excepción?

Si has encontrado esta excepción antes, te habrás dado cuenta de que el momento en que se produce es un poco inconsistente en diferentes versiones de la plataforma. Por ejemplo, es posible que haya encontrado que los dispositivos más antiguos tendían a producir la excepción con menor frecuencia, o que su aplicación es más probable que falle cuando se utiliza la biblioteca de compatibilidad que cuando se utilizan las clases del framework oficiales. Estas ligeras inconsistencias han llevado a muchos a suponer que la biblioteca de soporte tiene errores y no es confiable. Estos supuestos, sin embargo, por lo general no son ciertas. La razón de por qué existen estas ligeras inconsistencias se debe a un cambio significativo en el ciclo de vida de la actividad que se realizó en Honeycomb. Antes de Honeycomb, las activities no se consideraron "matables" hasta después de haber sido detenidas, lo que significa que onSaveInstanceState() se llama inmediatamente antes de onPause() . A partir de Honeycomb, sin embargo, las activities se consideran "matables" sólo después de que se hayan detenido, lo que significa que onSaveInstanceState() ahora se llama antes de OnStop() en lugar de inmediatamente antes de onPause() . Estas diferencias se resumen en la siguiente tabla:
pre-Honeycomb post-Honeycomb
¿Activities pueden ser matadas antes de onPause() ? NO NO
¿Activities pueden ser matadas antes de o nStop() ? YES NO
onSaveInstanceState(Bundle) es garantizado antes de llamar... onPause() onStop()
Como resultado de los ligeros cambios que se realizaron en el ciclo de vida de activity, la biblioteca de compatibilidad a veces tiene que alterar su comportamiento en función de la versión de la plataforma. Por ejemplo, en los dispositivos con Honeycomb y superior, una excepción será lanzada cada uno y cada vez que un commit() sea llamado después de onSaveInstanceState() para avisar al desarrollador que se ha producido la pérdida de estado. Sin embargo, lanzar una excepción cada vez que esto sucedía sería demasiado restrictivo en dispositivos pre-Honeycomb, que tienen su método onSaveInstanceState() llamado mucho antes en el ciclo de vida de activity y son más vulnerables a la pérdida de estado accidental como consecuencia de ello. El equipo de Android se vio obligado a hacer un compromiso: para una mejor interoperación con las versiones anteriores de la plataforma, los dispositivos más antiguos tendrían que vivir con la pérdida del estado accidental que pudiera resultar entre onPause() y OnStop() . El comportamiento de la biblioteca de soporte de compatibilidades a través de las dos plataformas se resume en el siguiente cuadro:
pre-Honeycomb post-Honeycomb
commit() antes de onPause() OK OK
commit() entre onPause() y onStop() STATE LOSS OK
commit() después de onStop() EXCEPTION EXCEPTION

¿Cómo evitar la excepción?

Evitar la pérdida del estado de actividad se convierte en una tarea mucho más fácil una vez que entienda lo que realmente está pasando. Si has llegado hasta aquí en el post, esperamos que pueda entender un poco mejor cómo funciona la biblioteca de compatibilidad y por qué es tan importante para evitar la pérdida del estado en las aplicaciones. Te dejamos algunas sugerencias a tener en cuenta cuando trabajes con FragmentTransactions en tus aplicaciones:
  • Tenga cuidado al cometer transacciones dentro de los métodos del ciclo de vida de activity . Una gran mayoría de las aplicaciones sólo se comprometerá la primera vez que se llame a onCreate() y/ o en respuesta a la entrada del usuario, y nunca se enfrentará a problemas como resultado. Sin embargo, como sus transacciones comiencen a realizarse en los otros métodos del ciclo de vida de actividad, tales como onActivityResult() , onStart() , y onResume() , las cosas pueden ser un poco más complicadas. Por ejemplo, no debería realizar transacciones dentro del método FragmentActivity#onResume() , ya que hay algunos casos en los que el método puede ser invocado antes de que se haya restaurado el estado de la actividad (ver la documentación para obtener más información). Si su aplicación requiere confirmar una transacción en un método del ciclo de vida de la activity que no sea onCreate() , trate de hacerlo en cualquiera de los métodos FragmentActivity# onResumeFragments() o Activity#onPostResume () . Se garantiza que estas dos formas de ser llamado después de que la activity sea restaurado a su estado original, y por lo tanto evitar la posibilidad de pérdida de estado.
  • Evite realizar transacciones dentro de los métodos de devolución de llamada asíncrona . Esto incluye métodos comúnmente utilizados tales como AsyncTask#OnPostExecute() y LoaderManager.LoaderCallbacks # onLoadFinished (). El problema de realizar transacciones en estos métodos es que no tienen ningún conocimiento de la situación actual del ciclo de vida de la activity cuando se les llama. Por ejemplo, considere la siguiente secuencia de eventos:
    1. Una actividad que ejecuta una AsyncTask.
    2. El usuario pulsa la tecla "Home", causando onSaveInstanceState de la actividad () y OnStop () para ser llamado.
    3. La tarea en segundo plano del AsyncTask se completa y OnPostExecute () es llamado, sin saber que su activity ya se ha detenido.
    4. Un FragmentTransaction se ha comprometido, lo que resulta en la pérdida de estado y lanzando una excepción.

En general, la mejor manera de evitar la excepción en estos casos es simplemente evitar cometer transacciones en los métodos de devolución de llamada asíncrona. Los ingenieros de Google parecen estar de acuerdo con esta creencia también. De acuerdo con este post en el grupo de desarrolladores de Android, tiene en cuenta los importantes cambios en la interfaz de usuario que pueden resultar de cometer FragmentTransactions dentro de los métodos de devolución de llamada asíncrona por ser malo para la experiencia del usuario. Si su aplicación requiere la realización de la transacción dentro de los métodos de devolución de llamada y no hay manera fácil de garantizar que la devolución de llamada no se invocará después onSaveInstanceState() , es posible que tenga que recurrir al uso commitAllowingStateLoss() y hacer frente a la pérdida del estado que podrían ocurrir.

  • Utilice commitAllowingStateLoss () sólo como último recurso . La única diferencia entre llamar a commit() y commitAllowingStateLoss() es que éste nunca lanzará una excepción si se produce la pérdida de estado. Por lo general, usted no desea utilizar este método, ya que implica que existe la posibilidad de que la pérdida del estado podría suceder. La mejor solución, por supuesto, es escribir la aplicación para que commit() se garantice ser llamado antes de que el estado de la actividad se haya salvado, ya que esto dará lugar a una mejor experiencia de usuario. A menos que no se puede evitar la posibilidad de la pérdida de estado, commitAllowingStateLoss() no debe ser utilizado.
Esperemos que estos consejos les ayude a resolver cualquier problema que haya tenido con esta excepción en el pasado. Como siempre, gracias por leer, y le invitamos a dejar un comentario si tiene alguna pregunta.

Estas son algunas de las empresas que ya confían en OpenWebinars

Profesores y profesionales

Nuestros docentes son profesionales que trabajan día a día en la materia que imparten

Conviértete en profesor de OpenWebinars