Singletons vs. Anwendungskontext in Android?


362

Ich erinnere mich an diesen Beitrag, in dem einige Probleme bei der Verwendung von Singletons aufgezählt wurden und einige Beispiele für Android-Anwendungen mit Singleton-Muster gesehen wurden. Ich frage mich, ob es eine gute Idee ist, Singletons anstelle einzelner Instanzen zu verwenden, die über den globalen Anwendungsstatus gemeinsam genutzt werden (Unterklasse android.os.Application und deren Erhalt) durch context.getApplication ()).

Welche Vor- und Nachteile hätten beide Mechanismen?

Um ehrlich zu sein, erwarte ich die gleiche Antwort in diesem Beitrag Singleton-Muster mit Webanwendung, keine gute Idee! aber auf Android angewendet. Hab ich recht? Was ist sonst in DalvikVM anders?

EDIT: Ich hätte gerne Meinungen zu verschiedenen Aspekten:

  • Synchronisation
  • Wiederverwendbarkeit
  • Testen

Antworten:


295

Ich bin mit Dianne Hackborns Antwort nicht einverstanden. Wir entfernen nach und nach alle Singletons aus unserem Projekt, um leichte, aufgabenbezogene Objekte zu erhalten, die einfach neu erstellt werden können, wenn Sie sie tatsächlich benötigen.

Singletons sind ein Albtraum für Tests und führen, wenn sie träge initialisiert werden, zu einem "Zustandsindeterminismus" mit subtilen Nebenwirkungen (die plötzlich auftreten können, wenn Anrufe getInstance()von einem Bereich in einen anderen verschoben werden). Die Sichtbarkeit wurde als ein weiteres Problem erwähnt, und da Singletons einen "globalen" (= zufälligen) Zugriff auf den gemeinsam genutzten Status implizieren , können subtile Fehler auftreten, wenn sie in gleichzeitigen Anwendungen nicht ordnungsgemäß synchronisiert werden.

Ich halte es für ein Anti-Muster, es ist ein schlechter objektorientierter Stil, der im Wesentlichen der Aufrechterhaltung des globalen Zustands gleichkommt.

Um auf Ihre Frage zurückzukommen:

Obwohl der App-Kontext selbst als Singleton betrachtet werden kann, wird er vom Framework verwaltet und verfügt über einen genau definierten Lebenszyklus , Umfang und Zugriffspfad. Daher glaube ich, dass wenn Sie den App-globalen Status verwalten müssen, er hier nirgendwo anders hingehen sollte. Überlegen Sie für alles andere, ob Sie wirklich ein Singleton-Objekt benötigen oder ob es auch möglich wäre, Ihre Singleton-Klasse neu zu schreiben, um stattdessen kleine, kurzlebige Objekte zu instanziieren, die die jeweilige Aufgabe ausführen.


131
Wenn Sie die Anwendung empfehlen, empfehlen Sie die Verwendung von Singletons. Ehrlich gesagt führt kein Weg daran vorbei. Die Anwendung ist ein Singleton mit einer schlechteren Semantik. Ich werde nicht auf religiöse Auseinandersetzungen über Singletons eingehen, die Sie niemals verwenden sollten. Ich bevorzuge es, praktisch zu sein - es gibt Orte, an denen sie eine gute Wahl für die Aufrechterhaltung des prozessbezogenen Zustands sind und die Dinge dadurch vereinfachen können, und Sie können sie genauso gut in der falschen Situation verwenden und sich in den Fuß schießen.
Hackbod

18
Richtig, und ich habe erwähnt, dass der "App-Kontext selbst als Singleton betrachtet werden kann". Der Unterschied besteht darin, dass es mit der App-Instanz viel schwieriger ist, sich in den Fuß zu schießen, da der Lebenszyklus vom Framework gesteuert wird. DI-Frameworks wie Guice, Hivemind oder Spring verwenden ebenfalls Singletons, aber das ist ein Implementierungsdetail, das dem Entwickler egal sein sollte. Ich denke, es ist im Allgemeinen sicherer, sich darauf zu verlassen, dass die Framework-Semantik korrekt implementiert wird, als auf Ihren eigenen Code. Ja, ich gebe zu! :-)
Matthias

