Cómo manejar los mensajes del Manejador cuando la actividad / fragmento está en pausa

Ligera variación en mi otra publicación

Básicamente tengo un Handler mensajes en mi Fragment que recibe un montón de mensajes que pueden dar como resultado que los diálogos se descarten o se muestren.

Cuando la aplicación se pone en segundo plano, recibo un onPause pero sigo recibiendo mis mensajes como es de esperar. Sin embargo, debido a que estoy usando fragmentos, no puedo simplemente descartar y mostrar diálogos ya que eso dará como resultado una IllegalStateException .

No puedo simplemente descartar o cancelar permitiendo la pérdida del estado.

Dado que tengo un Handler , me pregunto si existe un enfoque recomendado sobre cómo debo manejar los mensajes mientras estoy en pausa.

Una posible solución que estoy considerando es registrar los mensajes que llegan mientras están en pausa y reproducirlos en un onResume . Esto es algo insatisfactorio y estoy pensando que debe haber algo en el marco para manejar esto de manera más elegante.

Aunque el sistema operativo Android no parece tener un mecanismo que resuelva su problema de manera suficiente, creo que este patrón proporciona una solución relativamente simple de implementar.

La siguiente clase es un contenedor de android.os.Handler que almacena los mensajes cuando una actividad está en pausa y los reproduce en el currículum.

Asegúrese de que cualquier código que tenga que asincrónicamente cambie un estado de fragmento (por ejemplo, commit, dismiss) solo se invoque desde un mensaje en el controlador.

Derive su controlador de la clase PauseHandler .

Siempre que su actividad reciba una llamada PauseHandler.pause() y para onResume() llaman a PauseHandler.resume() .

Reemplace su implementación del handleMessage() con processMessage() .

