Anwendungskontext überall verwenden?

476

Stimmt in einer Android-App der folgende Ansatz nicht:

public class MyApp extends android.app.Application {

    private static MyApp instance;

    public MyApp() {
        instance = this;
    }

    public static Context getContext() {
        return instance;
    }

}

und überall dort übergeben (zB SQLiteOpenHelper), wo Kontext benötigt wird (und natürlich nicht leckt)?

yanchenko
quelle
23
<application>Um dies für andere zu erläutern, die dies implementieren, können Sie den Knoten Ihrer AndroidManifest.xml-Datei so ändern , dass er die folgende Attributdefinition enthält : android:name="MyApp". MyApp muss sich unter demselben Paket befinden, auf das Ihr Manifest verweist.
Matt Huggins
6
FANTASTISCHER Weg, um das Problem der Bereitstellung eines Kontexts für SQLiteOpenHelper zu umgehen !! Ich habe einen Singleton "SQLiteManager" implementiert und war festgefahren bei "Wie bekomme ich einen Kontext zum Singleton?"
Jemand irgendwo
8
Nur damit Sie wissen, dass Sie Ihre Anwendung über eine der Superschnittstellen zurücksenden. Wenn Sie also zusätzliche Methoden in MyApp bereitstellen, können Sie diese nicht verwenden. Ihr getContext () sollte stattdessen einen Rückgabetyp von MyApp haben. Auf diese Weise können Sie später hinzugefügte Methoden sowie alle Methoden in ContextWrapper und Context verwenden.
5
Siehe auch goo.gl/uKcFn - es ist eine weitere Antwort im Zusammenhang mit einem ähnlichen Beitrag. Setzen Sie die statische Variable besser in onCreate und nicht in c'tor.
AlikElzin-Kilaka
1
@ChuongPham Wenn das Framework Ihre App getötet hat, wird nichts auf den Null-Kontext zugreifen ...
Kevin Krumwiede

Antworten:

413

Es gibt einige potenzielle Probleme mit diesem Ansatz, obwohl er unter vielen Umständen (wie in Ihrem Beispiel) gut funktioniert.

Insbesondere sollten Sie vorsichtig sein, wenn Sie mit etwas umgehen, das sich mit dem befasst GUI, was a erfordert Context. Wenn Sie beispielsweise den Anwendungskontext an übergeben, erhalten LayoutInflaterSie eine Ausnahme. Im Allgemeinen ist Ihr Ansatz ausgezeichnet: Es ist empfehlenswert, einen Activity's Contextinnerhalb dieses Ansatzes zu verwenden Activityund Application Contextbeim Übergeben eines Kontexts, der über den Rahmen eines hinausgeht Activity, Speicherlecks zu vermeiden .

Auch als Alternative zu Ihrem Muster können Sie die Verknüpfung von Aufrufen verwenden getApplicationContext()auf einem ContextObjekt (zB eine Aktivität) , um den Anwendungskontext zu erhalten.

Reto Meier
quelle
22
Vielen Dank für eine inspirierende Antwort. Ich denke, ich werde diesen Ansatz ausschließlich für die Persistenzschicht verwenden (da ich nicht mit Inhaltsanbietern zusammenarbeiten möchte). Fragen Sie sich, was die Motivation war, SQLiteOpenHelper so zu gestalten, dass erwartet wird, dass ein Kontext bereitgestellt wird, anstatt ihn von der Anwendung selbst zu beziehen. PS Und dein Buch ist großartig!
Yanchenko
7
Die Verwendung des Anwendungskontexts mit hat LayoutInflatorgerade für mich funktioniert. Muss in den letzten drei Jahren geändert worden sein.
Jacob Phillips
5
@JacobPhillips Wenn Sie LayoutInflator ohne Aktivitätskontext verwenden, wird das Styling dieser Aktivität nicht berücksichtigt. Es würde also in einem Sinne funktionieren, aber nicht in einem anderen.
Mark
1
@MarkCarter Meinst du, dass die Verwendung des Anwendungskontexts das Styling der Aktivität verpasst?
Jacob Phillips
1
@JacobPhillips Ja, der Anwendungskontext kann nicht das Styling haben, da jede Aktivität möglicherweise anders gestaltet ist.
Mark
28

Nach meiner Erfahrung sollte dieser Ansatz nicht notwendig sein. Wenn Sie den Kontext für etwas benötigen, können Sie ihn normalerweise über einen Aufruf von View.getContext () abrufen. Mit dem dort Contexterhaltenen können Sie Context.getApplicationContext () aufrufen , um den ApplicationKontext abzurufen . Wenn Sie versuchen, den ApplicationKontext von einem Activityabzurufen, können Sie jederzeit Activity.getApplication () aufrufen , das als Contextfür einen Aufruf an übergeben werden sollte SQLiteOpenHelper().

