Warnung: Platzieren Sie Android-Kontextklassen nicht in statischen Feldern. Dies ist ein Speicherverlust (und bricht auch Instant Run)


83

Android Studio:

Platzieren Sie Android-Kontextklassen nicht in statischen Feldern. Dies ist ein Speicherverlust (und bricht auch Instant Run)

Also 2 Fragen:

# 1 Wie ruft man startServiceeine statische Methode ohne statische Variable für den Kontext auf?
# 2 Wie senden Sie einen localBroadcast von einer statischen Methode (gleich)?

Beispiele:

public static void log(int iLogLevel, String sRequest, String sData) {
    if(iLogLevel > 0) {

        Intent intent = new Intent(mContext, LogService.class);
        intent.putExtra("UPDATE_MAIN_ACTIVITY_VIEW", "UPDATE_MAIN_ACTIVITY_VIEW");
        mContext.startService(intent);
    }
}

oder

        Intent intent = new Intent(MAIN_ACTIVITY_RECEIVER_INTENT);
        intent.putExtra(MAIN_ACTIVITY_REQUEST_FOR_UPDATE, sRequest));
        intent.putExtra(MAIN_ACTIVITY_DATA_FOR_VIEW, sData);
        intent.putExtra(MAIN_ACTIVITY_LOG_LEVEL, iLogLevel);
        LocalBroadcastManager.getInstance(mContext).sendBroadcast(intent);

Was wäre der richtige Weg, dies ohne zu tun mContext ?

HINWEIS: Ich denke, meine Hauptfrage könnte sein, wie der Kontext an eine Klasse übergeben werden kann, aus der die aufrufende Methode stammt.


Können Sie Context nicht als Parameter in der Methode übergeben?
Juan Cruz Soler

Ich würde diese Routine an Orten aufrufen, die keinen Kontext hätten.
John Smith

# 1 übergibt es als Parameter # 2 gleich.
NJZK2

Dann müssen Sie den Kontext auch an die Aufrufermethode übergeben. Das Problem ist, dass statische Felder kein Müll gesammelt werden, so dass Sie eine Aktivität mit all ihren Ansichten verlieren könnten
Juan Cruz Soler

1
@JohnSmith Kaskadieren Sie es von der initiierenden Aktivität (über Konstruktorparameter oder Methodenparameter) bis zu dem Punkt, an dem Sie es benötigen.
AndroidMechanic - Viral Patel

Antworten:


55

Übergeben Sie es einfach als Parameter an Ihre Methode. Es macht keinen Sinn, eine statische Instanz von Contextausschließlich zum Starten einer zu erstellen Intent.

So sollte Ihre Methode aussehen:

public static void log(int iLogLevel, String sRequest, String sData, Context ctx) {
    if(iLogLevel > 0) {

        Intent intent = new Intent(ctx, LogService.class);
        intent1.putExtra("UPDATE_MAIN_ACTIVITY_VIEW", "UPDATE_MAIN_ACTIVITY_VIEW");
        ctx.startService(intent);
    }
}

Aktualisierung von Kommentaren zu Frage: Kaskadieren Sie den Kontext von der initiierenden Aktivität (über Konstruktorparameter oder Methodenparameter) bis zu dem Punkt, an dem Sie ihn benötigen.


Können Sie ein Konstruktorbeispiel angeben?
John Smith

Wenn Ihr Klassenname MyClassein öffentlicher Konstruktor wie eine Methode ist public MyClass(Context ctx) { // put this ctx somewhere to use later }(Dies ist Ihr Konstruktor), erstellen Sie jetzt eine neue Instanz der MyClassVerwendung dieses Konstruktors, z. B.MyClass mc = new MyClass(ctx);
AndroidMechanic - Viral Patel

Ich denke nicht, dass es so einfach ist, auf Anfrage weiterzugeben. Obwohl es offensichtliche Vorteile gibt, wie sich keine Gedanken über einen veralteten Kontext machen zu müssen oder wie hier einen statischen. Angenommen, Sie benötigen einen Kontext [möglicherweise möchten Sie in die Einstellungen schreiben] in einem Antwortrückruf, der asynchron aufgerufen wird. Manchmal müssen Sie es also in ein Mitgliedsfeld einfügen. Und jetzt muss man sich überlegen, wie man es nicht statisch macht. stackoverflow.com/a/40235834/2695276 scheint zu funktionieren.
Rajat Sharma

1
Ist es in Ordnung, ApplicationContext als statisches Feld zu verwenden? Im Gegensatz zu Aktivitäten wird das Anwendungsobjekt nicht zerstört, oder?
NeoWang

50

Stellen Sie einfach sicher, dass Sie context.getApplicationContext () übergeben oder getApplicationContext () für jeden Kontext aufrufen, der über Methoden / Konstruktor an Ihren Singleton übergeben wird, wenn Sie ihn in einem Mitgliedsfeld speichern möchten.

Beispiel für einen idiotensicheren Beweis (selbst wenn jemand eine Aktivität übergeben würde, wird der App-Kontext erfasst und damit der Singleton instanziiert):

public static synchronized RestClient getInstance(Context context) {
    if (mInstance == null) {
        mInstance = new RestClient(context.getApplicationContext());
    }
    return mInstance;
}

getApplicationContext () gemäß den Dokumenten: " Gibt den Kontext des einzelnen globalen Anwendungsobjekts des aktuellen Prozesses zurück."

Dies bedeutet, dass der von "getApplicationContext ()" zurückgegebene Kontext den gesamten Prozess durchläuft. Daher spielt es keine Rolle, ob Sie irgendwo einen statischen Verweis darauf speichern, da er zur Laufzeit Ihrer App immer vorhanden ist (und alle Objekte überlebt) / Singletons dadurch instanziiert).

