web-dev-qa-db-fra.com

Quand peut-on utiliser des classes internes (anonymes) en toute sécurité?

Je lisais quelques articles sur les fuites de mémoire dans Android et regardais cette vidéo intéressante de Google I/O sur le sujet .

Cependant, je ne comprends pas tout à fait le concept, et en particulier lorsqu'il est sûr ou dangereux pour l'utilisateur des classes internes à l'intérieur d'une activité .

C'est ce que j'ai compris:

Une fuite de mémoire se produira si une instance d'une classe interne survit plus longtemps que sa classe externe (une activité). -> Dans quelles situations cela peut-il arriver?

Dans cet exemple, je suppose qu'il n'y a pas de risque de fuite, car il est impossible que la classe anonyme qui étend OnClickListener vive plus longtemps que l'activité, n'est-ce pas?

    final Dialog dialog = new Dialog(this);
    dialog.setContentView(R.layout.dialog_generic);
    Button okButton = (Button) dialog.findViewById(R.id.dialog_button_ok);
    TextView titleTv = (TextView) dialog.findViewById(R.id.dialog_generic_title);

    // *** Handle button click
    okButton.setOnClickListener(new OnClickListener() {
        public void onClick(View v) {
            dialog.dismiss();
        }
    });

    titleTv.setText("dialog title");
    dialog.show();

Maintenant, cet exemple est-il dangereux et pourquoi?

// We are still inside an Activity
_handlerToDelayDroidMove = new Handler();
_handlerToDelayDroidMove.postDelayed(_droidPlayRunnable, 10000);

private Runnable _droidPlayRunnable = new Runnable() { 
    public void run() {
        _someFieldOfTheActivity.performLongCalculation();
    }
};

J'ai un doute sur le fait que comprendre ce sujet implique de comprendre en détail ce qui est conservé lorsqu'une activité est détruite et recréée.

Est ce

Disons que je viens de changer l'orientation de l'appareil (qui est la cause la plus fréquente de fuites). Lorsque super.onCreate(savedInstanceState) sera appelé dans mon onCreate(), cela restaurera-t-il les valeurs des champs (comme ils l'étaient avant le changement d'orientation)? Cela restaurera-t-il également les états des classes internes?

Je me rends compte que ma question n’est pas très précise, mais j’apprécierais vraiment toute explication qui permettrait de clarifier les choses.

306
Sébastien

Ce que vous demandez est une question assez difficile. Vous pensez peut-être qu'il ne s'agit que d'une seule question, mais vous posez plusieurs questions à la fois. Je ferai de mon mieux en sachant que je dois le couvrir et, espérons-le, d'autres se joindront à nous pour couvrir ce qui pourrait me manquer.

Classes imbriquées: Introduction

Comme je ne sais pas si vous êtes à l'aise avec OOP en Java, quelques notions de base vous seront utiles. Une classe imbriquée est quand 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 contenante.
    • Peut ne pas référencer les membres de la classe contenant sans une référence explicite.
    • Ont leur propre vie.
  • Classes imbriquées intérieures:
    • Nécessite toujours la construction d’une instance de la classe contenante.
    • Avoir automatiquement une référence implicite à l'instance contenant.
    • Peut accéder aux membres de la classe du conteneur sans la référence.
    • La durée de vie est supposée ne pas être plus longue que celle du conteneur.

Collecte des ordures ménagères et classes intérieures

La récupération de place est automatique, mais tente de supprimer les objets en fonction de leur utilisation ou non. Le ramasse-miettes est très intelligent, mais pas impeccable. Il peut uniquement déterminer si quelque chose est utilisé en fonction de l'existence ou non d'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 contenante. Cela ne peut se produire que si un objet situé en dehors de la classe contenante conserve une référence à l'objet interne, sans tenir compte de l'objet contenant.

Cela peut conduire à une situation dans laquelle l'objet interne est actif (via une référence) alors que les références à l'objet contenant ont déjà été supprimées de tous les autres objets. L'objet interne garde donc l'objet contenant en vie, car il aura toujours une référence . Le problème, c’est qu’à moins qu’il ne soit programmé, il n’ya aucun moyen de revenir à l’objet contenant pour vérifier s’il est encore en vie.

L’aspect le plus important de cette prise de conscience est le fait qu’il s’agisse d’une activité ou d’un dessin. Vous aurez toujours devez ê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 principal de votre code, les fuites peuvent être petites en comparaison. Malheureusement, ces fuites sont parmi les plus difficiles à trouver, car elles risquent de passer inaperçues jusqu'à ce que beaucoup d'entre elles aient fui.

Solutions: Inner Classes

  • Obtenez des références temporaires à partir de l'objet contenant.
  • Autorisez l'objet contenant à ê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 qui la contient, envisagez de la transformer en classe statique.
  • À utiliser avec prudence, qu’il s’agisse d’une activité ou non.

