Cómo manejar AsyncTask onPostExecute cuando se pausan para evitar IllegalStateException
Aprecio las numerosas publicaciones sobre AsyncTask en un cambio de rotación. Tengo el siguiente problema al usar el lib de la compatibilidad y de intentar descartar un DialogFragment
en onPostExecute
.
Tengo un fragmento que se activa de un AsyncTask que muestra un progreso DialogFragment
, a continuación, en onPostExecute
descarta el diálogo y, a continuación, potencialmente lanza otro DialogFragment
.
- Vista de Android - onAttachedToWindow y onDetachedFromWindow - cuándo se llaman en el ciclo de vida de la actividad?
- DialogFragment - retención del oyente tras la rotación de la pantalla
- OnCreate () llamado mientras se detiene la actividad (pero no se destruye). Sólo después de la instalación
- ¿Cuál es el fragmento equivalente de Activity.isFinishing ()?
- DatePickerDialog onDateSet llamado cuando se gira
Si cuando el diálogo del progreso se está exhibiendo puse la aplicación en el fondo consigo el siguiente para mi fragmento:
1) onPause
2) onSaveInstanceState
3) onPostExecute
en el que intento descartar e invocar un diálogo.
Recibo una IllegalStateException
porque estoy tratando de comprometer efectivamente una transacción cuando la actividad ha guardado su estado y entiendo esto.
En una rotación he asumido (quizás incorrectamente) que no conseguiría un onPostExecute
hasta que la actividad se ha recreado. Sin embargo, al poner la aplicación en segundo plano asumí (definitivamente incorrectamente) que el onPostExectute
no sería llamado mientras el fragmento / actividad estaba pausado.
Mi pregunta es, ¿es mi solución simplemente para detectar en onPostExecute
que el fragmento / actividad está en pausa y simplemente realizar lo que tengo que hacer en onResume
en onResume
lugar? Me parece algo feo.
Gracias de antemano, Peter.
Editar 1
Necesidad de soporte 2.1 y superior
Editar 2
He considerado la posibilidad de mostrar el diálogo utilizando FragmentTransaction:add
y FragmentTransaction:commitAllowingStateLoss
sin embargo esto no es sin problemas.
- Una vez más en el ciclo de vida de la actividad: onStart se llama cuando no debe ser
- Cancelar notificación después de que la aplicación se cierre desde el fondo
- Realización de una parada de actividad que no se reanuda - Android
- Variables estáticas públicas y gestión del ciclo de vida de la actividad de Android
- Cómo funciona el servicio Foreground (proceso diferente / mismo proceso) cuando Android tiene poca memoria
- ¿Cuánto manejo del ciclo de vida de Android es demasiado?
- ¿Cómo conservar el oyente en el cuadro de diálogo personalizado abierto desde el fragmento?
- Algunos fragmentos se pierden en rotación
Si necesita sincronizar su tarea con el ciclo de vida de la actividad, creo que los cargadores son exactamente lo que necesita. Más específicamente, debe utilizar AsyncTaskLoader para realizar el trabajo. Así que ahora en lugar de ejecutar un AsyncTask, se inicia su cargador, a continuación, esperar a la respuesta en un oyente. Si la actividad está en pausa, no obtendrá una devolución de llamada, esta parte se administrará para usted.
Hay otra manera de manejar esta tarea: usando un fragmento que retiene su instancia . La idea general es crear un fragmento sin UI y llamar a setRetainInstance(true)
. Tiene una tarea que está siendo notificada sobre la actividad que está disponible o no. De lo contrario, el subproceso de la tarea se suspende hasta que una actividad esté disponible.
Otra forma de lograr lo que necesita es implementar la clase PauseHandler que documenté en esta publicación .
A continuación, en su método onPostExecute, envíe sendMessage () para enviar su mensaje al manejador.
Cuando su aplicación se reanude, la acción será manejada.
En lugar de usar BroadcastReceiver, prefiero usar bibliotecas de bus como guava, otto o eventbus. Su rendimiento es mucho mejor que la implementación del receptor de difusión.
Me surgió una solución para este problema sin ninguna solución importante: La idea básica de cómo mantener un progressdialog y un asynctask se describe en este blogentry (por supuesto que he utilizado el AsyncTaskComplex-Version). Todos los créditos van al autor de este blog, solo agregué una pequeña cosa:
Obviamente no estoy usando showDialog () más. En su lugar me quedo con DialogFragments.
El segundo tweak es el importent y también resuelve el problema con la IllegalStateException:
En lugar de solo decirle al asynctask en onRetainCustomNonConfigurationInstance () que ya no hay actividad, también lo hago en onPause (). Y en lugar de solo decirle al asynctask en onCreate () que hay una nueva actividad, también lo hago en onResume ().
Y allí usted va, su AsyncTask no intentará informar su actividad sobre su final que causa un IllegalStateException cuando la actividad no es visible.
Si desea ver más código en lugar de palabras, deje un comentario.
/ Edit: Sourcecode para mostrar mi solución, que creo que es bastante decente 🙂
public class MyActivity extends Activity { private MyTask mTask; @Override protected void onCreate(Bundle pSavedInstanceState) { super.onCreate(pSavedInstanceState); setContentView(R.layout.editaccount); Object retained = getLastCustomNonConfigurationInstance(); if ( retained instanceof NewContactFolderIdTask ) { mTask = (MyTask) retained; mTask.setActivity(this); } } @Override protected void onPause() { if(mTask != null) { mTask.setActivity(null); } super.onPause(); } @Override public Object onRetainCustomNonConfigurationInstance() { if(mTask != null) { mTask.setActivity(null); return mTask; } return null; } @Override protected void onResume() { if(mTask != null) { mTask.setActivity(this); } loadValues(); // or refreshListView or whatever you need to do super.onResume(); } public void onTaskCompleted() { loadValues(); // or refreshListView or whatever you need to do DialogFragment dialogFragment = (DialogFragment) getSupportFragmentManager().findFragmentByTag(PROGRESS_DIALOG_FRAGMENT); if(dialogFragment != null) { dialogFragment.dismiss(); } } @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater menuInflater = getMenuInflater(); menuInflater.inflate(R.menu.main, menu); return super.onCreateOptionsMenu(menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: // app icon in Action Bar clicked; go home Intent intent = new Intent(this, OXClient.class); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(intent); return true; case R.id.menu_refresh: mTask = new MyTask(this); mTask.execute(); break; } return super.onOptionsItemSelected(item); } private class NewContactFolderIdTask extends AsyncTask<Boolean, Integer, Bundle> { private MyActivity mActivity; private boolean mCompleted; private NewContactFolderIdTask(MyActivity pActivity) { this.mActivity = pActivity; } public void setActivity(MyActivity pActivity) { this.mActivity = pActivity; if(mCompleted) { notifiyActivityTaskCompleted(); } } private void notifiyActivityTaskCompleted() { if(mActivity != null) { mActivity.onTaskCompleted(); } } @Override protected Bundle doInBackground(Boolean... pBoolean) { // Do your stuff, return result } @Override protected void onPreExecute() { DialogFragment newFragment = ProgressDialogFragment.newInstance(); newFragment.show(getSupportFragmentManager(), PROGRESS_DIALOG_FRAGMENT); } @Override protected void onPostExecute(Bundle pResult) { mCompleted = true; notifiyActivityTaskCompleted(); } }
}
En Cómo manejar los mensajes del manejador cuando se interrumpe la actividad / fragmento , ofrezco otra aproximación usando BroadcastReceiver.
Lo considero más limpio y elegante y ofrece las ventajas de que puede invocar código en su fragmento base desde cualquier lugar de su aplicación y mediante emisiones pegajosas su invocación puede ser "recordada" y ejecutada después de que su fragmento se reanude.
- "BadParcelableException: ClassNotFoundException cuando unmarshalling <myclass>" mientras utiliza el método Parcel.read que tiene un ClassLoader como argumento
- ¿Cómo agregar un fragmento a un diseño generado mediante programa?