2010-01-23 21 views
33

He estado trabajando con AsyncTasks en Android y estoy lidiando con un problema.Comportamiento de contexto Android AsyncTask

Tome un ejemplo simple, una Actividad con una AsyncTask. La tarea en el fondo no hace nada espectacular, simplemente duerme durante 8 segundos.

Al final de AsyncTask en el método onPostExecute() solo estoy configurando el estado de visibilidad de un botón en View.VISIBLE, solo para verificar mis resultados.

Ahora, esto funciona de maravilla hasta que el usuario decida cambiar la orientación de su teléfono mientras está funcionando la AsyncTask (dentro de la ventana de espera de 8 segundos).

Entiendo el ciclo de vida de la actividad de Android y sé que la actividad se destruye y vuelve a crear.

Aquí es donde aparece el problema. AsyncTask se refiere a un botón y aparentemente contiene una referencia al contexto que inició la AsyncTask en primer lugar.

Supongo que este contexto anterior (ya que el usuario provocó un cambio de orientación) se convertirá en nulo y AsyncTask arrojará un NPE para la referencia al botón que intenta hacer visible.

En su lugar, no se lanza NPE, la AsyncTask cree que la referencia del botón no es nula, lo establece en visible. ¿El resultado? ¡No pasa nada en la pantalla!

Actualización: He abordado esto manteniendo un WeakReference en la actividad y el cambio cuando ocurre un cambio de configuración. Esto es engorroso.

Aquí está el código:

public class Main extends Activity { 

    private Button mButton = null; 
    private Button mTestButton = null; 

    @Override 
    public void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     setContentView(R.layout.main); 

     mButton = (Button) findViewById(R.id.btnStart); 
     mButton.setOnClickListener(new OnClickListener() { 
      @Override 
      public void onClick(View v) { 
       new taskDoSomething().execute(0l); 
      } 
     }); 
     mTestButton = (Button) findViewById(R.id.btnTest); 
    } 

    private class TaskDoSomething extends AsyncTask<Long, Integer, Integer> 
    { 
     @Override 
     protected Integer doInBackground(Long... params) { 
      Log.i("LOGGER", "Starting..."); 
      try { 
       Thread.sleep(8000); 
      } catch (InterruptedException e) { 
       e.printStackTrace(); 
      } 
      return 0; 
     } 

     @Override 
     protected void onPostExecute(Integer result) { 
      Log.i("LOGGER", "...Done"); 
      mTestButton.setVisibility(View.VISIBLE); 
     } 
    } 
} 

intente ejecutar y mientras que el AsyncTask está trabajando cambiar su orientación móviles.

+0

es un poco engañoso escribir "mantener una WeakReference a la actividad" y no hacerlo en el código. Peor aún, más adelante dice "y cambiar cuando ocurre un cambio de configuración", lo que significa realmente nada ... ¿Cambiar qué? – Ewoks

Respuesta

23

AsyncTask no está diseñado para ser reutilizado una vez que una actividad ha sido derribado y reiniciado. El objeto Handler interno se vuelve obsoleto, tal como lo indicó. En el ejemplo de Shelves de Romain Guy, él simplemente cancela cualquier AsyncTask actualmente en ejecución y luego reinicia los cambios de posorientación nuevos.

Es posible transferir su Rosca a la nueva Actividad, pero agrega una gran cantidad de plomería.No se acuerda ninguna generalmente en forma de hacerlo, pero se puede leer acerca de mi método aquí: http://foo.jasonhudgins.com/2010/03/simple-progressbar-tutorial.html

2

Este es el tipo de cosas que me llevan a evitar siempre que mi actividad se destruya/recree en el cambio de orientación.

Para ello añadir esto a su etiqueta <Activity> en el archivo de manifiesto:

android:configChanges="orientation|keyboardHidden" 

y anular onConfigurationChanged en su clase de actividad:

@Override 
public void onConfigurationChanged(final Configuration newConfig) 
{ 
    // Ignore orientation change to keep activity from restarting 
    super.onConfigurationChanged(newConfig); 
} 
+5

Su enfoque funciona, pero ¿tiene buenos argumentos en contra de este enfoque? ¿Por qué es por defecto que el SO destruye y recrea la actividad entonces? En otras palabras, ¿qué "pierdo" haciendo lo que propones? Por cierto, funcionó como se esperaba. Gracias por la respuesta. – dnkoutso

+0

Hasta donde yo sé, lo principal que perdería sería la posibilidad de tener diferentes diseños de actividades para el modo paisaje y retrato. Puede haber otras cosas que "perderías", pero no sé cuáles son. –

+1

Generalmente, Google recomienda no utilizar este enfoque a menos que sepa lo que está haciendo. – emmby

2

Para evitar esto se puede usar la respuesta Givin aquí: https://stackoverflow.com/a/2124731/327011

Pero si usted necesita para destruir la actividad (diferentes diseños para retrato y paisaje) puede hacer que AsyncTask sea una clase pública (Lea aquí por qué no debe ser privado Android: AsyncTask recommendations: private class or public class?) y luego cree un método setActivity para establecer la referencia a la actividad actual cada vez que se destruye/crea.

se puede ver un ejemplo aquí: Android AsyncTask in external class

3

Si sólo necesita un contexto y no va a usar para la materia de la interfaz de usuario puede simplemente pasar el Application Context a su AsyncTask.You a menudo necesitan el contexto de los recursos del sistema, por ejemplo.

No intente actualizar la UI desde una AsyncTask e intente evitar el manejo de los cambios de configuración, ya que puede complicarse. Para actualizar la interfaz de usuario, puede registrar un receptor de difusión y enviar una transmisión.

También debe tener AsyncTask como una clase pública separada de la actividad mencionada anteriormente, hace las pruebas mucho más fáciles. Lamentablemente, la programación de Android a menudo refuerza las malas prácticas y los ejemplos oficiales no ayudan.

Cuestiones relacionadas