Proporcione una implementación simple de storeMessage() que siempre devuelve true .

 /** * Message Handler class that supports buffering up of messages when the * activity is paused ie in the background. */ public abstract class PauseHandler extends Handler { /** * Message Queue Buffer */ final Vector messageQueueBuffer = new Vector(); /** * 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 botón, se envía un mensaje retrasado al controlador.

Cuando el controlador recibe el mensaje (en el hilo de la interfaz de usuario), muestra un DialogFragment .

Si la clase PausedHandler no se estaba utilizando, se mostraría una IllegalStateException si se presionó el botón de inicio después de presionar el botón de prueba para iniciar el 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 ie in the background. */ static class ConcreteTestHandler extends PauseHandler { /** * 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; } } } } } 

storeMessage() un método storeMessage() a la clase PausedHandler en caso de que los mensajes se procesen inmediatamente, incluso cuando la actividad está en pausa. Si se maneja un mensaje, se debe devolver falso y el mensaje se descartará.

Una versión un poco más simple del excelente PauseHandler de QuickDraw es

 /** * Message Handler class that supports buffering up of messages when the activity is paused ie in the background. */ public abstract class PauseHandler extends Handler { /** * Message Queue Buffer */ private final List messageQueueBuffer = Collections.synchronizedList(new ArrayList()); /** * Flag indicating the pause state */ private Activity activity; /** * Resume the handler. */ public final synchronized void resume(Activity activity) { this.activity = activity; while (messageQueueBuffer.size() > 0) { final Message msg = messageQueueBuffer.get(0); messageQueueBuffer.remove(0); sendMessage(msg); } } /** * Pause the handler. */ public final synchronized void pause() { activity = null; } /** * Store the message if we have been paused, otherwise handle it now. * * @param msg Message to handle. */ @Override public final synchronized void handleMessage(Message msg) { if (activity == null) { final Message msgCopy = new Message(); msgCopy.copyFrom(msg); messageQueueBuffer.add(msgCopy); } else { processMessage(activity, msg); } } /** * 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 activity Activity owning this Handler that isn't currently paused. * @param message Message to be handled */ protected abstract void processMessage(Activity activity, Message message); } 

Asume que siempre quiere almacenar mensajes fuera de línea para la reproducción. Y proporciona la Actividad como entrada para #processMessages por lo que no necesita administrarla en la subclase.

Aquí hay una manera ligeramente diferente de abordar el problema de hacer commits de Fragmentos en una función de callback y evitar el problema de IllegalStateException.

Primero crea una interfaz ejecutable personalizada.

 public interface MyRunnable { void run(AppCompatActivity context); } 

A continuación, cree un fragmento para procesar los objetos MyRunnable. Si el objeto MyRunnable se creó después de pausar la actividad, por ejemplo, si se rota la pantalla, o si el usuario presiona el botón de inicio, se coloca en una cola para su posterior procesamiento con un nuevo contexto. La cola sobrevive a cualquier cambio de configuración porque la instancia setRetain está establecida en verdadero. El método runProtected se ejecuta en el subproceso UI para evitar una condición de carrera con el indicador isPaused.

 public class PauseHandlerFragment extends Fragment { private AppCompatActivity context; private boolean isPaused = true; private Vector buffer = new Vector<>(); @Override public void onAttach(Context context) { super.onAttach(context); this.context = (AppCompatActivity)context; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setRetainInstance(true); } @Override public void onPause() { isPaused = true; super.onPause(); } @Override public void onResume() { isPaused = false; playback(); super.onResume(); } private void playback() { while (buffer.size() > 0) { final MyRunnable runnable = buffer.elementAt(0); buffer.removeElementAt(0); new Handler(Looper.getMainLooper()).post(new Runnable() { @Override public void run() { //execute run block, providing new context, incase //Android re-creates the parent activity runnable.run(context); } }); } } public final void runProtected(final MyRunnable runnable) { context.runOnUiThread(new Runnable() { @Override public void run() { if(isPaused) { buffer.add(runnable); } else { runnable.run(context); } } }); } } 

Finalmente, el fragmento se puede usar en una aplicación principal de la siguiente manera:

 public class SomeActivity extends AppCompatActivity implements SomeListener { PauseHandlerFragment mPauseHandlerFragment; static class Storyboard { public static String PAUSE_HANDLER_FRAGMENT_TAG = "phft"; } protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... //register pause handler FragmentManager fm = getSupportFragmentManager(); mPauseHandlerFragment = (PauseHandlerFragment) fm. findFragmentByTag(Storyboard.PAUSE_HANDLER_FRAGMENT_TAG); if(mPauseHandlerFragment == null) { mPauseHandlerFragment = new PauseHandlerFragment(); fm.beginTransaction() .add(mPauseHandlerFragment, Storyboard.PAUSE_HANDLER_FRAGMENT_TAG) .commit(); } } // part of SomeListener interface public void OnCallback(final String data) { mPauseHandlerFragment.runProtected(new MyRunnable() { @Override public void run(AppCompatActivity context) { //this block of code should be protected from IllegalStateException FragmentManager fm = context.getSupportFragmentManager(); ... } }); } } 

En mis proyectos utilizo el patrón de diseño del observador para resolver esto. En Android, los receptores e intenciones de transmisión son una implementación de este patrón.

Lo que hago es crear un BroadcastReceiver que registre en fragment’s / activity’sResume y anote el registro en fragment’s / activity’s onPause . En el método de ReceiverReceiver onReceive pongo todo el código que necesita para ejecutarse como resultado de – el BroadcastReceiver – recibiendo un Intento (mensaje) que se envió a su aplicación en general. Para boost la selectividad sobre qué tipo de intenciones puede recibir su fragmento, puede usar un filtro de intención como en el ejemplo siguiente.

Una ventaja de este enfoque es que el Intento (mensaje) se puede enviar desde cualquier lugar dentro de su aplicación (un diálogo que se abre sobre su fragmento, una tarea asíncrona, otro fragmento, etc.). Los parámetros pueden pasar como extras de intención.

Otra ventaja es que este enfoque es compatible con cualquier versión de la API de Android, ya que BroadcastReceivers y Intents se han introducido en el nivel 1 de la API.

No es necesario que configure ningún permiso especial en el archivo de manifiesto de su aplicación, excepto si planea usar sendStickyBroadcast (donde debe agregar BROADCAST_STICKY).

 public class MyFragment extends Fragment { public static final String INTENT_FILTER = "gr.tasos.myfragment.refresh"; private BroadcastReceiver mReceiver = new BroadcastReceiver() { // this always runs in UI Thread @Override public void onReceive(Context context, Intent intent) { // your UI related code here // you can receiver data login with the intent as below boolean parameter = intent.getExtras().getBoolean("parameter"); } }; public void onResume() { super.onResume(); getActivity().registerReceiver(mReceiver, new IntentFilter(INTENT_FILTER)); }; @Override public void onPause() { getActivity().unregisterReceiver(mReceiver); super.onPause(); } // send a broadcast that will be "caught" once the receiver is up protected void notifyFragment() { Intent intent = new Intent(SelectCategoryFragment.INTENT_FILTER); // you can send data to receiver as intent extras intent.putExtra("parameter", true); getActivity().sendBroadcast(intent); } }