Vergleichen Sie dies mit dem Kontext innerhalb von Ansichten / Aktivitäten, die große Datenmengen enthalten. Wenn Sie einen Kontext verlieren, der von einer Aktivität gehalten wird, kann das System diese Ressource nicht freigeben, was offensichtlich nicht gut ist.

Ein Verweis auf eine Aktivität nach ihrem Kontext sollte denselben Lebenszyklus wie die Aktivität selbst haben, andernfalls wird der Kontext als Geisel gehalten, was zu einem Speicherverlust führt (was der Grund für die Flusenwarnung ist).

BEARBEITEN: Für den Kerl, der das Beispiel aus den obigen Dokumenten verprügelt hat, gibt es im Code sogar einen Kommentarbereich über das, worüber ich gerade geschrieben habe:

    // getApplicationContext() is key, it keeps you from leaking the
    // Activity or BroadcastReceiver if someone passes one in.

8
an den Kerl, der den Kerl verprügelt hat, der das obige Beispiel verprügelt hat: Der Punkt dieses Threads ist die Lint-Warnung, die im Widerspruch zu Googles empfohlenem Muster zum Erstellen von Singleton steht.
Raphael C

7
Lesen Sie: "Platzieren Sie Android-Kontextklassen nicht in statischen Feldern. Dies ist ein Speicherverlust (und unterbricht auch den Sofortlauf)." Wissen Sie, was Kontextklassen sind? Aktivität ist eine davon, und Sie sollten Aktivität nicht als statisches Feld speichern, wie Sie selbst beschrieben haben (da sonst Speicher verloren geht). Sie können jedoch den Kontext (solange es sich um den Anwendungskontext handelt) als statisches Feld speichern, da er alles überlebt. (Und ignorieren Sie daher die Warnung). Ich bin sicher, wir können uns auf diese einfache Tatsache einigen, oder?
Marcus Gruneau

Als iOS-Tierarzt in meiner ersten Woche mit Android ... Erklärungen wie diese helfen mir, diesen Kontext-Unsinn zu verstehen. Diese Flusenwarnung (oh, wie ich Warnungen nicht mag) wird also herumhängen, aber Ihre Antwort löst das eigentliche Problem .
Eric

@Marcus Wenn Ihre untergeordnete Klasse nicht weiß, wer sie mit welchem ​​Kontext instanziiert, ist es nur eine schlechte Praxis, sie als statisches Mitglied zu speichern. Darüber hinaus lebt der Anwendungskontext als Teil des Anwendungsobjekts Ihrer App. Das Anwendungsobjekt bleibt nicht für immer im Speicher, es wird getötet. Entgegen der landläufigen Meinung wird die App nicht von Grund auf neu gestartet. Android erstellt ein neues Anwendungsobjekt und startet die Aktivität, bei der der Benutzer zuvor war, um die Illusion zu vermitteln, dass die Anwendung überhaupt nicht getötet wurde.
Raphael C

@ RaphaelC haben Sie Dokumentation von solchen? Das scheint völlig falsch zu sein, da Android nur einen Anwendungskontext pro Lauf jedes Prozesses sicherstellt.
HaydenKai

6

Es ist nur eine Warnung. Mach dir keine Sorgen. Wenn Sie einen Anwendungskontext verwenden möchten, können Sie ihn in einer "Singleton" -Klasse speichern, mit der die gesamte Singleton-Klasse in Ihrem Projekt gespeichert wird.


2

In Ihrem Fall ist es nicht sehr sinnvoll, es als statisches Feld zu haben, aber ich denke nicht, dass es in allen Fällen schlecht ist. Wenn Sie jetzt wissen, was Sie tun, können Sie ein statisches Feld mit Kontext haben und es später auf Null setzen. Ich erstelle eine statische Instanz für meine Hauptmodellklasse, die einen Kontext enthält, deren Anwendungskontext kein Aktivitätskontext ist, und ich habe auch ein statisches Instanzfeld der Klasse, das Aktivität enthält, die ich beim Zerstören auf Null stelle. Ich sehe nicht, dass ich einen Speicherverlust habe. Also, wenn ein kluger Kerl denkt, ich liege falsch, kannst du gerne einen Kommentar abgeben ...

