Singletons vs contexte d'application dans Android?

362

Rappelant ce post énumérant plusieurs problèmes d'utilisation de singletons et ayant vu plusieurs exemples d'applications Android utilisant le modèle singleton, je me demande si c'est une bonne idée d'utiliser Singletons au lieu d'instances uniques partagées via l'état d'application global (sous-classe android.os.Application et l'obtenir) via context.getApplication ()).

Quels avantages / inconvénients les deux mécanismes auraient-ils?

Pour être honnête, je m'attends à la même réponse dans ce modèle post singleton avec application Web, ce n'est pas une bonne idée! mais appliqué à Android. Ai-je raison? Qu'est-ce qui est différent dans DalvikVM autrement?

EDIT: Je voudrais avoir des avis sur plusieurs aspects impliqués:

  • Synchronisation
  • Réutilisabilité
  • Essai
mschonaker
la source

Réponses:

295

Je suis très en désaccord avec la réponse de Dianne Hackborn. Nous supprimons petit à petit tous les singletons de notre projet au profit d'objets légers à portée de tâche qui peuvent facilement être recréés lorsque vous en avez réellement besoin.

Les singletons sont un cauchemar pour les tests et, s'ils sont initialisés paresseusement, introduiront un "indéterminisme d'état" avec des effets secondaires subtils (qui peuvent soudainement apparaître lors du déplacement des appels getInstance()d'une portée à une autre). La visibilité a été mentionnée comme un autre problème, et puisque les singletons impliquent un accès "global" (= aléatoire) à l'état partagé, des bogues subtils peuvent survenir lorsqu'ils ne sont pas correctement synchronisés dans les applications simultanées.

Je le considère comme un anti-modèle, c'est un mauvais style orienté objet qui revient essentiellement à maintenir l'état global.

Pour revenir à votre question:

Bien que le contexte de l'application puisse être considéré comme un singleton lui-même, il est géré par le framework et a un cycle de vie , une portée et un chemin d'accès bien définis . Par conséquent, je crois que si vous avez besoin de gérer l'état global de l'application, cela devrait aller ici, nulle part ailleurs. Pour toute autre chose, repensez si vous avez vraiment besoin d'un objet singleton, ou s'il serait également possible de réécrire votre classe singleton pour instancier à la place de petits objets de courte durée qui effectuent la tâche à accomplir.

