library - com.android.support:design 26
commitAllowingStateLoss() y commit() fragmento (6)
Bueno, encontré el mismo problema y encontré una solución muy simple. Dado que Android OS no tiene ninguna solución, esa vez (todavía no tengo una buena, a pesar de que commitAllowingStateLoss()
es una de sus soluciones, y usted conoce el resto).
La solución fue escribir una clase de controlador que almacena los mensajes cuando pasa la actividad y los reproduce en onResume
nuevamente.
Al utilizar esta clase, asegúrese de que se llame a todo el código desde el que se asynchronously
un mensaje en este `controlador.
Extienda FragmenntPauseHandler
desde la clase de manejador.
Cada vez que su actividad recibe una llamada FragmenntPauseHandler.pause()
y para onResume()
llame a FragmenntPauseHandler.resume()
.
Reemplace su implementación del Handler handleMessage()
con processMessage()
.
Proporcionar una implementación simple de storeMessage()
que siempre devuelve true.
/**
* Message Handler class that supports buffering up of messages when the
* activity is paused i.e. in the background.
*/
public abstract class FragmenntPauseHandler extends Handler {
/**
* Message Queue Buffer
*/
final Vector<Message> messageQueueBuffer = new Vector<Message>();
/**
* Flag indicating the pause state
*/
private boolean paused;
/**
* Resume the handler
*/
final public void resume() {
paused = false;
while (messageQueueBuffer.size() > 0) {
final Message msg = messageQueueBuffer.elementAt(0);
messageQueueBuffer.removeElementAt(0);
sendMessage(msg);
}
}
/**
* Pause the handler
*/
final public void pause() {
paused = true;
}
/**
* Notification that the message is about to be stored as the activity is
* paused. If not handled the message will be saved and replayed when the
* activity resumes.
*
* @param message
* the message which optional can be handled
* @return true if the message is to be stored
*/
protected abstract boolean storeMessage(Message message);
/**
* Notification message to be processed. This will either be directly from
* handleMessage or played back from a saved message when the activity was
* paused.
*
* @param message
* the message to be handled
*/
protected abstract void processMessage(Message message);
/** {@inheritDoc} */
@Override
final public void handleMessage(Message msg) {
if (paused) {
if (storeMessage(msg)) {
Message msgCopy = new Message();
msgCopy.copyFrom(msg);
messageQueueBuffer.add(msgCopy);
}
} else {
processMessage(msg);
}
}
}
A continuación se muestra un ejemplo simple de cómo se puede usar la clase PausedHandler.
Al hacer clic en un Button
se envía un mensaje de retraso al handler
.
Cuando el handler
recibe el mensaje (en el subproceso de la interfaz de usuario) muestra un DialogFragment
.
Si no se utilizara la clase FragmenntPauseHandler
, se IllegalStateException
una IllegalStateException
si se presionara el botón de inicio después de presionar el botón de prueba para iniciar el cuadro de diálogo.
public class FragmentTestActivity extends Activity {
/**
* Used for "what" parameter to handler messages
*/
final static int MSG_WHAT = (''F'' << 16) + (''T'' << 8) + ''A'';
final static int MSG_SHOW_DIALOG = 1;
int value = 1;
final static class State extends Fragment {
static final String TAG = "State";
/**
* Handler for this activity
*/
public ConcreteTestHandler handler = new ConcreteTestHandler();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
@Override
public void onResume() {
super.onResume();
handler.setActivity(getActivity());
handler.resume();
}
@Override
public void onPause() {
super.onPause();
handler.pause();
}
public void onDestroy() {
super.onDestroy();
handler.setActivity(null);
}
}
/**
* 2 second delay
*/
final static int DELAY = 2000;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
if (savedInstanceState == null) {
final Fragment state = new State();
final FragmentManager fm = getFragmentManager();
final FragmentTransaction ft = fm.beginTransaction();
ft.add(state, State.TAG);
ft.commit();
}
final Button button = (Button) findViewById(R.id.popup);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final FragmentManager fm = getFragmentManager();
State fragment = (State) fm.findFragmentByTag(State.TAG);
if (fragment != null) {
// Send a message with a delay onto the message looper
fragment.handler.sendMessageDelayed(
fragment.handler.obtainMessage(MSG_WHAT, MSG_SHOW_DIALOG, value++),
DELAY);
}
}
});
}
public void onSaveInstanceState(Bundle bundle) {
super.onSaveInstanceState(bundle);
}
/**
* Simple test dialog fragment
*/
public static class TestDialog extends DialogFragment {
int value;
/**
* Fragment Tag
*/
final static String TAG = "TestDialog";
public TestDialog() {
}
public TestDialog(int value) {
this.value = value;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final View inflatedView = inflater.inflate(R.layout.dialog, container, false);
TextView text = (TextView) inflatedView.findViewById(R.id.count);
text.setText(getString(R.string.count, value));
return inflatedView;
}
}
/**
* Message Handler class that supports buffering up of messages when the
* activity is paused i.e. in the background.
*/
static class ConcreteTestHandler extends FragmenntPauseHandler {
/**
* Activity instance
*/
protected Activity activity;
/**
* Set the activity associated with the handler
*
* @param activity
* the activity to set
*/
final void setActivity(Activity activity) {
this.activity = activity;
}
@Override
final protected boolean storeMessage(Message message) {
// All messages are stored by default
return true;
};
@Override
final protected void processMessage(Message msg) {
final Activity activity = this.activity;
if (activity != null) {
switch (msg.what) {
case MSG_WHAT:
switch (msg.arg1) {
case MSG_SHOW_DIALOG:
final FragmentManager fm = activity.getFragmentManager();
final TestDialog dialog = new TestDialog(msg.arg2);
// We are on the UI thread so display the dialog
// fragment
dialog.show(fm, TestDialog.TAG);
break;
}
break;
}
}
}
}
}
He agregado un método storeMessage()
a la clase FragmenntPauseHandler
en caso de que los mensajes se procesen de inmediato, incluso cuando la actividad está en pausa. Si se maneja un mensaje, entonces se debe devolver falso y el mensaje se descartará. Espero que ayude. Utilicé esta en 4 aplicaciones y nunca más tuve el mismo problema.
Quiero cometer un fragmento después de la operación de fondo de red. Estaba llamando a commit () después de una operación de red exitosa, pero en caso de que la actividad fuera a pausar o detener el estado, se estaba fallando la aplicación que decía la excepción IllegalState.
Así que intenté usar commitAllowingStateLoss () y ahora funciona bien.
Revisé algunos blogs y artículos que dicen que commitAllowingStateLoss () no es bueno de usar.
¿Cuál es la manera de manejar el fragmento de confirmación después de que la actividad de manejo de la operación de red detenga y detenga el estado?
Me gustaría agregar información a Aritra Roy (hasta ahora he leído, es una muy buena respuesta).
Encontré el problema anteriormente y encontré que el problema principal es que está intentando realizar algunas operaciones asíncronas (HTTP, cálculos, ...) en otro hilo, lo cual es una buena práctica, pero debe informar a su usuario DESPUÉS de recibir respuestas
El problema principal es que, dado que se trata de operaciones asíncronas, ya no hay garantía de que el usuario aún esté en su actividad / aplicación. Y si se fue, no hay necesidad de hacer cambios en la interfaz de usuario. Además, como Android puede matar su aplicación / actividad por problemas de memoria, no tiene garantías de poder obtener su respuesta y guardarla para restaurarla. El problema no es solo que "el usuario puede abrir otra aplicación", sino que "mi actividad se puede recrear a partir del cambio de configuración" y es posible que esté intentando realizar cambios en la interfaz de usuario durante la recreación de la actividad, lo que sería realmente malo.
Usar "commitAllowingStateLoss" es como decir "no me importa si la IU no está realmente en buen estado". Puedes hacerlo por cosas pequeñas (como activar un gif que dice que tu descarga ha finalizado) ... Eso no es un gran problema, y este problema realmente no vale la pena tratarlo, ya que "en general" el usuario permanecerá en tu aplicación.
Pero, el usuario hizo algo, está intentando obtener información en la web, la información está lista y debe mostrarla cuando el usuario reanude la aplicación ... la palabra principal es " reanudar ".
Debe reunir los datos que necesita en una variable (y si puede, una variable parcelable o primitiva), y luego, anular sus funciones "onResume" u "onPostResume" (para actividades) de la siguiente manera.
public void onResume/onPostResume() {
super.onResume/onPostResume();
if(someTreatmentIsPending) {
/*do what you need to do with your variable here : fragment
transactions, dialog showing...*/
}
}
Información adicional: Este tema y especialmente @jed answer, y @pjv, @Sufian le hacen comentarios. Este blog para comprender por qué se produce el error y por qué funcionan las respuestas propuestas / aceptadas.
Última palabra: en caso de que se pregunte "por qué usar un servicio es mejor que asyncTask". Por lo que entendí, eso no es realmente mejor. La principal diferencia es que usar el servicio correctamente le permite registrar / anular el registro de los manejadores cuando su actividad se detiene / reanuda. Por lo tanto, siempre obtiene sus respuestas cuando su actividad está activa, evitando que ocurra el error.
Tenga en cuenta que no es porque no se produce el error que está a salvo. Si realizó cambios directamente en sus vistas, no hay fragmentos de Transacciones involucradas, por lo tanto, no hay garantía de que el cambio se retendrá y recreará cuando la aplicación se vuelva a crear, reanudar, relanzar o cualquier otra cosa.
Solo verifique si la actividad está terminando o no, y luego commit()
la transacción.
if (!isFinishing()) {
// commit transaction
}
La excepción que obtienes está en un escenario, cuando se termina la actividad, y estás intentando confirmar una nueva transacción, lo que obviamente no será guardado por FragmentManager
porque ya se ha ejecutado onSavedInstanceState()
. Es por eso que el marco te obliga a implementarlo "correctamente".
Trataré de explicarte todo en detalle.
FragmentTransaction
en la biblioteca de soporte proporciona cuatro formas de confirmar una transacción,
1) commit()
3) commitNow()
4) commitNowAllowingStateLoss()
Probablemente esté recibiendo una IllegalStateException
que dice que no puede confirmar después de que se onSaveInstanceState()
. Revise esta publicación que describe por qué se lanza esta excepción en primer lugar.
El commit()
y el commitAllowingStateLoss()
son casi idénticos en su implementación con una diferencia, commit()
verifica si el estado ya se ha guardado, y si es así, lanza una IllegalStateException
. Puede comprobar el código fuente usted mismo.
Entonces, ¿pierde el estado cada vez que llama a commitAllowingStateLoss()
? No, ciertamente no . PUEDE perder el estado del FragmentManager o cualquier otro fragmento agregado o eliminado después de onSaveInstanceState () si la aplicación es eliminada.
Aquí hay un escenario práctico para su caso de uso:
- Tu actividad está mostrando FragmentA
- Su actividad pasa a segundo plano y se llama a
onSaveInstanceState()
- Su operación de red se completa y reemplaza FragmentA con FragmentB utilizando
commitAllowingStateLoss()
Estas son las dos cosas posibles que pueden suceder ahora.
Si el sistema eliminó su aplicación debido a la falta de memoria, se volverá a crear la aplicación con el estado guardado realizado en el paso 2. FragmentB no será visible. Esto es cuando pierdes el estado .
Si el sistema no eliminó su aplicación y aún está en la memoria, se volverá a poner en primer plano y se mostrará FragmentB. En este caso, no has perdido el estado.
Puede comprobar this proyecto Github y probar este escenario usted mismo.
Si ha activado la opción de desarrollador "No mantener las actividades" , experimentará el primer escenario en el que el estado realmente se pierde.
Si tiene desactivada esa opción, experimentará el segundo escenario en el que no se pierde ningún estado.
Una forma sencilla sería esperar a que se reanude la actividad, para que pueda commit
su acción, una solución simple se vería así:
@Override
public void onNetworkResponse(){
//Move to next fragmnt if Activity is Started or Resumed
shouldMove = true;
if (isResumed()){
moveToNext = false;
//Move to Next Page
getActivity().getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fragment_container, new NextFragment())
.addToBackStack(null)
.commit();
}
}
Entonces, si se reanuda el Fragment
(por lo tanto, la Activity
), puede commit
su acción, pero si no, esperará a que la Activity
comience a commit
su acción:
@Override
public void onResume() {
super.onResume();
if(moveToNext){
moveToNext = false;
//Move to Next Page
getActivity().getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fragment_container, new NextFragment())
.addToBackStack(null)
.commit();
}
}
PS: Preste atención a moveToNext = false
; Es para asegurarse de que después de la commit
no se repetirá la commit
en caso de que vuelva a presionar la tecla Volver.
commit Programa una confirmación de esta transacción. El compromiso no sucede de inmediato; se programará como trabajo en el subproceso principal que se realizará la próxima vez que el subproceso esté listo.
Una transacción solo se puede comprometer con este método antes de que contenga la actividad guardando su estado. Si se intenta la confirmación después de ese punto, se lanzará una excepción. Esto se debe a que el estado después de la confirmación se puede perder si la actividad necesita ser restaurada de su estado.
commitAllowingStateLoss
Como commit () pero permite que el commit se ejecute después de que se guarde el estado de una actividad. Esto es peligroso porque la confirmación puede perderse si la actividad necesita ser restaurada posteriormente de su estado, por lo que solo debe usarse en los casos en que está bien que el estado de la interfaz de usuario cambie inesperadamente en el usuario.
Y de acuerdo con su pregunta, está utilizando AsyncTask para la operación en segundo plano de la red. Por lo tanto, este podría ser un problema por el que está cometiendo un fragmento dentro de su método de devolución de llamada. Para evitar esta excepción, simplemente no cometa dentro de los métodos de devolución de llamadas asíncritas. Esto no es una solución sino una precaución.