Auch Instant Run funktioniert hier gut ...


Ich glaube nicht, dass Sie sich in diesem Prinzip irren, aber Sie müssen besonders darauf achten, dass die Aktivität, über die Sie sprechen, zu einem bestimmten Zeitpunkt nur maximal eine einzelne Instanz enthält, bevor statische Felder verwendet werden können. Wenn Ihre App mehr als einen Backstack hat, weil sie von verschiedenen Orten aus gestartet werden kann (Benachrichtigung, Deep Linking, ...), gehen Dinge schief, es sei denn, Sie verwenden ein Flag wie singleInstance im Manifest. So ist es immer einfacher, statische Felder aus Aktivitäten zu vermeiden.
BladeCoder

android: launchMode = "singleTask" sollte ausreichen, also wechsle ich dazu, ich habe singleTop verwendet, wusste aber nicht, dass es nicht genug ist, weil ich immer nur einzelne Instanzen meiner Hauptaktivitäten haben möchte, so sind meine Apps gestaltet.
Renetik

2
"singleTask" garantiert nur eine Instanz pro Aufgabe. Wenn Ihre App über mehrere Einstiegspunkte verfügt, z. B. Deep Linking oder Starten über eine Benachrichtigung, werden möglicherweise mehrere Aufgaben ausgeführt.
BladeCoder

1

Vermeiden Sie im Allgemeinen, dass Kontextfelder als statisch definiert werden. Die Warnung selbst erklärt, warum: Es ist ein Speicherverlust. Sofortiger Lauf kann brechen nicht das größte Problem auf dem Planeten.

Nun gibt es zwei Szenarien, in denen Sie diese Warnung erhalten würden. Für eine Instanz (die offensichtlichste):

public static Context ctx;

Und dann gibt es das etwas kniffligere, bei dem der Kontext in eine Klasse eingeschlossen ist:

public class Example{
    public Context ctx;
    //Constructor omitted for brievety 
}

Und diese Klasse ist irgendwo als statisch definiert:

public static Example example;

Und du wirst die Warnung bekommen.

Die Lösung selbst ist recht einfach: Platzieren Sie keine Kontextfelder in statischen Instanzen , sei es die einer Wrapping-Klasse, oder deklarieren Sie sie direkt als statisch.

Die Lösung für die Warnung ist einfach: Platzieren Sie das Feld nicht statisch. Übergeben Sie in Ihrem Fall den Kontext als Instanz an die Methode. Verwenden Sie für Klassen, in denen mehrere Kontextaufrufe ausgeführt werden, einen Konstruktor, um den Kontext (oder eine entsprechende Aktivität) an die Klasse zu übergeben.

Beachten Sie, dass es sich um eine Warnung und nicht um einen Fehler handelt. Wenn Sie aus irgendeinem Grund einen statischen Kontext benötigen , können Sie dies tun. Dabei entsteht jedoch ein Speicherverlust.


Wie können wir das tun, ohne einen Speicherverlust zu verursachen?
isJulian00

1
Das kannst du nicht. Wenn Sie unbedingt Kontexte weitergeben müssen, können Sie in einen Eventbus schauen
Zoe

ok, das war das Problem, das ich hatte, wenn Sie es sich bitte ansehen könnten, vielleicht gibt es eine andere Möglichkeit, es zu tun. Übrigens muss die Methode statisch sein, weil ich sie von c ++ - Code aufhebe stackoverflow.com/questions/54683863/…
isJulian00

0

Wenn Sie sicherstellen, dass es sich um einen Anwendungskontext handelt. Es ist wichtig. Füge das hinzu

@SuppressLint("StaticFieldLeak")

Ich würde es sowieso nicht empfehlen. Wenn Sie Kontext benötigen, können Sie die Methode requireContext () verwenden, wenn Sie AndroidX-Bibliotheken verwenden. Oder Sie können den Kontext direkt an die Methode übergeben, die ihn benötigt. Oder Sie können sogar nur die Klassenreferenz der App abrufen, aber ich würde eher empfehlen, einen solchen SuppressLint-Vorschlag nicht zu verwenden.
Oleksandr Nr.

0

Verwenden Sie WeakReferencediese Option , um den Kontext in Singleton-Klassen zu speichern. Die Warnung wird gelöscht

private WeakReference<Context> context;

//Private contructor
private WidgetManager(Context context) {
    this.context = new WeakReference<>(context);
}

//Singleton
public static WidgetManager getInstance(Context context) {
    if (null == widgetManager) {
        widgetManager = new WidgetManager(context);
    }
    return widgetManager;
}

Jetzt können Sie auf Kontext wie zugreifen

  if (context.get() instanceof MainActivity) {
            ((MainActivity) context.get()).startActivityForResult(pickIntent, CODE_REQUEST_PICK_APPWIDGET);
        }
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.