programacion - manual android studio avanzado
Cómo manejar AsyncTask onPostExecute cuando está en pausa para evitar IllegalStateException (5)
En Cómo manejar los mensajes del Manejador cuando la actividad / fragmento está en pausa , ofrezco otro enfoque usando un Receptor de difusión.
Considero que es más limpio y elegante, y ofrece las ventajas de que puede invocar código en su fragmento base desde cualquier lugar dentro de su aplicación y mediante transmisiones fijas su invocación puede "recordarse" y ejecutarse después de que se reanude su fragmento.
Aprecio las numerosas publicaciones relacionadas con AsyncTask en un cambio de rotación. Tengo el siguiente problema cuando uso la lib de compatibilidad y trato de descartar un DialogFragment
en onPostExecute
.
Tengo un fragmento que se dispara de una AsyncTask que muestra un DialogFragment
progreso, luego en onPostExecute
descarta el diálogo y luego potencialmente arroja otro DialogFragment
.
Si cuando se muestra el cuadro de diálogo de progreso pongo la aplicación en segundo plano, obtengo lo siguiente para mi fragmento:
1) onPause
2) onSaveInstanceState
3) onPostExecute
en el que trato de descartar e invocar un diálogo.
Obtengo una IllegalStateException
porque intento comprometer efectivamente una transacción cuando la actividad ha guardado su estado y lo entiendo.
En una rotación, he asumido (quizás incorrectamente) que no obtendría un onPostExecute
hasta que la actividad haya sido recreada. Sin embargo, al poner la aplicación en segundo plano, asumí (definitivamente incorrectamente) que onPostExectute
no se llamaría mientras el fragmento / actividad estaba en pausa.
Mi pregunta es, ¿mi solución es simplemente detectar en onPostExecute
que el fragmento / actividad está en pausa y simplemente realizar lo que tengo que hacer en onResume
? Me parece algo feo.
Gracias de antemano, Peter.
Editar 1
Necesidad de soportar 2.1 o superior
Editar 2
He considerado mostrar el diálogo usando FragmentTransaction:add
y FragmentTransaction:commitAllowingStateLoss
sin embargo, esto no está exento de problemas.
En lugar de utilizar 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.
Otra forma de lograr lo que necesita es implementar la clase PauseHandler que documenté en esta publicación .
Luego, en su método onPostExecute, llame a sendMessage () para publicar su mensaje en el controlador.
Cuando su aplicación se reanude, se manejará la acción.
Se me ocurrió una solución para este problema sin ninguna solución importante: la idea básica de cómo mantener un diálogo de progreso y una asynctask se describe en este blogentry (por supuesto, utilicé AsyncTaskComplex-Version). Todos los créditos van al autor de este blogentry, solo agregué una pequeña cosa:
Obviamente ya no estoy usando showDialog (). En cambio, me quedo con DialogFragments.
El segundo ajuste es importante y también resuelve el problema con IllegalStateException:
En lugar de solo decirle a asynctask en onRetainCustomNonConfigurationInstance () que no hay más actividad, también lo hago en onPause (). Y en vez de solo decirle a asynctask en onCreate () que hay una nueva actividad, también lo hago en onResume ().
Y ahí lo tienes, tu AsyncTask no intentará informar tu actividad sobre su finalización, lo que ocasionará una IllegalStateException cuando la actividad no sea 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();
}
}
}
Si necesita sincronizar su tarea con el ciclo de vida de la actividad, creo que los Loaders son exactamente lo que necesita. Más específicamente, debe usar AsyncTaskLoader para hacer el trabajo. Entonces ahora, en lugar de ejecutar AsyncTask, inicia su cargador y luego espera la respuesta en un oyente. Si la actividad está en pausa, no recibirá una devolución de llamada, esta parte se administrará por usted.
Hay otra forma de manejar esta tarea: usar un fragmento que conserva su instancia . La idea general es crear un fragmento sin UI y llamar a setRetainInstance(true)
. Tiene una tarea que se notifica sobre la actividad disponible o no. De lo contrario, el hilo de la tarea se suspende hasta que una actividad esté disponible.