93
Ehrlich gesagt hindert es Sie nicht daran, sich mehr in den Fuß zu schießen als ein Singleton. Es ist etwas verwirrend, aber es gibt keinen Lebenszyklus der Anwendung. Es wird erstellt, wenn Ihre App gestartet wird (bevor eine ihrer Komponenten instanziiert wird) und an diesem Punkt wird onCreate () aufgerufen, und ... das ist alles. Es sitzt dort und lebt für immer, bis der Prozess beendet ist. Genau wie ein Singleton. :)
Hackbod

30
Eine Sache, die dies verwirren kann: Android wurde hauptsächlich entwickelt, um Apps in Prozessen auszuführen und den Lebenszyklus dieser Prozesse zu verwalten. Unter Android sind Singletons eine sehr natürliche Möglichkeit, diese Prozessverwaltung zu nutzen. Wenn Sie etwas in Ihrem Prozess zwischenspeichern möchten, bis die Plattform den Arbeitsspeicher des Prozesses für etwas anderes zurückfordern muss, wird dies durch Einfügen dieses Status in einen Singleton erreicht.
Hackbod

7
Okay, fair genug. Ich kann nur sagen, dass ich nicht mehr zurückgeschaut habe, seit wir uns von selbstverwaltenden Singletons entfernt haben. Wir entscheiden uns jetzt für eine leichte DI-Lösung, bei der wir einen Factory-Singleton (RootFactory) behalten, der wiederum von der App-Instanz verwaltet wird (wenn Sie so wollen, ist es ein Delegat). Dieser Singleton verwaltet allgemeine Abhängigkeiten, auf die sich alle App-Komponenten stützen. Die Instanziierung wird jedoch an einem einzigen Ort verwaltet - der App-Klasse. Während bei diesem Ansatz ein Singleton verbleibt, ist er auf die Anwendungsklasse beschränkt, sodass kein anderes Codemodul über dieses "Detail" Bescheid weiß.
Matthias

231

Ich kann Singletons nur empfehlen. Wenn Sie einen Singleton haben, der einen Kontext benötigt, haben Sie:

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

Ich bevorzuge Singletons gegenüber Application, da dies dazu beiträgt, eine App viel organisierter und modularer zu gestalten. Anstatt einen Ort zu haben, an dem Ihr gesamter globaler Status in der App beibehalten werden muss, kann sich jedes einzelne Teil um sich selbst kümmern. Auch die Tatsache, dass Singletons (auf Anfrage) träge initialisiert werden, anstatt Sie auf den Weg zu führen, alle Initialisierungen in Application.onCreate () im Voraus durchzuführen, ist gut.

Es ist an sich nichts Falsches daran, Singletons zu verwenden. Verwenden Sie sie einfach richtig, wenn es Sinn macht. Das Android-Framework verfügt tatsächlich über viele davon, damit pro Prozess Caches mit geladenen Ressourcen und anderen derartigen Dingen verwaltet werden können.

Auch für einfache Anwendungen wird Multithreading bei Singletons nicht zum Problem, da standardmäßig alle Standardrückrufe an die App im Hauptthread des Prozesses gesendet werden, sodass kein Multithreading stattfindet, es sei denn, Sie führen es explizit über Threads oder ein implizit durch Veröffentlichung eines Inhaltsanbieters oder Dienstes IBinder für andere Prozesse.

Denken Sie nur darüber nach, was Sie tun. :) :)


1
Wenn ich einige Zeit später ein externes Ereignis anhören oder auf einem IBinder teilen möchte (ich denke, das wäre keine einfache App), müsste ich Double Locking, Synchronisation, Volatile hinzufügen, oder? Vielen Dank für Ihre Antwort :)
mschonaker

