2012-05-22 15 views
6

Poiché non è consigliabile mantenere un riferimento forte a Context in un'attività (il contesto potrebbe essere distrutto mentre l'attività è ancora in esecuzione, ma mantenuta in memoria dall'attività), mi chiedevo se lo stesso si applica a Frammenti?È sicuro mantenere un riferimento forte a un frammento in un AsyncTask?

I frammenti gestiscono il loro riferimento attività e il supporto viene mantenuto tramite setRetainInstance. Posso presumere che, ad es. creare un asyncTask interno non statico in un frammento è sicuro in termini di non rischiare di perdere $this?

risposta

6

È generalmente una cattiva metodologia mantenere i riferimenti tra i thread e un AsyncTask è qualcosa come una discussione.

Va bene, a patto che ci si assicuri di dereferenziarlo quando si è finito di usarlo.

In caso contrario, è possibile che si verifichino perdite di memoria.

In questo caso, va bene perché il Fragment si trova nel contesto di AsyncTask. Quando l'attività è terminata, perderà quel riferimento.

Se ciò fosse fatto in un Service, sarebbe una pessima idea.

+2

Il rischio è, in questo caso se si mantiene il riferimento alla AsyncTask quando il frammento è morto. L'utente potrebbe dire di fare un'azione che apre il frammento dalla pila. In questo caso, di solito non vorresti più che lo stato di Fragment esista. Se si desiderava lo stato di asynctask perché conteneva dati aggiuntivi che si desidera utilizzare, quindi manterrebbe il riferimento al frammento anche se il frammento non dovrebbe più esistere. Credo che sarà distaccato dall'attività da quando ha attraversato il normale ciclo di vita, ma sarebbe comunque "esistito". – DeeV

+0

@DeeV: Sì, è esattamente ciò di cui mi preoccupavo – Matthias

+0

Potresti semplicemente passare a assegnarlo a un WeakReference e annullare AsyncTask se il Frammento diventa nullo? – DeeV

2

La risposta di Phoenixblade9 è corretta, ma per renderlo completo aggiungerei una cosa.

C'è un ottimo sostituto per AsyncTask - AsyncTaskLoader, o Loaders in generale. Gestisce il suo ciclo di vita in base al contesto da cui è stato chiamato (Attività, Frammento) e implementa un gruppo di ascoltatori per aiutarti a separare la logica di un secondo thread dal thread dell'interfaccia utente. Ed è generalmente immune dal contesto che perde.

E non infastidire il nome: è un vantaggio anche per il salvataggio dei dati.


Come promesso pubblicherò il mio codice per AsyncTaskLoader con più oggetti restituiti. Il caricatore più o meno così:

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; 
} 

Nella funzione getYourData() ottengo sia il codice messaggio del server o qualche altro codice di errore e di un ArrayList<SomeItem>. Posso uso nel mio Frammento come questo:

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ì, buon punto; Non ho ancora imparato a usare 'Loader', ma un rapido sguardo suggerisce che soffre dello stesso difetto di AsyncTask (http://stackoverflow.com/questions/3357477/is-asynctask-really-conceptually-flawed- or-am-i-just-missing-something), ovvero non fornire un riferimento gestito all'attività/frammento nei callback. – Matthias

+0

Ma perché avresti bisogno di un riferimento all'Attività dall'interno del Caricatore? Questa è la sua bellezza - questa bella separazione. Il lavoro sul thread ui avviene solo nel codice di attività (tramite l'interfaccia LoaderCallbacks), quindi il caricamento (salvataggio, ecc.) Avviene sul secondo thread e l'aggiornamento del thread ui - on. L'unico difetto che posso pensare è il problema con l'aggiornamento durante il caricamento, ma probabilmente non è così difficile da fare con le estensioni di LoaderCallbacks. –

+0

Cosa succede se si caricano due cose diverse? Non è possibile implementare la stessa interfaccia due volte utilizzando variabili di tipo diverse, quindi i callback devono essere implementati su un delegato, che quindi deve gestire nuovamente i riferimenti al contesto. Anche se suppongo che si possa creare un caricatore composito che fornisce un'uscita composita che combina due risultati diversi. – Matthias

Problemi correlati