Matthias
la source
131
Si vous recommandez l'application, vous recommandez l'utilisation de singletons. Honnêtement, il n'y a aucun moyen de contourner cela. L'application est un singleton, avec une sémantique plus folle. Je n'entrerai pas dans des arguments religieux au sujet de singletons quelque chose que vous ne devriez jamais utiliser. Je préfère être pratique - il y a des endroits où ils sont un bon choix pour maintenir l'état par processus et peuvent simplifier les choses en le faisant, et vous pouvez tout aussi bien les utiliser dans la mauvaise situation et vous tirer une balle dans le pied.
hackbod
18
Certes, et j'ai mentionné que le «contexte d'application peut être considéré comme un singleton lui-même». La différence est qu'avec l'instance d'application, se tirer une balle dans le pied est beaucoup plus difficile, car son cycle de vie est géré par le framework. Les frameworks DI comme Guice, Hivemind ou Spring utilisent également des singletons, mais c'est un détail d'implémentation dont le développeur ne devrait pas se soucier. Je pense qu'il est généralement plus sûr de s'appuyer sur une sémantique de framework correctement implémentée plutôt que sur votre propre code. Oui, je l'avoue! :-)
Matthias
93
Honnêtement, cela ne vous empêche pas de vous tirer une balle dans le pied plus qu'un singleton. C'est un peu déroutant, mais il n'y a pas de cycle de vie de l'application. Il est créé lorsque votre application démarre (avant que l'un de ses composants ne soit instancié) et que son onCreate () soit appelé à ce moment-là, et ... c'est tout. Il reste là et vit pour toujours, jusqu'à ce que le processus soit tué. Tout comme un singleton. :)
hackbod
30
Oh, une chose qui peut prêter à confusion: Android est très conçu pour exécuter des applications dans les processus et gérer le cycle de vie de ces processus. Ainsi, sur Android, les singletons sont un moyen très naturel de tirer parti de cette gestion de processus - si vous souhaitez mettre en cache quelque chose dans votre processus jusqu'à ce que la plate-forme ait besoin de récupérer la mémoire du processus pour autre chose, mettre cet état dans un singleton fera cela.
hackbod
7
Très bien. Je peux seulement dire que je n'ai pas regardé en arrière depuis que nous avons abandonné les singletons autogérés. Nous optons maintenant pour une solution légère de style DI, où nous conservons un seul singleton d'usine (RootFactory), qui à son tour est géré par l'instance de l'application (c'est un délégué si vous voulez). Ce singleton gère les dépendances communes sur lesquelles s'appuient tous les composants de l'application, mais l'instanciation est gérée en un seul emplacement - la classe de l'application. Alors qu'avec cette approche, il reste un singleton, il est limité à la classe Application, donc aucun autre module de code ne connaît ce "détail".
Matthias
231

Je recommande vivement les singletons. Si vous avez un singleton qui a besoin d'un contexte, ayez:

MySingleton.getInstance(Context c) {
    //
    // ... needing to create ...
    sInstance = new MySingleton(c.getApplicationContext());
}

Je préfère les singletons à Application car cela aide à garder une application beaucoup plus organisée et modulaire - au lieu d'avoir un endroit où tout votre état global à travers l'application doit être maintenu, chaque pièce séparée peut prendre soin d'elle-même. Le fait que les singletons initialisent paresseusement (à la demande) au lieu de vous guider sur la voie de faire toute l'initialisation à l'avance dans Application.onCreate () est également bon.

Il n'y a rien de intrinsèquement mauvais à utiliser des singletons. Utilisez-les correctement, quand cela a du sens. Le cadre Android en a beaucoup, pour qu'il conserve des caches par processus des ressources chargées et d'autres choses de ce genre.

De plus, pour les applications simples, le multithreading ne devient pas un problème avec les singletons, car par conception, tous les rappels standard vers l'application sont distribués sur le thread principal du processus afin que le multi-threading ne se produise que si vous l'introduisez explicitement via les threads ou implicitement en publiant un fournisseur de contenu ou un service IBinder sur d'autres processus.

Réfléchissez simplement à ce que vous faites. :)

hackbod
la source
1
Si quelque temps plus tard, je veux écouter un événement externe, ou partager sur un IBinder (je suppose que ce ne serait pas une simple application), je devrais ajouter un double verrouillage, synchronisation, volatile, non? Merci pour votre réponse :)
mschonaker
2
Pas pour un événement externe - BroadcastReceiver.onReceive () est également appelé sur le thread principal.
hackbod
2
D'accord. Pourriez-vous m'indiquer du matériel de lecture (je préfère le code) où je peux voir le mécanisme principal de distribution des threads? Je pense que cela clarifiera plusieurs concepts à la fois pour moi. Merci d'avance.
mschonaker
2
Il s'agit du principal code de répartition côté application: android.git.kernel.org/?p=platform/frameworks/…
hackbod
8
Il n'y a rien de mal intrinsèquement à utiliser des singletons. Utilisez-les correctement, quand cela a du sens. .. bien sûr, bien dit. Le cadre Android en a beaucoup, pour qu'il conserve des caches par processus des ressources chargées et d'autres choses de ce genre. Exactement comme vous le dites. De vos amis dans le monde iOS, "tout est un singleton" dans iOS .. rien ne pourrait être plus naturel sur les appareils physiques qu'un concept singleton: le gps, l'horloge, les gyroscopes, etc. etc - conceptuellement comment pourriez-vous les concevoir comme singletons? Donc voilà.
Fattie
22

De: Développeur> référence - Application

Il n'est normalement pas nécessaire de sous-classer Application. Dans la plupart des situations, les singletons statiques peuvent fournir les mêmes fonctionnalités de manière plus modulaire. Si votre singleton a besoin d'un contexte global (par exemple pour enregistrer des récepteurs de diffusion), la fonction pour le récupérer peut recevoir un Context qui utilise en interne Context.getApplicationContext () lors de la première construction du singleton.