Insgesamt scheint es kein Problem mit Ihrem Ansatz für diese Situation zu geben, aber stellen Sie beim Umgang Contextdamit sicher, dass Sie nirgendwo Speicher verlieren, wie im offiziellen Blog von Google Android Developers beschrieben .

snctln
quelle
13

Einige Leute haben gefragt: Wie kann der Singleton einen Nullzeiger zurückgeben? Ich beantworte diese Frage. (Ich kann nicht in einem Kommentar antworten, da ich eine Postleitzahl eingeben muss.)

Zwischen zwei Ereignissen kann null zurückgegeben werden: (1) die Klasse wird geladen und (2) das Objekt dieser Klasse wird erstellt. Hier ist ein Beispiel:

class X {
    static X xinstance;
    static Y yinstance = Y.yinstance;
    X() {xinstance=this;}
}
class Y {
    static X xinstance = X.xinstance;
    static Y yinstance;
    Y() {yinstance=this;}
}

public class A {
    public static void main(String[] p) {
    X x = new X();
    Y y = new Y();
    System.out.println("x:"+X.xinstance+" y:"+Y.yinstance);
    System.out.println("x:"+Y.xinstance+" y:"+X.yinstance);
    }
}

Lassen Sie uns den Code ausführen:

$ javac A.java 
$ java A
x:X@a63599 y:Y@9036e
x:null y:null

Die zweite Zeile zeigt , daß Y.xinstance und X.yinstance ist null ; sie sind null , weil die Variablen X.xinstance ans Y.yinstance gelesen wurden , wenn sie null waren.

Kann das behoben werden? Ja,

class X {
    static Y y = Y.getInstance();
    static X theinstance;
    static X getInstance() {if(theinstance==null) {theinstance = new X();} return theinstance;}
}
class Y {
    static X x = X.getInstance();
    static Y theinstance;
    static Y getInstance() {if(theinstance==null) {theinstance = new Y();} return theinstance;}
}

public class A {
    public static void main(String[] p) {
    System.out.println("x:"+X.getInstance()+" y:"+Y.getInstance());
    System.out.println("x:"+Y.x+" y:"+X.y);
    }
}

und dieser Code zeigt keine Anomalie:

$ javac A.java 
$ java A
x:X@1c059f6 y:Y@152506e
x:X@1c059f6 y:Y@152506e

ABER dies ist keine Option für das Android- ApplicationObjekt: Der Programmierer steuert nicht die Zeit, zu der es erstellt wird.

Noch einmal: Der Unterschied zwischen dem ersten und dem zweiten Beispiel besteht darin, dass das zweite Beispiel eine Instanz erstellt, wenn der statische Zeiger null ist. Ein Programmierer kann das Android-Anwendungsobjekt jedoch nicht erstellen, bevor sich das System dafür entscheidet.

AKTUALISIEREN

Ein weiteres rätselhaftes Beispiel, in dem sich initialisierte statische Felder befinden null.

Main.java :

enum MyEnum {
    FIRST,SECOND;
    private static String prefix="<", suffix=">";
    String myName;
    MyEnum() {
        myName = makeMyName();
    }
    String makeMyName() {
        return prefix + name() + suffix;
    }
    String getMyName() {
        return myName;
    }
}
public class Main {
    public static void main(String args[]) {
        System.out.println("first: "+MyEnum.FIRST+" second: "+MyEnum.SECOND);
        System.out.println("first: "+MyEnum.FIRST.makeMyName()+" second: "+MyEnum.SECOND.makeMyName());
        System.out.println("first: "+MyEnum.FIRST.getMyName()+" second: "+MyEnum.SECOND.getMyName());
    }
}

Und du bekommst:

$ javac Main.java
$ java Main
first: FIRST second: SECOND
first: <FIRST> second: <SECOND>
first: nullFIRSTnull second: nullSECONDnull

Beachten Sie, dass Sie die statische Variablendeklaration nicht um eine Zeile nach oben verschieben können. Der Code wird nicht kompiliert.

18446744073709551615
quelle
3
Nützliches Beispiel; Es ist gut zu wissen, dass es so ein Loch gibt. Was ich davon wegnehme, ist, dass man es vermeiden sollte, während der statischen Initialisierung einer Klasse auf eine solche statische Variable zu verweisen.
ToolmakerSteve
10

Anwendungsklasse:

import android.app.Application;
import android.content.Context;

public class MyApplication extends Application {

    private static Context mContext;

    public void onCreate() {
        super.onCreate();
        mContext = getApplicationContext();
    }

    public static Context getAppContext() {
        return mContext;
    }

}

Deklarieren Sie die Anwendung im AndroidManifest:

<application android:name=".MyApplication"
    ...
/>

Verwendungszweck:

MyApplication.getAppContext()
toha
quelle
1
Anfällig für Speicherlecks. Du solltest das niemals tun.
Dragas
9

Sie versuchen, einen Wrapper zu erstellen, um den Anwendungskontext abzurufen, und es besteht die Möglichkeit, dass er den nullZeiger " " zurückgibt.

Nach meinem Verständnis denke ich, dass es besser ist, eine der 2 Context.getApplicationContext() oder 2 anzurufen Activity.getApplication().

Prasanta
quelle
13
wann sollte es null zurückgeben?
Stuck
25
Es gibt keine statische Context.getApplicationContext () -Methode, die mir bekannt ist. Vermisse ich etwas
Dalcantara
Ich implementiere den gleichen Ansatz auch in meiner Anwendung, aber beim Aufrufen von SQLiteOpenHelper wird der Nullzeiger zurückgegeben. Jede Antwort auf diese Art von Situation.
Ashutosh
2
Dies kann der Fall sein, wenn Sie SQLiteOpenHelper in einem Inhaltsanbieter aufrufen, der vor der App geladen wird.
Gunnar Bernstein
5

Es ist ein guter Ansatz. Ich benutze es auch selbst. Ich würde nur vorschlagen, zu überschreiben onCreate, um den Singleton festzulegen, anstatt einen Konstruktor zu verwenden.

Und da haben Sie schon erwähnt SQLiteOpenHelper: In können onCreate ()Sie auch die Datenbank öffnen.

Persönlich denke ich, dass die Dokumentation falsch war, als sie sagte, dass es normalerweise nicht notwendig ist, die Anwendung in eine Unterklasse zu unterteilen . Ich denke, das Gegenteil ist der Fall: Sie sollten Application immer unterordnen.

Martin
quelle
3

Ich würde den Anwendungskontext verwenden, um einen Systemdienst im Konstruktor abzurufen. Dies erleichtert das Testen und profitiert von der Zusammensetzung

public class MyActivity extends Activity {

    private final NotificationManager notificationManager;

    public MyActivity() {
       this(MyApp.getContext().getSystemService(NOTIFICATION_SERVICE));
    }

    public MyActivity(NotificationManager notificationManager) {
       this.notificationManager = notificationManager;
    }

    // onCreate etc

}

Die Testklasse würde dann den überladenen Konstruktor verwenden.

Android würde den Standardkonstruktor verwenden.

Blundell
quelle
1

Ich mag es, aber ich würde stattdessen einen Singleton vorschlagen:

package com.mobidrone;

import android.app.Application;
import android.content.Context;

public class ApplicationContext extends Application
{
    private static ApplicationContext instance = null;

    private ApplicationContext()
    {
        instance = this;
    }

    public static Context getInstance()
    {
        if (null == instance)
        {
            instance = new ApplicationContext();
        }

        return instance;
    }
}
Franklin Peña
quelle
31
Das Erweitern von android.app.application garantiert bereits Singleton, so dass dies nicht erforderlich ist
Vincent
8
Was ist, wenn Sie Zugang zu Nicht-Aktivitätsklassen wünschen?
Maxrunner
9
Sie sollten newdie Anwendung niemals selbst erstellen (mit der möglichen Ausnahme von Unit-Tests). Das Betriebssystem wird das tun. Sie sollten auch keinen Konstruktor haben. Dafür ist da onCreate.
Martin
@ Vincent: Kannst du einen Link dazu posten? vorzugsweise Code - ich frage
Mr_and_Mrs_D
@radzio warum sollten wir das nicht im Konstruktor machen?
Miha_x64
1

Ich benutze den gleichen Ansatz, ich schlage vor, den Singleton etwas besser zu schreiben:

public static MyApp getInstance() {

    if (instance == null) {
        synchronized (MyApp.class) {
            if (instance == null) {
                instance = new MyApp ();
            }
        }
    }

    return instance;
}

aber ich benutze nicht überall, ich benutze getContext()und getApplicationContext()wo ich es tun kann!

Seraphims
quelle
Schreiben Sie also bitte einen Kommentar, um zu erklären, warum Sie die Antwort abgelehnt haben, damit ich sie verstehen kann. Der Singleton-Ansatz wird häufig verwendet, um einen gültigen Kontext außerhalb von Aktivitäten oder Ansichten zu erhalten ...
Seraphims
1
Keine Notwendigkeit, da das Betriebssystem sicherstellt, dass die Anwendung genau einmal instanziiert wird. Wenn überhaupt, würde ich vorschlagen, den Singelton in onCreate () zu setzen.
Martin
1
Ein guter Thread-sicherer Weg, um einen Singleton faul zu initialisieren, aber hier nicht notwendig.
NaXa
2
Wow, gerade als ich dachte, die Leute hätten endlich aufgehört, doppelt überprüfte Sperren zu verwenden ... cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
Søren Boisen