Ce que vous demandez est une question assez difficile. Bien que vous puissiez penser qu'il ne s'agit que d'une seule question, vous posez en fait plusieurs questions à la fois. Je ferai de mon mieux en sachant que je dois le couvrir et, espérons-le, d'autres se joindront à moi pour couvrir ce qui pourrait me manquer.
Classes imbriquées: introduction
Comme je ne sais pas à quel point vous êtes à l'aise avec la POO en Java, cela touchera quelques bases. Une classe imbriquée est lorsqu'une définition de classe est contenue dans une autre classe. Il existe essentiellement deux types: les classes imbriquées statiques et les classes internes. La vraie différence entre ceux-ci sont:
- Classes imbriquées statiques:
- Sont considérés comme "de haut niveau".
- Ne nécessite pas la construction d'une instance de la classe conteneur.
- Ne peut pas référencer les membres de classe contenant sans référence explicite.
- Ont leur propre vie.
- Classes imbriquées internes:
- Il faut toujours construire une instance de la classe conteneur.
- Avoir automatiquement une référence implicite à l'instance conteneur.
- Peut accéder aux membres de la classe du conteneur sans référence.
- La durée de vie ne devrait pas être plus longue que celle du conteneur.
Collecte des ordures et classes internes
Le garbage collection est automatique mais essaie de supprimer des objets selon qu'il pense qu'ils sont utilisés. Le garbage collector est assez intelligent, mais pas parfait. Il peut uniquement déterminer si quelque chose est utilisé, qu'il existe ou non une référence active à l'objet.
Le vrai problème ici est quand une classe interne a été maintenue en vie plus longtemps que son conteneur. Cela est dû à la référence implicite à la classe conteneur. Cela ne peut se produire que si un objet en dehors de la classe conteneur conserve une référence à l'objet interne, sans tenir compte de l'objet conteneur.
Cela peut conduire à une situation où l'objet interne est vivant (via une référence) mais les références à l'objet contenant ont déjà été supprimées de tous les autres objets. L'objet intérieur garde donc vivant l'objet contenant car il y aura toujours une référence. Le problème avec cela est qu'à moins qu'il ne soit programmé, il n'y a aucun moyen de revenir à l'objet contenant pour vérifier s'il est encore vivant.
L'aspect le plus important de cette prise de conscience est qu'il ne fait aucune différence que ce soit dans une activité ou dans un dessin. Vous devrez toujours être méthodique lorsque vous utilisez des classes internes et assurez-vous qu'elles ne survivent jamais aux objets du conteneur. Heureusement, si ce n'est pas un objet central de votre code, les fuites peuvent être faibles en comparaison. Malheureusement, ce sont quelques-unes des fuites les plus difficiles à trouver, car elles sont susceptibles de passer inaperçues jusqu'à ce que beaucoup d'entre elles aient fui.
Solutions: classes internes
- Obtenez des références temporaires à partir de l'objet conteneur.
- Permettez à l'objet conteneur d'être le seul à conserver des références de longue durée aux objets internes.
- Utilisez des modèles établis tels que l'usine.
- Si la classe interne n'a pas besoin d'accéder aux membres de la classe contenante, envisagez de la transformer en classe statique.
- À utiliser avec prudence, qu'il s'agisse d'une activité ou non.
Activités et opinions: Introduction
Les activités contiennent beaucoup d'informations pour pouvoir être exécutées et affichées. Les activités sont définies par la caractéristique qu'elles doivent avoir une vue. Ils ont également certains gestionnaires automatiques. Que vous le spécifiiez ou non, l'activité a une référence implicite à la vue qu'elle contient.
Pour qu'une vue soit créée, elle doit savoir où la créer et si elle a des enfants pour qu'elle puisse s'afficher. Cela signifie que chaque vue a une référence à l'activité (via getContext()
). De plus, chaque vue conserve des références à ses enfants (c. getChildAt()
-à-d.). Enfin, chaque vue conserve une référence au bitmap rendu qui représente son affichage.
Chaque fois que vous avez une référence à une activité (ou un contexte d'activité), cela signifie que vous pouvez suivre la chaîne ENTIÈRE dans la hiérarchie de la disposition. C'est pourquoi les fuites de mémoire concernant les activités ou les vues sont si importantes. Cela peut être une tonne de mémoire qui fuit en même temps.
Activités, vues et classes internes
Compte tenu des informations ci-dessus sur les classes internes, ce sont les fuites de mémoire les plus courantes, mais aussi les plus couramment évitées. Bien qu'il soit souhaitable d'avoir une classe interne ayant un accès direct aux membres d'une classe d'activités, beaucoup sont prêts à les rendre statiques pour éviter les problèmes potentiels. Le problème avec les activités et les vues va bien plus loin que cela.
Activités, vues et contextes d'activité qui ont fait l'objet d'une fuite
Tout se résume au contexte et au cycle de vie. Certains événements (tels que l'orientation) peuvent tuer un contexte d'activité. Étant donné que tant de classes et de méthodes nécessitent un contexte, les développeurs essaient parfois de sauvegarder du code en saisissant une référence à un contexte et en le conservant. Il se trouve que bon nombre des objets que nous devons créer pour exécuter notre activité doivent exister en dehors du cycle de vie de l'activité afin de permettre à l'activité de faire ce qu'elle doit faire. Si l'un de vos objets se trouve avoir une référence à une activité, son contexte ou l'une de ses vues lorsqu'il est détruit, vous venez de divulguer cette activité et toute son arborescence de vues.
Solutions: activités et vues
- Évitez à tout prix de faire une référence statique à une vue ou une activité.
- Toutes les références aux contextes d'activité doivent être de courte durée (la durée de la fonction)
- Si vous avez besoin d'un contexte de longue durée, utilisez le contexte d'application (
getBaseContext()
ou getApplicationContext()
). Ceux-ci ne conservent pas implicitement les références.
- Vous pouvez également limiter la destruction d'une activité en remplaçant les modifications de configuration. Cependant, cela n'empêche pas d'autres événements potentiels de détruire l'activité. Bien que vous puissiez le faire, vous pouvez toujours vous référer aux pratiques ci-dessus.
Runnables: Introduction
Les runnables ne sont en fait pas si mal. Je veux dire, ils pourraient l' être, mais vraiment nous avons déjà touché la plupart des zones de danger. Une Runnable est une opération asynchrone qui exécute une tâche indépendante du thread sur lequel elle a été créée. La plupart des exécutables sont instanciés à partir du thread d'interface utilisateur. En substance, l'utilisation d'un Runnable crée un autre thread, juste un peu plus géré. Si vous classe un Runnable comme une classe standard et suivez les instructions ci-dessus, vous devriez rencontrer quelques problèmes. La réalité est que de nombreux développeurs ne le font pas.
Par facilité, lisibilité et flux de programme logique, de nombreux développeurs utilisent des classes internes anonymes pour définir leurs Runnables, comme l'exemple que vous créez ci-dessus. Cela donne un exemple comme celui que vous avez tapé ci-dessus. Une classe intérieure anonyme est fondamentalement une classe intérieure discrète. Vous n'avez tout simplement pas à créer une toute nouvelle définition et simplement remplacer les méthodes appropriées. À tous les autres égards, il s'agit d'une classe interne, ce qui signifie qu'elle conserve une référence implicite à son conteneur.
Runnables et activités / vues
Yay! Cette section peut être courte! En raison du fait que les Runnables s'exécutent en dehors du thread actuel, le danger avec celles-ci vient des opérations asynchrones de longue durée. Si le fichier exécutable est défini dans une activité ou une vue comme une classe interne anonyme OU une classe interne imbriquée, il existe des dangers très graves. En effet, comme indiqué précédemment, il doit savoir qui est son conteneur. Entrez le changement d'orientation (ou la suppression du système). Reportez-vous maintenant aux sections précédentes pour comprendre ce qui vient de se passer. Oui, votre exemple est assez dangereux.
Solutions: Runnables
- Essayez d'étendre Runnable, s'il ne casse pas la logique de votre code.
- Faites de votre mieux pour rendre les Runnables étendus statiques, s'ils doivent être des classes imbriquées.
- Si vous devez utiliser des Runnables anonymes, évitez de les créer dans un objet qui a une référence longue durée à une activité ou une vue en cours d'utilisation.
- De nombreux Runnables auraient tout aussi bien pu être des AsyncTasks. Pensez à utiliser AsyncTask car ceux-ci sont gérés par VM par défaut.
Répondre à la dernière question
maintenant pour répondre aux questions qui n'ont pas été directement abordées par les autres sections de cet article. Vous avez demandé "Quand un objet d'une classe interne peut-il survivre plus longtemps que sa classe externe?" Avant d'en arriver là, permettez-moi de le souligner à nouveau: bien que vous ayez raison de vous en préoccuper dans Activités, cela peut provoquer une fuite n'importe où. Je vais fournir un exemple simple (sans utiliser d'activité) juste pour démontrer.
Voici un exemple courant d'une usine de base (sans le code).
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;
}
}
Ce n'est pas un exemple aussi courant, mais assez simple à démontrer. La clé ici est le constructeur ...
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...
}
}
Maintenant, nous avons des fuites, mais pas d'usine. Même si nous avons sorti Factory, il restera en mémoire car chaque fuite a une référence. Peu importe que la classe externe ne dispose pas de données. Cela se produit beaucoup plus souvent qu'on ne pourrait le penser. Nous n'avons pas besoin du créateur, juste de ses créations. Nous en créons donc un temporairement, mais utilisons les créations indéfiniment.
Imaginez ce qui se passe lorsque nous changeons légèrement le constructeur.
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();
}
}
}
Maintenant, chacun de ces nouveaux LeakFactories vient d'être divulgué. Que penses-tu de cela? Ce sont deux exemples très courants de la façon dont une classe interne peut survivre à une classe externe de n'importe quel type. Si cette classe externe avait été une activité, imaginez à quel point cela aurait été pire.
Conclusion
Ceux-ci répertorient les principaux dangers connus de l'utilisation inappropriée de ces objets. En général, ce message aurait dû couvrir la plupart de vos questions, mais je comprends qu'il s'agissait d'un long message, donc si vous avez besoin de clarifications, faites le moi savoir. Tant que vous suivrez les pratiques ci-dessus, vous aurez très peu de soucis de fuite.