Activités et vues: Introduction

Les activités contiennent beaucoup d'informations à exécuter et à afficher. 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é comporte 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 pouvoir l'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'est-à-dire getChildAt()). 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. C’est pourquoi les fuites de mémoire concernant les activités ou les vues représentent un si gros problème. Il peut s'agir d'une tonne de fuite de mémoire en une seule fois.

activités, vues et classes intérieures

Compte tenu des informations ci-dessus sur les classes internes, il s'agit des fuites de mémoire les plus courantes, mais aussi les plus couramment évitées. Bien qu'il soit souhaitable qu'une classe interne ait un accès direct aux membres d'une classe Activités, beaucoup sont disposés à les rendre statiques pour éviter les problèmes potentiels. Le problème avec Activités et Vues va beaucoup plus loin que cela.

activités, vues et contextes d'activité ayant fui

Tout se résume au contexte et au cycle de vie. Certains événements (tels que l'orientation) tueront un contexte d'activité. Comme de nombreuses classes et méthodes nécessitent un contexte, les développeurs tentent parfois de sauvegarder du code en saisissant une référence à un contexte et en la conservant. Il se trouve que la plupart des objets que nous devons créer pour exécuter notre activité doivent exister en dehors de Activity LifeCycle afin de permettre à l’activité de faire ce qu’elle doit faire. S'il arrive que l'un de vos objets ait une référence à une activité, à son contexte ou à l'une de ses vues lors de sa destruction, 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 les références implicitement.
  • 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 souhaiterez peut-être quand même vous référer aux pratiques ci-dessus.

Runnables: Introduction

Les runnables ne sont pas si mauvaises. Je veux dire, ils pourraient être, mais en réalité, nous avons déjà atteint la plupart des zones de danger. Un 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 runnables sont instanciées à partir du thread d'interface utilisateur. En substance, utiliser un fichier Runnable crée un autre thread, légèrement plus géré. Si vous classez une classe comme une classe standard et suivez les instructions ci-dessus, vous ne rencontrerez que peu de problèmes. La réalité est que beaucoup de développeurs ne le font pas.

Par souci de facilité, de lisibilité et de déroulement logique du programme, de nombreux développeurs utilisent des classes internes anonymes pour définir leurs Runnables, comme dans l'exemple ci-dessus. Cela donne un exemple semblable à celui que vous avez saisi ci-dessus. Une classe interne anonyme est fondamentalement une classe interne discrète. Vous n'avez simplement pas besoin de créer une nouvelle définition et de 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 and Activities/Views

Yay! Cette section peut être courte! En raison du fait que Runnables s'exécute en dehors du thread actuel, le danger de ces opérations est le maintien d'opérations asynchrones de longue durée. Si le fichier exécutable est défini dans une activité ou une vue en tant que classe interne anonyme OR classe interne imbriquée, il existe de très graves dangers. En effet, comme indiqué précédemment, il a pour savoir qui est son conteneur. Entrez le changement d'orientation (ou la mort 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, si cela 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 any ayant une référence de longue durée à une activité ou à une vue. qui est en cours d'utilisation.
  • Beaucoup de Runnables auraient tout aussi bien pu être des AsyncTasks. Pensez à utiliser AsyncTask car ils sont VM gérés par défaut.

Répondant à la question finale Maintenant, répondez aux questions qui n'étaient pas directement traitées par les autres sections de ce message. Vous avez demandé "Quand un objet d'une classe interne peut-il survivre plus longtemps que sa classe externe?" Avant d’en venir à cela, laissez-moi souligner de nouveau: bien que vous ayez raison de vous en préoccuper dans Activités, cela peut provoquer une fuite n'importe où. Je vais donner un exemple simple (sans utiliser d'activité) juste pour démontrer.

Vous trouverez ci-dessous un exemple courant d’usine de base (code manquant).

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

Cet exemple n’est pas aussi courant, mais il est 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 l’Usine, celle-ci restera en mémoire car chaque fuite est référencée. Peu importe que la classe externe n'ait pas de données. Cela se produit beaucoup plus souvent qu'on pourrait le penser. Nous n'avons pas besoin du créateur, seulement de ses créations. Nous en créons donc un temporairement, mais nous utilisons les créations indéfiniment.

Imaginez ce qui se passe lorsque nous modifions 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 de fuir. 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 tout type. Si cette classe extérieure avait été une activité, imaginez à quel point cela aurait été pire.

Conclusion

Celles-ci énumèrent les principaux dangers connus liés à une utilisation inappropriée de ces objets. En général, cet article aurait dû couvrir la plupart de vos questions, mais si j'ai bien compris, c'était un article très long. Si vous avez besoin d'éclaircissements, n'hésitez pas à me le faire savoir. Tant que vous suivez les pratiques ci-dessus, vous ne craindrez pas les fuites.

619
Fuzzical Logic