Somatik
la source
1
Et si vous écrivez une interface pour le singleton, laissant getInstance non statique, vous pouvez même faire en sorte que le constructeur par défaut de la classe utilisant singleton injecte le singleton de production via un constructeur non par défaut, qui est également le constructeur que vous utilisez pour créer le classe utilisant singleton dans ses tests unitaires.
android.weasel
11

L'application n'est pas la même que pour le Singleton, pour les raisons suivantes:

  1. La méthode de l'application (comme onCreate) est appelée dans le thread ui;
  2. la méthode de singleton peut être appelée dans n'importe quel thread;
  3. Dans la méthode "onCreate" d'Application, vous pouvez instancier Handler;
  4. Si le singleton est exécuté dans un thread sans interface utilisateur, vous ne pouvez pas instancier Handler;
  5. L'application a la capacité de gérer le cycle de vie des activités dans l'application. Elle a la méthode "registerActivityLifecycleCallbacks". Mais les singletons n'ont pas la capacité.
sunhang
la source
1
Remarque: vous pouvez instancier Handler sur n'importe quel thread. Extrait du doc: "Lorsque vous créez un nouveau gestionnaire, il est lié à la file d'attente des threads / messages du thread qui le crée"
Christ
1
@Christ Merci! Tout à l'heure, j'ai appris le "mécanisme du Looper". Si le gestionnaire d'instanciation sur le thread sans interface utilisateur sans le code "Looper.prepare ()", le système signalera l'erreur "java.lang.RuntimeException: Can 'crée pas de gestionnaire à l'intérieur du thread qui n'a pas appelé Looper.prepare () ".
sunhang
11

J'ai eu le même problème: Singleton ou créer une sous-classe android.os.Application?

J'ai d'abord essayé avec le Singleton mais mon application à un moment donné appelle le navigateur

Intent myIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.google.com"));

et le problème est que, si le combiné n'a pas assez de mémoire, la plupart de vos classes (même les singletons) sont nettoyées pour obtenir de la mémoire donc, lors du retour du navigateur vers mon application, il se bloquait à chaque fois.

Solution: placez les données nécessaires dans une sous-classe de la classe Application.

JoséMi
la source
1
Je suis souvent tombé sur des messages où les gens affirment que cela peut se produire. J'attache donc simplement des objets à l'application comme des singletons avec le chargement paresseux etc. juste pour être sûr que le cycle de vie est documenté et connu. Assurez-vous simplement de ne pas enregistrer des centaines d'images dans votre objet d'application, car je comprends qu'il ne sera pas effacé de la mémoire si votre application est en arrière-plan et que toutes les activités sont détruites pour libérer de la mémoire pour d'autres processus.
Janusz
Eh bien, le chargement paresseux de Singleton après le redémarrage de l'application n'est pas la bonne façon de laisser les objets être balayés par le GC. Les références sont faibles, non?
mschonaker
15
Vraiment? Dalvik décharge les classes et perd l'état du programme? Êtes-vous sûr que ce n'est pas que cela ramasse les ordures le genre d'objets liés à l'activité à cycle de vie limité que vous ne devriez pas mettre en singletons en premier lieu? Vous devez donner des exemples clairs pour une revendication aussi extraordinaire!
android.weasel
1
Sauf s'il y a eu des changements que je ne connais pas, Dalvik ne décharge pas les classes. Déjà. Le comportement qu'ils voient est que leur processus est tué en arrière-plan pour faire de la place au navigateur. Ils initialisaient probablement la variable dans leur activité "principale", qui n'était peut-être pas créée dans le nouveau processus en revenant du navigateur.
Groxx
5

Considérez les deux en même temps:

  • avoir des objets singleton en tant qu'instances statiques à l'intérieur des classes.
  • avoir une classe commune (Context) qui renvoie les instances singleton pour tous les objets singelton dans votre application, ce qui présente l'avantage que les noms de méthode dans Context auront un sens, par exemple: context.getLoggedinUser () au lieu de User.getInstance ().

De plus, je suggère que vous développiez votre Context pour inclure non seulement l'accès aux objets singleton mais certaines fonctionnalités qui doivent être accessibles globalement, comme par exemple: context.logOffUser (), context.readSavedData (), etc. Renommer probablement le Context en La façade aurait alors un sens.

