Statische Referenzen werden gelöscht--muss Android entladen von Klassen zur Laufzeit, wenn nicht verwendete?
Ich habe eine spezifische Frage zu, wie die zum laden von Klassen /garbage collection arbeitet in Android. Wir haben stolperte über dieses Problem ein paar mal jetzt, und soweit ich sagen kann, Android verhält sich hier anders aus als eine gewöhnliche JVM.
Das problem ist dieses: Wir sind derzeit versucht zu reduzieren auf die singleton-Klassen in der app zu Gunsten einer einzigen root-factory-singleton-die einzige Aufgabe ist die Verwaltung von anderen manager-Klassen. Eine top-level-manager, wenn man so will. Dies macht es für uns nicht leicht zu ersetzen-Implementierungen in den tests, ohne sich für eine vollständige DI Lösung, da alle Aktivitäten und Dienstleistungen Anteil die gleiche Referenz auf das root-Fabrik.
Hier ist, wie es aussieht:
public class RootFactory {
private static volatile RootFactory instance;
@SuppressWarnings("unused")
private Context context; //I'd like to keep this for now
private volatile LanguageSupport languageSupport;
private volatile Preferences preferences;
private volatile LoginManager loginManager;
private volatile TaskManager taskManager;
private volatile PositionProvider positionManager;
private volatile SimpleDataStorage simpleDataStorage;
public static RootFactory initialize(Context context) {
instance = new RootFactory(context);
return instance;
}
private RootFactory(Context context) {
this.context = context;
}
public static RootFactory getInstance() {
return instance;
}
public LanguageSupport getLanguageSupport() {
return languageSupport;
}
public void setLanguageSupport(LanguageSupport languageSupport) {
this.languageSupport = languageSupport;
}
//...
}
initialize
einmal aufgerufen wird, in Application.onCreate
, d.h. vor jede Tätigkeit oder Dienst gestartet wird. Nun, hier ist das problem: die getInstance
Methode kommt manchmal zurück, wie null
- auch wenn Sie angerufen werden, auf dem gleichen thread! Das klingt wie es ist nicht eine Sichtbarkeit problem, sondern die statische singleton-Referenz halten auf Klassenebene scheint tatsächlich gelöscht wurden durch den garbage collector. Vielleicht habe ich voreilige Schlüsse zu ziehen hier, aber konnte dies sein, weil die Android-garbage-collector-oder class-loading-Mechanismus kann tatsächlich entladen Klassen, wenn der Speicher wird knapp, in dem Fall die einzige Referenz zur singleton-Instanz zu gehen? Ich bin nicht wirklich tief in der Java-memory-Modell, aber ich nehme an, dass sollte nicht passieren, sonst wird dieser gemeinsame Weg für die Umsetzung von singletons nicht funktionieren würde auf jede JVM, richtig?
Irgendeine Idee, warum dies geschieht genau?
PS: man kann es umgehen das, indem es "Globale" Verweise auf die einzige Instanz der Anwendung statt. Das bewiesen hat, zuverlässig zu sein, wenn man halten muss, auf Objekt, um über die gesamte Lebensdauer einer app.
UPDATE
Anscheinend ist mein Einsatz flüchtiger hier für Verwirrung. Meine Absicht war es, sicherzustellen, dass der statische Verweis der aktuelle Zustand ist immer sichtbar für alle threads zugreifen. Ich muss das tun, weil ich sowohl schreiben und Lesen, das referenzieren von mehr als einem thread: In einer normalen app läuft nur im Hauptanwendungs-thread, aber in einer Instrumentierung Testlauf, bei denen Objekte ersetzt zu bekommen mit mocks, ich Schreibe es aus der instrumentation-Gewinde-und Lesen Sie es auf dem UI-thread. Ich könnte auch synchronisiert den Anruf zu getInstance
, aber das ist teurer, da es erfordert, fordern Sie ein Objekt sperren. Sehen Was ist eine effiziente Methode zum implementieren eines singleton-pattern in Java? für eine detailliertere Diskussion um dieses.
- Ich habe gesehen, diese Art von Verhalten zu, während der Ausführung von Instrumentierung. Ich wäre daran interessiert zu sehen, eine Antwort. Aber danke für den Hinweis über
volatile
; ich werde haben Sie einen Blick auf, die. - "initialize aufgerufen wird, sobald in Anwendung.onCreate, also vor jeder Aktivität oder der Dienst wird gestartet" --> alle refs auf dieser ?
- Ein sinnvoller Schritt wäre, das logging
initialize()
undgetInstance()
, und bestätigen, dass das letztere wird nie aufgerufen, bevor die ehemaligen. Auch, fügen Sie die Protokollierung, um alles, was updatesinstance
. Der garbage collector nicht NULL Felder aus, und auch wenn Android hat entladen Klassen würde es nur tun, wenn alle Klassen geladen, die von den gleichen ClassLoader konnte verworfen werden. Beachten Sie, dass alle statischen Felder sind im wesentlichen "reset", wenn die app getötet wird, und durch das system wiederholt gestartet. - Hatten Sie schon einmal unten auf dieser? FYI: ein Beispiel für eine statische volatil präsentiert sich in Effektive Java-2. Auflage, Seite 262, Ziffer 66. Wie bereits erwähnt, flüchtig ist erforderlich, um sicherzustellen, dass das aktuellste Wert der Instanz ist sichtbar für alle threads. Obwohl, kann es sich lohnen, mit einem enum oder ein anderes Synchronisations-Technik. So wie es da steht, nichts verhindert, dass clients, die gegen die angenommenen singleton-invariante, weil jeder Aufruf von initialize erstellen einer anderen Instanz.
Du musst angemeldet sein, um einen Kommentar abzugeben.
Beide Sie (@Matthias) und Mark Murphy (@CommonsWare) sind korrekt, was Sie sagen, aber das wesentliche scheint verloren. (Die Verwendung von
volatile
korrekt ist und die Kurse werden nicht entladen.)Kern der Frage ist, wo
initialize
aufgerufen wird.Hier ist, was ich denke, passiert ist:
Activity
*Process
Application
und die topActivity
getInstance
wird wiedernull
alsinitialize
wurde nicht genannt,Mich korrigieren, wenn ich falsch bin.
Update:
Meine Vermutung–, dass
initialize
genannt wird, von einemActivity
* – scheint falsch gewesen, in diesem Fall. Allerdings lasse ich diese Antwort, weil, das Szenario ist eine häufige Quelle von Fehlern.Ich habe noch nie in meinem Leben gesehen ein statisches Datenelement deklariert
volatile
. Ich bin mir auch nicht sicher, was das bedeutet.Statische Datenmember, wird bestehen, bis der Prozess beendet wird oder bis Sie Sie loswerden können (z.B.
null
aus der statischen Verweis). Der Prozess kann beendet werden, sobald alle Aktivitäten und Dienstleistungen sind proaktiv geschlossen durch den Nutzer (z.B. ZURÜCK-Taste) und dem code (z.B.stopService()
). Der Prozess kann beendet werden, sogar mit live-Komponenten, wenn das Android ist verzweifelt kurz auf den RAM, aber das ist eher ungewöhnlich. Der Prozess kann beendet werden, mit einem live-Dienst, wenn Android denkt, dass Ihr Dienst wurde im hintergrund zu lange, obwohl es neu starten, der Dienst je nach Rückgabewert vononStartCommand()
.Klassen werden nicht entladen, Zeitraum, kurz, der Prozess wird beendet.
Adresse der anderen von @sergui die Punkte, Aktivitäten zerstört werden kann, mit Instanz-Zustand gespeichert (wenn auch im RAM, nicht "Feste Lagerung"), um freie RAM. Android wird dazu neigen, dies zu tun, vor dem beenden von aktiven Prozessen, aber wenn es zerstört die Letzte Aktivität des Prozesses und es gibt keine Laufenden Dienste,, der Prozess wird ein heißer Kandidat für die Kündigung.
Die einzige seltsame Sache, die erheblich über Ihre Umsetzung ist Ihre Nutzung
volatile
.volatile
bewirkt eine Leerung der Speicher zwischen threads gemeinsam genutzt werden. Mit flüchtige hier also stellt sicher, dass die Referenz immer sichtbar für alle threads zugreifen. Wenn volatile nicht verwendet, ist die Instrumentierung thread schreiben, der Hinweis, aber der UI-thread kann immer noch sehen Sie es als null (da der Speicher nicht synchronisiert wurde). Nicht sicher, was Sie seltsam finden, dass. Können Sie das noch näher erläutern?volatile
bedeutet. Ich habe noch nie gesehenvolatile
verwendet, auf eine statische Daten-member vor, und Ihre Wikipedia-Referenz nicht anzugeben, ob es verschiedene Merkmale einervolatile
statische versus einevolatile
non-static. Es scheint, wie die meisten Menschen heute verwendensynchronized
oderjava.util.concurrent
zu machen, was thread-safe. Dies bedeutet nicht, dass Ihre Nutzung desvolatile
ist falsch. Jedoch, es ist eine ungewöhnliche Sache, die ich sehe in Ihre Umsetzung, und daher ist ein Kandidat für irgendwie die Quelle der Schwierigkeit.volatile
. Ich entschuldige mich für den Versuch zu helfen Sie mit Ihrem problem.Statische Referenzen werden gelöscht, wenn das system fühlt sich an wie es und Ihre Anwendung ist nicht top-level (der Benutzer wird nicht ausgeführt es explizit). Wenn Ihr die app minimiert ist, und das OS will etwas mehr Speicher wird es entweder töten Sie Ihre app oder Serialisierung auf Feste Lagerung für die spätere Verwendung, aber in beiden Fällen statische Variablen werden gelöscht.
Auch, wenn Sie Ihre app bekommt eine
Force Close
Fehler, alle statics gelöscht werden. In meiner Erfahrung habe ich gesehen, dass es immer besser ist, um die Verwendung von Variablen in das Application-Objekt als statische Variablen.Ich habe gesehen, ähnlich seltsames Verhalten mit meinem eigenen code mit dem verschwinden von statischen Variablen (ich glaube nicht, dass dieses problem nichts zu tun hat mit dem volatile-Schlüsselwort). Insbesondere hat dies, wenn ich initialisiert haben ein logging-framework (ex. Crashlytics, log4j), und dann nach einiger Zeit der Aktivität, es scheint nicht initialisiert werden. Die Untersuchung hat gezeigt, daß dies geschieht, nachdem die OS Aufrufe
onSaveInstanceState(Bundle b)
.Statischen Variablen gehalten werden, indem der Classloader, die sich innerhalb Ihrer app verarbeiten. Laut google:
http://developer.android.com/guide/topics/processes/process-lifecycle.html
Was das bedeutet für einen Entwickler ist, dass Sie nicht erwarten können, dass statische Variablen bleiben auf unbestimmte Zeit initialisiert. Sie müssen sich verlassen auf einen anderen Mechanismus für die Persistenz.
Einen workaround, den ich verwendet habe, um meine logging-framework initialisiert ist für alle meine Aktivitäten zu erweitern, eine Basis-Klasse, wo ich überschreiben
onCreate
und überprüfen Sie für die Initialisierung und re-Initialisierung, wenn nötig.Ich denke, die offizielle Lösung ist die Verwendung der
onSaveInstanceState(Bundle b)
Rückruf zu bestehen, etwas, dass Ihre Tätigkeit muss später, und dann re-initialisierenonCreate(Bundle b)
wennb != null
.Google erklärt es am besten:
http://developer.android.com/training/basics/activity-lifecycle/recreating.html