2012-05-22 10 views
6

Dado que no se recomienda mantener una referencia fuerte a Context en una tarea (el contexto puede destruirse mientras la tarea todavía se está ejecutando, pero la tarea lo mantiene en la memoria), me preguntaba si esto se aplica a los Fragmentos.¿Es seguro mantener una referencia fuerte a un Fragmento en una AsyncTask?

Los fragmentos gestionan su referencia de actividad, y la compatibilidad se mantiene a través de setRetainInstance. ¿Puedo suponer que, por ejemplo, Crear un AsyncTask interno no estático en un Fragmento es seguro en términos de no arriesgarse a fugas $this?

Respuesta

6

En general, es una mala metodología para mantener las referencias entre los hilos, y un AsyncTask es algo así como un hilo.

Está bien, siempre y cuando se asegure de quitarle la referencia cuando haya terminado de usarlo.

De lo contrario, podría tener pérdidas de memoria.

En este caso, está bien porque su Fragment está en el contexto de AsyncTask. Cuando la tarea está hecha, perderá esa referencia.

Si esto se hiciera en un Service, sería una muy mala idea.

+2

El riesgo es en este caso si conservas la referencia a la asynctask cuando el Fragmento está muerto. El usuario podría decir hacer una acción que muestre el fragmento de la pila. En este caso, normalmente no querrás que el estado del Fragmento exista más. Si querías el estado de la asynctask porque contenía datos adicionales que querías usar, entonces mantendría la referencia al Fragmento aunque el Fragmento ya no exista. Creo que se separará de la actividad ya que pasó por el ciclo de vida normal, pero aún "existiría". – DeeV

+0

@DeeV: Sí, eso es exactamente lo que me preocupaba – Matthias

+0

¿Podría simplemente salirse con la asignación a una WeakReference y cancelar AsyncTask si el Fragmento se vuelve nulo? – DeeV

2

La respuesta de Phoenixblade9 es correcta, pero para completarla, agregaría una cosa.

Hay un gran reemplazo para AsyncTask - AsyncTaskLoader, o los cargadores en general. Gestiona su ciclo de vida de acuerdo con el contexto desde el que se lo ha llamado (Actividad, Fragmento) e implementa un grupo de oyentes para ayudarlo a separar la lógica de un segundo hilo del hilo de la interfaz de usuario. Y generalmente es inmune al contexto de filtración.

Y no moleste el nombre; también es bueno para guardar datos.


Según lo prometido, publicaré mi código para AsyncTaskLoader withg varios objetos devueltos. El cargador es algo como esto:

public class ItemsLoader extends AsyncTaskLoader<HashMap<String, Object>>{ 

HashMap<String, Object> returned; 
ArrayList<SomeItem> items; 
Context cxt; 

public EventsLoader(Context context) { 
    super(context); 
    //here you can initialize your vars and get your context if you need it inside 
} 

@Override 
public HashMap<String, Object> loadInBackground() { 


    returned = getYourData(); 

    return returned; 

} 

@Override 
public void deliverResult(HashMap<String, Object> returned) { 
    if (isReset()) { 
     return; 
    } 

    this.returned = returned; 

    super.deliverResult(returned); 
} 

@Override 
protected void onStartLoading() { 
    if (returned != null) { 
     deliverResult(returned); 
    } 

    if (takeContentChanged() || returned == null) { 
     forceLoad(); 
    } 
} 

@Override 
protected void onStopLoading() { 
    cancelLoad(); 
} 

@Override 
protected void onReset() { 
    super.onReset(); 

    onStopLoading(); 

    returned = null; 
} 

En la función getYourData() consigo tanto el código de mensaje de servidor o algún otro código de error y una ArrayList<SomeItem>. Puedo usarlos en mi Fragmento de esta manera:

public class ItemListFragment extends ListFragment implements LoaderCallbacks<HashMap<String, Object>>{ 

private LoaderManager lm; 

@Override 
public void onActivityCreated(Bundle savedInstanceState) { 
    super.onActivityCreated(savedInstanceState); 

    lm = getLoaderManager(); 

    Bundle args = new Bundle(); 
args.putInt("someId", someId); 
lm.initLoader(0, args, this); 
} 


@Override 
public Loader<HashMap<String, Object>> onCreateLoader(int arg0, Bundle args) { 
    ItemsLoader loader = new ItemsLoader(getActivity(), args.getInt("someId")); 
    return loader; 
} 

@Override 
public void onLoadFinished(Loader<HashMap<String, Object>> loader, HashMap<String, Object> data) { 

    if(data!=null){ if(data.containsKey("items")){ 
     ArrayList<SomeItem> items = (ArrayList<EventItem>)data.get("items"); 

    } else { //error 
     int error = 0; 
     if(data.containsKey("error")){ 
      error = (Integer) data.get("error"); 
     } 
      } 

} 

@Override 
public void onLoaderReset(Loader<HashMap<String, Object>> arg0) { 

} 
+0

Sí, buen punto; Todavía no he usado el 'Loader', pero una mirada rápida sugiere que adolece del mismo defecto que AsyncTask (http://stackoverflow.com/questions/3357477/is-asynctask-really-conceptually-flawed- or-am-i-just-missing-something), es decir, que no proporciona una referencia administrada a la Actividad/Fragmento en las devoluciones de llamada. – Matthias

+0

¿Pero por qué necesitarías una referencia a Activity from inside of Loader? Esa es la belleza de esta agradable separación. El trabajo en el hilo de la interfaz de usuario solo se produce en el código de actividad (a través de la interfaz LoaderCallbacks), por lo que la carga (almacenamiento, etc.) ocurre en el segundo hilo y actualiza el hilo de ui - on ui. El único defecto que se me ocurre es el problema de actualizar Ui DURANTE la carga, pero probablemente no sea tan difícil hacerlo con una extensión de LoaderCallbacks. –

+0

¿Qué pasa si cargas dos cosas diferentes? No puede implementar la misma interfaz dos veces utilizando diferentes variables de tipo, por lo que las devoluciones de llamadas deben implementarse en un delegado, que a su vez debe administrar las referencias de contexto. Aunque supongo que uno podría crear un compuesto de cargador que ofrezca una salida compuesta que combine dos resultados diferentes. – Matthias

Cuestiones relacionadas