2012-05-25 11 views
12

? Hay numerosas publicaciones sobre cómo manejar un cambio de configuración durante una AsyncTask, pero ninguna que encontré dio una solución clara con respecto a las aplicaciones que están en segundo plano (onPause()) cuando una AsyncTask finaliza e intenta descartar un DialogFragment (biblioteca de compatibilidad).¿Cómo se puede manejar descartar un DialogFragment (compatibility lib) al completar una AsyncTask

Este es el problema, si tengo una ejecución AsyncTask que debería descartar un DialogFragment en onPostExecute(), obtengo una IllegalStateException si la aplicación está en segundo plano cuando intenta descartar el DialogFragment.

private static class SomeTask extends AsyncTask<Void, Void, Boolean> { 

    public SomeTask(SomeActivity tActivity) 
    { 
     mActivity = tActivity; 
    } 

    private SomeActivity mActivity; 

    /** Set the view during/after config change */ 
    public void setView(Activity tActivity) { 
     mActivity tActivity; 
    } 

    @Override 
    protected Boolean doInBackground(Void... tParams) { 
     try { 
      //simulate some time consuming process 
      TimeUnit.SECONDS.sleep(3); 
     } catch (InterruptedException ignore) {} 
     return true; 
    } 

    @Override 
    protected void onPostExecute(Boolean tRouteFound) { 
     mActivity.dismissSomeDialog(); 
    } 

} 

La actividad tiene el siguiente aspecto:

import android.support.v4.app.FragmentActivity; 
import android.support.v4.app.FragmentManager; 

public class SomeActivity extends FragmentActivity { 

    public void someMethod() { 
     ... 
     displaySomeDialog(); 
     new SomeTask(this).execute(); 
     ... 
    } 

    public void displaySomeDialog() { 
     DialogFragment someDialog = new SomeDialogFragment(); 
     someDialog.show(getFragmentManager(), "dialog"); 
    } 

    public void dismissSomeDialog() { 
     SomeDialogFragment someDialog = (SomeDialogFragment) getFragmentManager().findFragmentByTag("dialog"); 
     someDialog.dismiss(); 
    } 

    .... 

} 

funciona bien a menos que el cambios de aplicación a fondo mientras SomeTask todavía se está ejecutando. En ese caso, cuando SomeTask intenta descartar SomeDialog(), obtengo una IllegalStateException.

05-25 16:36:02.237: E/AndroidRuntime(965): java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState 

Todas las publicaciones que he visto parecen apuntar en alguna dirección complicada con soluciones elaboradas. ¿No hay alguna forma de manejar esto en Android? Si fuera un cuadro de diálogo en lugar de un cuadro de diálogo, entonces el archivo de descarte de la actividad() lo manejaría correctamente. Si se tratara de un DialogFragment real en lugar de uno de la ACP, entonces descartaría a AllowingStateLoss() lo manejaría. ¿No hay algo como esto para la versión ACP de DialogFragment?

+1

