Quello che stai ponendo è una domanda piuttosto difficile. Mentre potresti pensare che sia solo una domanda, in realtà stai ponendo più domande contemporaneamente. Farò del mio meglio con la consapevolezza che devo coprirlo e, si spera, alcuni altri si uniranno per coprire ciò che potrei perdere.
Classi nidificate: introduzione
Dato che non sono sicuro di quanto tu sia a tuo agio con OOP in Java, questo colpirà un paio di basi. Una classe nidificata è quando una definizione di classe è contenuta in un'altra classe. Esistono sostanzialmente due tipi: Classi nidificate statiche e Classi interne. La vera differenza tra questi sono:
- Classi nidificate statiche:
- Sono considerati "di alto livello".
- Non richiede la creazione di un'istanza della classe contenente.
- Non può fare riferimento ai membri della classe contenente senza un riferimento esplicito.
- Hanno la loro vita.
- Classi annidate interne:
- Richiede sempre la creazione di un'istanza della classe contenitore.
- Avere automaticamente un riferimento implicito all'istanza contenente.
- Può accedere ai membri della classe del contenitore senza il riferimento.
- La vita non dovrebbe essere più lunga di quella del contenitore.
Garbage Collection e classi interne
Garbage Collection è automatico ma cerca di rimuovere gli oggetti in base al fatto che pensi che vengano utilizzati. Il Garbage Collector è piuttosto intelligente, ma non perfetto. Può solo determinare se qualcosa viene utilizzato se esiste o meno un riferimento attivo all'oggetto.
Il vero problema qui è quando una classe interiore è stata mantenuta in vita più a lungo del suo contenitore. Ciò è dovuto al riferimento implicito alla classe contenente. L'unico modo in cui ciò può accadere è se un oggetto esterno alla classe contenente mantiene un riferimento all'oggetto interno, indipendentemente dall'oggetto contenitore.
Ciò può portare a una situazione in cui l'oggetto interno è vivo (tramite riferimento) ma i riferimenti all'oggetto contenente sono già stati rimossi da tutti gli altri oggetti. L'oggetto interno, quindi, mantiene vivo l'oggetto contenitore perché avrà sempre un riferimento ad esso. Il problema è che, a meno che non sia programmato, non c'è modo di tornare all'oggetto contenitore per verificare se è ancora vivo.
L'aspetto più importante di questa realizzazione è che non fa alcuna differenza se si trova in un'attività o è un disegno. Dovrai sempre essere metodico quando usi le classi interne e assicurarti che non sopravvivano mai agli oggetti del contenitore. Fortunatamente, se non è un oggetto principale del codice, le perdite potrebbero essere piccole in confronto. Sfortunatamente, queste sono alcune delle perdite più difficili da trovare, perché è probabile che passino inosservate fino a quando molte di esse non sono trapelate.
Soluzioni: classi interne
- Ottieni riferimenti temporanei dall'oggetto contenitore.
- Consentire all'oggetto di contenimento di essere l'unico a mantenere riferimenti di lunga durata agli oggetti interni.
- Usa modelli consolidati come Factory.
- Se la classe interna non richiede l'accesso ai membri della classe contenente, prendere in considerazione la possibilità di trasformarla in una classe statica.
- Usare con cautela, indipendentemente dal fatto che si trovi in un'attività o meno.
Attività e viste: Introduzione
Le attività contengono molte informazioni per poter essere eseguite e visualizzate. Le attività sono definite dalla caratteristica che devono avere una vista. Hanno anche alcuni gestori automatici. Indipendentemente dal fatto che tu lo specifichi o meno, l'attività ha un riferimento implicito alla vista che contiene.
Per poter creare una vista, deve sapere dove crearla e se ha figli in modo che possa essere visualizzata. Ciò significa che ogni vista ha un riferimento all'attività (via getContext()
). Inoltre, ogni vista mantiene riferimenti ai propri figli (es getChildAt()
.). Infine, ogni vista mantiene un riferimento alla bitmap renderizzata che rappresenta la sua visualizzazione.
Ogni volta che hai un riferimento a un'attività (o contesto di attività), ciò significa che puoi seguire TUTTA la catena nella gerarchia del layout. Questo è il motivo per cui le perdite di memoria relative alle attività o alle visualizzazioni sono un grosso problema. Può essere una tonnellata di memoria che fuoriesce tutto in una volta.
Attività, viste e classi interne
Date le informazioni di cui sopra sulle classi interne, queste sono le perdite di memoria più comuni, ma anche le più comunemente evitate. Mentre è auspicabile che una classe interna abbia accesso diretto ai membri di una classe Attività, molti sono disposti a renderli statici per evitare potenziali problemi. Il problema con Attività e Viste è molto più profondo di così.
Attività trapelate, visualizzazioni e contesti di attività
Tutto dipende dal contesto e dal ciclo di vita. Ci sono alcuni eventi (come l'orientamento) che uccideranno un contesto di attività. Poiché così tante classi e metodi richiedono un contesto, gli sviluppatori a volte cercheranno di salvare un po 'di codice afferrando un riferimento a un contesto e trattenendolo. Accade semplicemente che molti degli oggetti che dobbiamo creare per eseguire la nostra attività debbano esistere al di fuori del ciclo di vita delle attività, al fine di consentire all'attività di fare ciò che deve fare. Se uno qualsiasi dei tuoi oggetti ha un riferimento a un'attività, al suo contesto o a una qualsiasi delle sue viste quando viene distrutta, hai appena fatto trapelare quell'attività e l'intero albero della vista.
Soluzioni: attività e viste
- Evitare, a tutti i costi, di fare un riferimento statico a una vista o attività.
- Tutti i riferimenti ai contesti di attività devono essere di breve durata (la durata della funzione)
- Se è necessario un contesto di lunga durata, utilizzare il contesto dell'applicazione (
getBaseContext()
o getApplicationContext()
). Questi non mantengono i riferimenti implicitamente.
- In alternativa, puoi limitare la distruzione di un'attività sostituendo le modifiche alla configurazione. Tuttavia, ciò non impedisce ad altri potenziali eventi di distruggere l'attività. Mentre puoi farlo, potresti comunque voler fare riferimento alle pratiche di cui sopra.
Runnables: Introduzione
I runner non sono poi così male. Voglio dire, potrebbero esserlo, ma in realtà abbiamo già colpito la maggior parte delle zone di pericolo. Un Runnable è un'operazione asincrona che esegue un'attività indipendentemente dal thread sul quale è stata creata. La maggior parte dei runnable sono istanziati dal thread dell'interfaccia utente. In sostanza, l'utilizzo di Runnable sta creando un altro thread, leggermente più gestito. Se classifichi un Runnable come una classe standard e segui le linee guida sopra, dovresti riscontrare alcuni problemi. La realtà è che molti sviluppatori non lo fanno.
Per facilità, leggibilità e flusso logico del programma, molti sviluppatori utilizzano le classi interne anonime per definire i loro Runnable, come nell'esempio sopra creato. Ciò si traduce in un esempio come quello che hai digitato sopra. Una classe interna anonima è sostanzialmente una classe interna discreta. Non devi creare una definizione completamente nuova e semplicemente sostituire i metodi appropriati. Sotto tutti gli altri aspetti è una classe interna, il che significa che mantiene un riferimento implicito al suo contenitore.
Runnables e attività / viste
Sìì! Questa sezione può essere breve! A causa del fatto che i Runnable funzionano al di fuori del thread corrente, il pericolo con questi è rappresentato dalle operazioni asincrone a esecuzione prolungata. Se il runnable è definito in un'attività o vista come una classe interna anonima o una classe interna nidificata, ci sono alcuni pericoli molto gravi. Questo perché, come precedentemente affermato, deve sapere chi è il suo contenitore. Immettere la modifica dell'orientamento (o kill del sistema). Ora fai riferimento alle sezioni precedenti per capire cosa è appena successo. Sì, il tuo esempio è abbastanza pericoloso.
Soluzioni: Runnables
- Prova ad estendere Runnable, se non rompe la logica del tuo codice.
- Fai del tuo meglio per rendere statici Runnables estesi, se devono essere classi nidificate.
- Se è necessario utilizzare Runnable anonimi, evitare di crearli in qualsiasi oggetto che abbia un riferimento di lunga durata a un'attività o vista in uso.
- Molti Runnable avrebbero potuto essere altrettanto facilmente AsyncTasks. Prendi in considerazione l'utilizzo di AsyncTask poiché quelli sono gestiti da VM per impostazione predefinita.
Rispondere alla domanda finale
ora per rispondere alle domande che non sono state affrontate direttamente dalle altre sezioni di questo post. Hai chiesto "Quando un oggetto di una classe interna può sopravvivere più a lungo della sua classe esterna?" Prima di arrivare a questo, lasciatemi riproporre: anche se hai ragione a preoccuparti di ciò in Attività, può causare una perdita ovunque. Fornirò un semplice esempio (senza usare un'attività) solo per dimostrare.
Di seguito è riportato un esempio comune di una fabbrica di base (manca il codice).
public class LeakFactory
{//Just so that we have some data to leak
int myID = 0;
// Necessary because our Leak class is an Inner class
public Leak createLeak()
{
return new Leak();
}
// Mass Manufactured Leak class
public class Leak
{//Again for a little data.
int size = 1;
}
}
Questo non è un esempio comune, ma abbastanza semplice da dimostrare. La chiave qui è il costruttore ...
public class SwissCheese
{//Can't have swiss cheese without some holes
public Leak[] myHoles;
public SwissCheese()
{//Gotta have a Factory to make my holes
LeakFactory _holeDriller = new LeakFactory()
// Now, let's get the holes and store them.
myHoles = new Leak[1000];
for (int i = 0; i++; i<1000)
{//Store them in the class member
myHoles[i] = _holeDriller.createLeak();
}
// Yay! We're done!
// Buh-bye LeakFactory. I don't need you anymore...
}
}
Ora abbiamo perdite, ma nessuna fabbrica. Anche se abbiamo rilasciato la Factory, rimarrà nella memoria perché ogni singola perdita ha un riferimento ad essa. Non importa nemmeno che la classe esterna non abbia dati. Questo accade molto più spesso di quanto si possa pensare. Non abbiamo bisogno del creatore, solo delle sue creazioni. Quindi ne creiamo uno temporaneamente, ma utilizziamo le creazioni indefinitamente.
Immagina cosa succede quando cambiamo leggermente il costruttore.
public class SwissCheese
{//Can't have swiss cheese without some holes
public Leak[] myHoles;
public SwissCheese()
{//Now, let's get the holes and store them.
myHoles = new Leak[1000];
for (int i = 0; i++; i<1000)
{//WOW! I don't even have to create a Factory...
// This is SOOOO much prettier....
myHoles[i] = new LeakFactory().createLeak();
}
}
}
Ora, ognuna di quelle nuove LeakFactories è appena trapelata. Cosa ne pensi? Questi sono due esempi molto comuni di come una classe interna può sopravvivere a una classe esterna di qualsiasi tipo. Se quella classe esterna fosse stata un'attività, immagina quanto sarebbe peggiorato.
Conclusione
Questi elencano i pericoli principalmente noti dell'utilizzo inappropriato di questi oggetti. In generale, questo post avrebbe dovuto coprire la maggior parte delle tue domande, ma capisco che era un post molto lungo, quindi se hai bisogno di chiarimenti, fammelo sapere. Finché seguirai le pratiche di cui sopra, avrai pochissima preoccupazione di perdite.