2
Nicht für ein externes Ereignis - BroadcastReceiver.onReceive () wird auch im Hauptthread aufgerufen.
Hackbod

2
Okay. Würden Sie mich auf Lesematerial verweisen (ich würde Code bevorzugen), in dem ich den Hauptmechanismus für das Versenden von Threads sehen kann? Ich denke, das wird für mich mehrere Konzepte gleichzeitig klären. Danke im Voraus.
Mschonaker

2
Dies ist der wichtigste App-seitige Versandcode: android.git.kernel.org/?p=platform/frameworks/…
Hackbod

8
Es ist an sich nichts Falsches daran, Singletons zu verwenden. Verwenden Sie sie einfach richtig, wenn es Sinn macht. .. sicher, genau, gut gesagt. Das Android-Framework verfügt tatsächlich über viele davon, damit pro Prozess Caches mit geladenen Ressourcen und anderen derartigen Dingen verwaltet werden können. Genau wie du sagst. Von Ihren Freunden in der iOS-Welt ist "alles ein Singleton" in iOS. Nichts könnte auf physischen Geräten natürlicher sein als ein Singleton-Konzept: das GPS, die Uhr, die Gyros usw. usw. - konzeptionell, wie würden Sie diese sonst konstruieren als als Singletons? Also ja.
Fattie

22

Von: Entwickler> Referenz - Anwendung

Normalerweise muss die Anwendung nicht in Unterklassen unterteilt werden. In den meisten Situationen können statische Singletons dieselbe Funktionalität auf modularere Weise bereitstellen. Wenn Ihr Singleton einen globalen Kontext benötigt (zum Beispiel um Registrierungsempfänger zu registrieren), kann der Funktion zum Abrufen ein Kontext zugewiesen werden, der beim erstmaligen Erstellen des Singletons intern Context.getApplicationContext () verwendet.


1
Wenn Sie eine Schnittstelle für den Singleton schreiben und getInstance nicht statisch lassen, können Sie sogar festlegen, dass der Standardkonstruktor der singletonverwendenden Klasse den Produktions-Singleton über einen nicht standardmäßigen Konstruktor injiziert, der auch der Konstruktor ist, den Sie zum Erstellen des Singletons verwenden Singleton-using-Klasse in ihren Unit-Tests.
android.weasel

11

Die Anwendung ist nicht die gleiche wie beim Singleton. Die Gründe sind:

  1. Die Methode der Anwendung (z. B. onCreate) wird im UI-Thread aufgerufen.
  2. Die Singleton-Methode kann in jedem Thread aufgerufen werden.
  3. In der Methode "onCreate" von Application können Sie Handler instanziieren.
  4. Wenn der Singleton in einem Nicht-UI-Thread ausgeführt wird, können Sie den Handler nicht instanziieren.
  5. Die Anwendung hat die Fähigkeit, den Lebenszyklus der Aktivitäten in der App zu verwalten. Sie hat die Methode "registerActivityLifecycleCallbacks". Aber die Singletons haben nicht die Fähigkeit.

1
Hinweis: Sie können Handler für jeden Thread instanziieren. aus doc: "Wenn Sie einen neuen Handler erstellen, ist er an die Thread- / Nachrichtenwarteschlange des Threads gebunden, der ihn erstellt"
Christ

1
@Christ Vielen Dank! Gerade habe ich den "Mechanismus des Looper" gelernt. Wenn der Handler im Nicht-UI-Thread ohne den Code 'Looper.prepare ()' instanziiert wird, meldet das System den Fehler "java.lang.RuntimeException: Can Erstellen Sie keinen Handler innerhalb eines Threads, der Looper.prepare () nicht aufgerufen hat. "
Sunhang

11

Ich hatte das gleiche Problem: Singleton oder eine Unterklasse android.os.Application erstellen?

Zuerst habe ich es mit dem Singleton versucht, aber meine App ruft irgendwann den Browser an

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