Eche un vistazo a mi [** publicación de blog **] (http://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html) sobre este tema ... tal vez ayudará. –

Respuesta

15

Para evitar el problema de la excepción de estado ilegal y esencialmente implementar un dismissAllowingStateLoss() se puede hacer usando lo siguiente.

getFragmentManager().beginTransaction().remove(someDialog).commitAllowingStateLoss(); 

Esto debería resolver el problema sin el código hacky. Lo mismo se puede aplicar para mostrar si tiene hilos que se comunican a través de un manejador con el hilo UI usando dialog.show(); Lo que puede provocar una excepción estatal ilegal, así

getFragmentManager().beginTransaction().add(someDialog).commitAllowingStateLoss(); 


@joneswah es correcta, teniendo en cuenta la cuestión carteles. Si está utilizando la biblioteca de soporte, vuelva a colocar

getFragmentManager() 

con

getSupportFragmentManager() 


Para los futuros empleados de Google: @Alex Lockwood plantea preocupaciones buenas y válidas con esta solución. La solución resuelve el error y funcionará en la mayoría de los casos, pero insinúa que hay problemas con el enfoque en la pregunta original, desde una perspectiva UX.

La actividad debería suponer que la tarea asincrónica puede no completarse y que no funcionará en PostExecute(). Cualquier acción de UI (es decir, spinner, idealmente no un diálogo) se inicia para notificar al usuario de la operación asincrónica, debe tener disposiciones para detenerse automáticamente en un tiempo de espera o por estado de seguimiento y comprobar eventos de ciclo de vida de tipo Restore/onResume para garantizar la La interfaz de usuario se actualiza correctamente. Los servicios también pueden valer la pena investigar.

+0

Solución mucho mejor. Gracias por mencionarlo. –

+0

Me alegro de que haya ayudado. Me enfrenté al mismo problema recientemente y puse una cantidad ridícula de esfuerzo en rastrear una solución. El único otro que encontré fue básicamente cambiar la fuente de la biblioteca de compatibilidad, o extender la clase e implementar el método usted mismo. –

+1

Esto debería usar getSupportFragmentManager() en lugar de getFragmentManager() – joneswah

0

Cuando Android detiene su aplicación porque el usuario pulsa la tecla de la parte posterior o el botón de inicio, sus cuadros de diálogo están cerrados para usted. Por lo general, el truco es preservar los diálogos entre onStop()/onStart(). Entonces, a menos que necesites hacer algo más que cerrar el diálogo, yo diría que no te preocupes.

EDITAR: en su actividad que aloja el cuadro de diálogo, es posible que desee cerrar el cuadro de diálogo si todavía está abierto dentro deStop(). Esto ayuda a evitar fugas de memoria. Pero esto no necesita ser activado desde AsyncTask.

Como dije antes, el problema es qué sucede cuando presiona onStart() nuevamente y su AsyncTask NO está terminada todavía. Tendrá que encontrar la manera de determinarlo y volver a abrir ese diálogo si es necesario.

+0

Tengo un enfoque de trabajo para onStop() y onStart(). Donde viene el problema es con onPause(), es decir. mi actividad ya no es visible En el caso de una pausa, el diálogo permanece abierto y la AsyncTask continúa ejecutándose. Una vez que la tarea finaliza, intenta cerrar un ProgressDialog utilizando Activity.dismissDialog(), que funciona bien incluso mientras está en pausa. A continuación, intenta descartar un DialogFragment (versión de ACP), llamando a DialogFragment.dismiss(). Esto da como resultado una InvalidStateException. –

+0

Me temo que puede necesitar replantear su diseño. La forma más sencilla de hacer lo que usted quiere con el trabajo básico puede ser simplemente cerrar el diálogo en onPause(). También podría intentar cancelar AsyncTask en OnPause, pero eso puede depender de lo que esté haciendo AsyncTask. la clase AsyncTask tiene un método de cancelar, pero eso en realidad no mata al hilo generado, simplemente establece un indicador y tu código dentro de AsyncTask debe tratarlo de alguna manera. Algunas tareas de IO, como el tráfico de red, pueden ser particularmente difíciles de cancelar o abortar. En cuyo caso, intentar cancelar AsyncTask podría ser un problema. – Glaucus

+0

También le sugiero que considere un servicio. El servicio podría luego transmitir los resultados a su actividad.Con este modelo, no se intentarán acceder al diálogo fuera de su actividad. Para ser sincero, a veces me gustaría que nunca hayan hecho AsyncTask por razones como esta, en realidad hace que sea más difícil lidiar con los casos extremos. – Glaucus

3

Debe cancelar su AsyncTask en onPause() si onPostExecute() va a actualizar la interfaz de usuario. No debe intentar actualizar la interfaz de usuario mientras su actividad ha sido detenida.

Por ejemplo. en su onPause():

if (task != null) task.cancel(true); 

Si desea que los cambios de la tarea de persistir a la próxima vez, a continuación, almacenar los datos/cambios en la doInBackground() y luego actualizar la interfaz de usuario cuando su actividad// fragmento de diálogo se reanudó

Si no desea que los cambios con respecto a la tarea de persistir, entonces no guardar los cambios hasta onPostExecute()

+0

Siento que el sistema operativo Android me está convirtiendo en spaghetti donde la actividad debe realizar un seguimiento si la tarea aún se está ejecutando cuando su estado cambia y la tarea debe ser consciente de si la actividad está visible para decidir qué viene después. No está bien. Realmente me gustaría evitar esto. Seguramente hay una manera más directa de abordar esto. Y seguramente no soy el primer desarrollador en encontrar el problema. Alguien debe tener un lenguaje limpio para manejar el escenario de una tarea que se ejecuta cuando la actividad pasa a segundo plano. –

+0

Para profundizar en el comentario anterior, claramente el marco puede manejar Activity.dismissDialog() correctamente incluso cuando está en segundo plano. DialogFragment.dismissAllowingStateLoss() funciona correctamente incluso en segundo plano. Sin embargo, en ACP no hay DialogFragment.dismissAllowingStateLoss(). Aún así, la gente de Android debe haber encontrado esta situación antes y debe tener una forma sugerida de manejarlo. –

+1

Solo extiendo DialogFragment y make dismissAllowStateLoss(), o anula el descarte que es lo que hago. Utilice getFragmentTransaction(), luego elimine (this), luego commitAllowStateLoss(). Funciona bien. –

0

Después de numerosos rediseños que finalmente se estableció en un enfoque que parece funcionar. Sin embargo, no he podido encontrar ciertos aspectos de mi diseño documentados en otras partes de las discusiones sobre el desarrollo de Android. Entonces me interesaría cualquier comentario.

En resumen, lo que tenía que hacer es esto:

  • onSaveInstanceState() - si SomeTask se está ejecutando, uso un simple bloqueo para bloquear SomeTask salga doInBackground() durante la pausa.
  • onResume() - Tuve que poner una declaración de cambio para manejar diferentes situaciones de reanudación. Si ejecuto la aplicación, no hago nada ya que nada está en pausa, si reinicio después de estar oculto o después de cambiar la configuración, lo desbloqueo para que la instancia conservada SomeTask pueda reanudarse donde lo dejó, etc.
  • onDestroy() - CancelO SomeTask si está funcionando.

Pondré los fragmentos de código para esta solución en mi publicación original.

+0

El bloqueo de los métodos del ciclo de vida de la actividad para que no se ejecuten utilizando bloqueos realmente no es una gran solución. –

+0

De acuerdo. Lo eliminé de la publicación. La respuesta correcta indicada es la forma correcta de hacerlo. –

22

Los fragmentos se guardan como parte del estado de cada actividad, por lo que realizar transacciones después de llamar onSaveInstanceState() técnicamente no tiene sentido.

Definitivamente no desea utilizar commitAllowingStateLoss() para evitar la excepción en este caso. Considere este escenario como un ejemplo:

  1. La actividad ejecuta un AsyncTask. El AsyncTask muestra un DialogFragment en onPreExecute() y comienza a ejecutar su tarea en una cadena de fondo.
  2. El usuario hace clic en "Inicio" y el Activity se detiene y se lo fuerza a un segundo plano. El sistema decide que el dispositivo tiene poca memoria, por lo que decide que también debe destruir el Activity.
  3. Se completa el AsyncTask y se llama onPostExecute(). Dentro de onPostExecute() se cierra el DialogFragment usando commitAllowingStateLoss() para evitar la excepción.
  4. El usuario navega de regreso al Activity. El FragmentManager restaurará el estado de sus fragmentos según el estado guardado de Activity. El estado guardado no sabe nada después de haber llamado al onSaveInstanceState(), por lo que la solicitud para cerrar el DialogFragment no se recordará y el DialogFragment se restaurará aunque el AsyncTask ya se haya completado.

Debido a errores extraños como estos que ocasionalmente pueden ocurrir, generalmente no es una buena idea usar commitAllowingStateLoss() para evitar esta excepción. Dado que los métodos de devolución de llamada AsyncTask (llamados en respuesta a un hilo de fondo que termina su trabajo) no tienen absolutamente nada que ver con los métodos de ciclo de vida Activity (que son invocados por el proceso del servidor del sistema en respuesta a eventos externos de todo el sistema, como el dispositivo se queda dormido o la memoria baja), manejar estas situaciones requiere que haga un poco de trabajo extra. Por supuesto, estos errores son extremadamente raros, y la protección de su aplicación contra ellos a menudo no será la diferencia entre una clasificación de 1 estrella y una de 5 estrellas en Play Store ... pero todavía es algo de lo que debe tenerse en cuenta.

Esperemos que tenga al menos algo de sentido. Además, tenga en cuenta que Dialog s también existen como parte del estado Activity s, por lo que aunque utilizar un simple Dialog podría evitar la excepción, esencialmente tendría el mismo problema (es decir, descartar el Dialog no se recordaría cuando el Activity el estado es luego restaurado).

Para ser sincero, la mejor solución sería evitar mostrar un cuadro de diálogo durante la duración del AsyncTask. Una solución mucho más fácil de usar sería mostrar un spinner de progreso indeterminado en el ActionBar (como las aplicaciones G + y Gmail, por ejemplo). Causar grandes cambios en la interfaz de usuario en respuesta a las devoluciones de llamada asíncronas es malo para la experiencia del usuario porque es inesperado y saca al usuario bruscamente de lo que está haciendo.

Consulte this blog post sobre el tema para obtener más información.

+0

Alex, ¿podría aclarar cómo se implementaría una solución de este tipo para rastrear el estado de la tarea Async mientras se está en segundo plano para evitar "commitAllowingStateLoss()"? Definitivamente tiene razón sobre el impacto de la IU y el potencial de una experiencia de IU discontinua. –

+0

@Alex, Realmente he estado leyendo muchas de tus cosas desde hace un tiempo. Tengo casi el mismo problema pero aún no he podido implementar ninguna de sus soluciones. Por favor, ayúdame a echar un vistazo a [esto] (http://stackoverflow.com/questions/22465289/cannot-perform-this-task-after-onsavedinstancestate-while-dismissing-dialog-frag), Gracias. – KingBryan

+0

Enfrentando el mismo problema al presionar el botón de inicio después de la rotación, ¿alguna idea para implementar de forma independiente, sin seguir el estado de la actividad? – karate

0

getActivity().finish(); en el DialogFragment funcionó para mí.

Cuestiones relacionadas