adranale
la source
4

Ils sont en fait les mêmes. Il y a une différence que je peux voir. Avec la classe Application, vous pouvez initialiser vos variables dans Application.onCreate () et les détruire dans Application.onTerminate (). Avec singleton, vous devez vous baser sur la statique d'initialisation et de destruction de VM.

Fedor
la source
16
les documents pour onTerminate disent qu'il n'est appelé que par l'émulateur. Sur les appareils, cette méthode ne sera probablement pas appelée. developer.android.com/reference/android/app/…
danb
3

Mes 2 cents:

J'ai remarqué que certains champs singleton / statiques ont été réinitialisés lorsque mon activité a été détruite. J'ai remarqué cela sur certains appareils bas de gamme 2.3.

Mon cas était très simple: je viens d'avoir un dossier privé "init_done" et une méthode statique "init" que j'ai appelée depuis activity.onCreate (). Je remarque que la méthode init se réexécutait sur une certaine recréation de l'activité.

Bien que je ne puisse pas prouver mon affirmation, cela peut être lié à QUAND le singleton / classe a été créé / utilisé en premier. Lorsque l'activité est détruite / recyclée, il semble que toutes les classes auxquelles seule cette activité fait référence sont également recyclées.

J'ai déplacé mon instance de singleton vers une sous-classe d'application. Je les accède depuis l'instance d'application. et, depuis lors, n'a plus remarqué le problème.

J'espère que cela peut aider quelqu'un.

Christ
la source
3

De la bouche du cheval proverbial ...

Lors du développement de votre application, il peut s'avérer nécessaire de partager des données, du contexte ou des services à l'échelle mondiale dans votre application. Par exemple, si votre application contient des données de session, telles que l'utilisateur actuellement connecté, vous souhaiterez probablement exposer ces informations. Dans Android, le modèle de résolution de ce problème est que votre instance android.app.Application possède toutes les données globales, puis traite votre instance Application comme un singleton avec des accesseurs statiques pour les divers services et données.

Lorsque vous écrivez une application Android, vous êtes assuré de n'avoir qu'une seule instance de la classe android.app.Application, et il est donc sûr (et recommandé par l'équipe Google Android) de la traiter comme un singleton. Autrement dit, vous pouvez ajouter en toute sécurité une méthode statique getInstance () à votre implémentation d'application. Ainsi:

public class AndroidApplication extends Application {

    private static AndroidApplication sInstance;

    public static AndroidApplication getInstance(){
        return sInstance;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        sInstance = this;
    }
}
RMcGuigan
la source
2

Mon activité appelle finish () (ce qui ne le fait pas terminer immédiatement, mais le fera éventuellement) et appelle Google Street Viewer. Lorsque je le débogue sur Eclipse, ma connexion à l'application se brise lorsque Street Viewer est appelé, ce que je comprends comme la fermeture de l'application (entière), soi-disant pour libérer de la mémoire (car une seule activité terminée ne devrait pas provoquer ce comportement) . Néanmoins, je suis capable d'enregistrer l'état dans un bundle via onSaveInstanceState () et de le restaurer dans la méthode onCreate () de l'activité suivante dans la pile. Soit en utilisant un singleton statique ou une application de sous-classement, je fais face à la fermeture et à la perte de l'application (sauf si je la sauvegarde dans un bundle). Donc, d'après mon expérience, ils sont les mêmes en ce qui concerne la préservation de l'État. J'ai remarqué que la connexion est perdue dans Android 4.1.2 et 4.2.2 mais pas sur 4.0.7 ou 3.2.4,

Piovezan
la source
"J'ai remarqué que la connexion est perdue dans Android 4.1.2 et 4.2.2 mais pas sur 4.0.7 ou 3.2.4, ce qui, à mon avis, suggère que le mécanisme de récupération de mémoire a changé à un moment donné." ..... Je pense que vos appareils n'ont pas la même quantité de mémoire disponible, ni la même application installée. et donc votre conclusion peut-être incorrecte
Christ
@Christ: Oui, vous devez avoir raison. Il serait étrange que le mécanisme de récupération de mémoire change entre les versions. Probablement l'utilisation différente de la mémoire a provoqué les comportements distincts.
Piovezan