und das Problem ist, dass, wenn das Mobilteil nicht über genügend Speicher verfügt, die meisten Ihrer Klassen (sogar Singletons) bereinigt werden, um Speicherplatz zu erhalten. Bei der Rückkehr vom Browser zu meiner App stürzte es jedes Mal ab.

Lösung: Fügen Sie die erforderlichen Daten in eine Unterklasse der Anwendungsklasse ein.


1
Ich bin oft auf Beiträge gestoßen, in denen Leute behaupten, dass dies passieren könnte. Ich hänge daher einfach Objekte wie Singletons mit dem verzögerten Laden usw. an die Anwendung an, um sicherzugehen, dass der Lebenszyklus dokumentiert und bekannt ist. Stellen Sie nur sicher, dass Sie nicht Hunderte von Bildern in Ihrem Anwendungsobjekt speichern, da es meines Wissens nicht aus dem Speicher gelöscht wird, wenn sich Ihre App im Hintergrund befindet und alle Aktivitäten zerstört werden, um Speicher für andere Prozesse freizugeben.
Janusz

Nun, das verzögerte Laden von Singleton nach dem Neustart der Anwendung ist nicht der richtige Weg, um Objekte vom GC abtasten zu lassen. Schwache Referenzen sind, richtig?
Mschonaker

15
"Ja wirklich?" Dalvik entlädt Klassen und verliert den Programmstatus? Sind Sie sicher, dass es sich nicht um Müll handelt, der die Art von aktivitätsbezogenen Objekten mit begrenztem Lebenszyklus sammelt, die Sie überhaupt nicht in Singletons einfügen sollten? Sie müssen klare Beispiele für solch einen außergewöhnlichen Anspruch geben!
android.weasel

1
Es sei denn , es Änderungen Ich bin nicht bewusst, nicht Dalvik nicht entladen Klassen. Je. Das Verhalten, das sie sehen, ist, dass ihr Prozess im Hintergrund abgebrochen wird, um Platz für den Browser zu schaffen. Wahrscheinlich haben sie die Variable in ihrer "Haupt" -Aktivität initialisiert, die möglicherweise nicht im neuen Prozess erstellt wurde, als sie vom Browser zurückkam.
Groxx

5

Betrachten Sie beide gleichzeitig:

  • Singleton-Objekte als statische Instanzen innerhalb der Klassen haben.
  • Eine gemeinsame Klasse (Context) gibt die Singleton-Instanzen für alle Singelton-Objekte in Ihrer Anwendung zurück. Dies hat den Vorteil, dass die Methodennamen in Context beispielsweise von Bedeutung sind: context.getLoggedinUser () anstelle von User.getInstance ().

Darüber hinaus schlage ich vor, dass Sie Ihren Kontext erweitern, um nicht nur den Zugriff auf Singleton-Objekte, sondern auch einige Funktionen einzuschließen, auf die global zugegriffen werden muss, z. B. context.logOffUser (), context.readSavedData () usw. Vermutlich wird der Kontext in umbenannt Fassade würde dann Sinn machen.


4

Sie sind eigentlich gleich. Es gibt einen Unterschied, den ich sehen kann. Mit der Application-Klasse können Sie Ihre Variablen in Application.onCreate () initialisieren und in Application.onTerminate () zerstören. Bei Singleton müssen Sie sich darauf verlassen, dass die VM die Statik initialisiert und zerstört.


16
Die Dokumente für onTerminate besagen, dass es immer nur vom Emulator aufgerufen wird. Auf Geräten wird diese Methode wahrscheinlich nicht aufgerufen. developer.android.com/reference/android/app/…
danb

3

Meine 2 Cent:

Ich habe festgestellt, dass einige Singleton- / statische Felder zurückgesetzt wurden, als meine Aktivität zerstört wurde. Ich habe dies auf einigen Low-End-2.3-Geräten bemerkt.

Mein Fall war sehr einfach: Ich habe nur eine private Datei "init_done" und eine statische Methode "init", die ich von activity.onCreate () aufgerufen habe. Ich stelle fest, dass sich die Methode init bei einer Neuerstellung der Aktivität erneut selbst ausführte.

Obwohl ich meine Bestätigung nicht beweisen kann, kann dies damit zusammenhängen, WENN der Singleton / die Klasse zuerst erstellt / verwendet wurde. Wenn die Aktivität zerstört / recycelt wird, scheinen alle Klassen, auf die sich nur diese Aktivität bezieht, ebenfalls recycelt zu werden.

Ich habe meine Singleton-Instanz in eine Unterklasse von Application verschoben. Ich greife auf sie aus der Anwendungsinstanz zu. und bemerkte das Problem seitdem nicht mehr.

Ich hoffe das kann jemandem helfen.


3

Aus dem sprichwörtlichen Pferdemund ...

Bei der Entwicklung Ihrer App müssen Sie möglicherweise Daten, Kontext oder Dienste global für Ihre App freigeben. Wenn Ihre App beispielsweise Sitzungsdaten enthält, z. B. den aktuell angemeldeten Benutzer, möchten Sie diese Informationen wahrscheinlich verfügbar machen. In Android besteht das Muster zur Lösung dieses Problems darin, dass Ihre android.app.Application-Instanz alle globalen Daten besitzt und Ihre Anwendungsinstanz dann als Singleton mit statischen Zugriffsmethoden für die verschiedenen Daten und Dienste behandelt.

Wenn Sie eine Android-App schreiben, haben Sie garantiert nur eine Instanz der android.app.Application-Klasse. Daher ist es sicher (und vom Google Android-Team empfohlen), sie als Singleton zu behandeln. Das heißt, Sie können Ihrer Anwendungsimplementierung sicher eine statische getInstance () -Methode hinzufügen. Wie so:

public class AndroidApplication extends Application {

    private static AndroidApplication sInstance;

    public static AndroidApplication getInstance(){
        return sInstance;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        sInstance = this;
    }
}

2

Meine Aktivität ruft finish () auf (was nicht sofort beendet wird, aber irgendwann erledigt wird) und ruft Google Street Viewer auf. Wenn ich es auf Eclipse debugge, wird meine Verbindung zur App unterbrochen, wenn Street Viewer aufgerufen wird. Ich verstehe, dass die (gesamte) Anwendung geschlossen wird, um Speicher freizugeben (da eine einzelne Aktivität, die beendet wird, dieses Verhalten nicht verursachen sollte). . Trotzdem kann ich den Status in einem Bundle über onSaveInstanceState () speichern und in der onCreate () -Methode der nächsten Aktivität im Stapel wiederherstellen. Entweder durch Verwendung eines statischen Singletons oder einer Unterklassenanwendung kann die Anwendung geschlossen werden und den Status verlieren (es sei denn, ich speichere sie in einem Bundle). Aus meiner Erfahrung sind sie in Bezug auf die Erhaltung des Staates gleich. Ich habe festgestellt, dass die Verbindung in Android 4.1.2 und 4.2.2 unterbrochen ist, jedoch nicht in 4.0.7 oder 3.2.4.


"Ich habe festgestellt, dass die Verbindung in Android 4.1.2 und 4.2.2 unterbrochen ist, jedoch nicht in 4.0.7 oder 3.2.4, was meines Wissens darauf hindeutet, dass sich der Speicherwiederherstellungsmechanismus irgendwann geändert hat." ..... Ich würde denken, Ihre Geräte haben nicht die gleiche Menge an verfügbarem Speicher oder die gleiche App installiert. und deshalb ist Ihre Schlussfolgerung möglicherweise falsch
Christ

@Christ: Ja, du musst recht haben. Es wäre seltsam, wenn sich der Speicherwiederherstellungsmechanismus zwischen den Versionen ändern würde. Wahrscheinlich hat die unterschiedliche Speichernutzung das unterschiedliche Verhalten verursacht.
Piovezan
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.