Usi il contesto dell'applicazione ovunque?


476

In un'app Android, c'è qualcosa che non va nel seguente approccio:

public class MyApp extends android.app.Application {

    private static MyApp instance;

    public MyApp() {
        instance = this;
    }

    public static Context getContext() {
        return instance;
    }

}

e passarlo ovunque (es. SQLiteOpenHelper) dove è richiesto il contesto (e ovviamente non perde)?


23
Proprio per elaborare per gli altri danno attuazione alla presente, è possibile quindi modificare il <application>nodo del file AndroidManifest.xml per includere la seguente definizione di attributo: android:name="MyApp". MyApp deve trovarsi nello stesso pacchetto a cui fa riferimento il tuo manifest.
Matt Huggins,

6
Ottimo modo per aggirare il problema di fornire un contesto a SQLiteOpenHelper !! Ho implementato un "SQLiteManager" singleton ed ero bloccato su "come F ottengo un contesto per il singleton?"
Someone Somewhere

8
Solo per sapere che stai restituendo la tua applicazione tramite una delle sue super interfacce, quindi se fornissi metodi aggiuntivi all'interno di MyApp non saresti in grado di usarli. GetContext () dovrebbe invece avere un tipo restituito di MyApp e in questo modo è possibile utilizzare i metodi aggiunti in seguito, nonché tutti i metodi in ContextWrapper e Context.

5
Vedi anche goo.gl/uKcFn - è un'altra risposta relativa a un post simile. Meglio impostare la variabile statica in onCreate e non in c'tor.
AlikElzin-Kilaka,

1
@ChuongPham Se il framework ha ucciso la tua app, non ci sarà nulla che acceda al contesto null ...
Kevin Krumwiede,

Risposte:


413

Ci sono un paio di potenziali problemi con questo approccio, sebbene in molte circostanze (come il tuo esempio) funzionerà bene.

In particolare dovresti stare attento quando hai a che fare con tutto ciò che ha a che fare con GUIun Context. Ad esempio, se si passa l'applicazione Contesto in LayoutInflatersi otterrà un'eccezione. In generale, il tuo approccio è eccellente: è una buona pratica usare un Activity's Contextdentro quello Activity, e Application Contextquando si passa un contesto oltre lo scopo di un Activityper evitare perdite di memoria .

Inoltre, in alternativa al modello è possibile utilizzare il collegamento di chiamata getApplicationContext()a un Contextoggetto (come un'attività) per ottenere il contesto dell'applicazione.


22
Grazie per una risposta stimolante. Penso che userò questo approccio esclusivamente per il livello di persistenza (poiché non voglio andare con i fornitori di contenuti). Mi chiedo quale sia stata la motivazione alla base della progettazione di SQLiteOpenHelper in un modo che prevede che venga fornito un contesto invece di acquisirlo dall'applicazione stessa. PS E il tuo libro è fantastico!
yanchenko,

7
Utilizzando il contesto dell'applicazione con LayoutInflatorappena lavorato per me. Deve essere stato cambiato negli ultimi tre anni.
Jacob Phillips,

5
@JacobPhillips L'uso di LayoutInflator senza un contesto di attività perderà lo stile di quell'attività. Quindi funzionerebbe in un certo senso, ma non in un altro.
Segna il

1
@MarkCarter Vuoi dire che l'utilizzo del contesto applicativo perderà lo stile dell'attività?
Jacob Phillips,

1
@JacobPhillips sì, il contesto dell'applicazione non può avere lo stile perché ogni attività può essere definita in modo diverso.
Segna il

28

Nella mia esperienza questo approccio non dovrebbe essere necessario. Se hai bisogno del contesto per qualsiasi cosa, di solito puoi ottenerlo tramite una chiamata a View.getContext () e usando quello Contextottenuto puoi chiamare Context.getApplicationContext () per ottenere il Applicationcontesto. Se stai cercando di ottenere Applicationquesto contesto da un Activity, puoi sempre chiamare Activity.getApplication () che dovrebbe essere in grado di essere passato come Contextnecessario per una chiamata a SQLiteOpenHelper().

Nel complesso non sembra esserci un problema con il tuo approccio a questa situazione, ma quando Contexthai a che fare assicurati solo che non stai perdendo memoria da nessuna parte come descritto sul blog ufficiale degli sviluppatori Android di Google .


13

Alcune persone hanno chiesto: come può il singleton restituire un puntatore nullo? Sto rispondendo a questa domanda. (Non posso rispondere in un commento perché devo inserire il codice.)

Può restituire null tra due eventi: (1) la classe viene caricata e (2) viene creato l'oggetto di questa classe. Ecco un esempio:

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);
    }
}

Eseguiamo il codice:

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

La seconda riga mostra che Y.xinstance e X.yinstance sono null ; sono nulli perché le variabili X.xinstance e Y.yinstance sono state lette quando erano nulle.

Questo può essere risolto? Sì,

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);
    }
}

e questo codice non mostra anomalie:

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

MA questa non è un'opzione per l' Applicationoggetto Android : il programmatore non controlla l'ora in cui viene creato.

Ancora una volta: la differenza tra il primo esempio e il secondo è che il secondo esempio crea un'istanza se il puntatore statico è nullo. Ma un programmatore non può creare l' oggetto dell'applicazione Android prima che il sistema decida di farlo.

AGGIORNARE

Un altro esempio sconcertante in cui si trovano i campi statici inizializzati 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());
    }
}

E ottieni:

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

Si noti che non è possibile spostare la dichiarazione della variabile statica di una riga in alto, il codice non verrà compilato.


3
Esempio utile; è bello sapere che esiste un tale buco. Ciò che tolgo da ciò è che si dovrebbe evitare di fare riferimento a tale variabile statica durante l'inizializzazione statica di qualsiasi classe.
ToolmakerSteve

10

Classe di applicazione:

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;
    }

}

Dichiarare l'applicazione in AndroidManifest:

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

Uso:

MyApplication.getAppContext()

1
Incline a perdite di memoria. Non dovresti mai farlo.
Dragas,

9

Stai cercando di creare un wrapper per ottenere il contesto dell'applicazione e c'è la possibilità che possa restituire il nullpuntatore " ".

Secondo la mia comprensione, immagino che sia il miglior approccio per chiamare uno dei 2 Context.getApplicationContext() o Activity.getApplication().


13
quando dovrebbe restituire null?
Bloccato il

25
Non esiste un metodo Context.getApplicationContext () statico di cui sono a conoscenza. Mi sto perdendo qualcosa?
Dalcantara,

Inoltre implemento lo stesso approccio nella mia applicazione, ma quando si chiama in SQLiteOpenHelper, restituisce il puntatore null. Qualsiasi risposta per questo tipo di situazione.
Ashutosh,

2
Questo può essere il caso se chiami SQLiteOpenHelper in un contentprovider che viene caricato prima dell'app.
Gunnar Bernstein,

5

È un buon approccio. Lo uso anche io. Vorrei solo suggerire di eseguire l'override onCreateper impostare il singleton invece di utilizzare un costruttore.

E dal momento che hai menzionato SQLiteOpenHelper: In onCreate ()puoi anche aprire il database.

Personalmente penso che la documentazione abbia sbagliato nel dire che normalmente non è necessario sottoclassare l'Applicazione . Penso che sia vero il contrario: dovresti sempre sottoclassare l'Applicazione.


3

Vorrei utilizzare il contesto dell'applicazione per ottenere un servizio di sistema nel costruttore. Ciò facilita i test e beneficia della composizione

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

}

La classe di test utilizzerà quindi il costruttore sovraccarico.

Android userebbe il costruttore predefinito.


1

Mi piace, ma suggerirei invece un singleton:

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;
    }
}

31
L'estensione di android.app.application garantisce già singleton, quindi questo non è necessario
Vincent,

8
Cosa succede se si desidera accedere da classi non di attività?
Maxrunner,

9
Non dovresti mai newl'Applicazione tu stesso (con la possibile eccezione del test unitario). Il sistema operativo lo farà. Inoltre, non dovresti avere un costruttore. Questo è ciò che onCreateserve.
Martin,

@Vincent: puoi pubblicare qualche link su questo? preferibilmente codice - sto chiedendo qui: stackoverflow.com/questions/19365797/…
Mr_and_Mrs_D

@radzio perché non dovremmo farlo nel costruttore?
Miha_x64,

1

Sto usando lo stesso approccio, suggerisco di scrivere un po 'meglio il singleton:

public static MyApp getInstance() {

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

    return instance;
}

ma non sto usando ovunque, io uso getContext()e getApplicationContext()dove posso farlo!


Quindi, per favore, scrivi un commento per spiegare perché hai annullato il voto della risposta in modo che io possa capire. L'approccio singleton è ampiamente utilizzato per ottenere un contesto valido al di fuori delle attività o dei punti di vista ...
Seraphim's

1
Non è necessario poiché il sistema operativo garantisce che l'applicazione venga istanziata esattamente una volta. Se del caso, suggerirei di impostare Singelton in onCreate ().
Martin,

1
Un buon modo thread-safe per inizializzare un singoletto pigro, ma non necessario qui.
naXa,

2
Wow, proprio quando pensavo che la gente avesse finalmente smesso di usare il doppio controllo di blocco ... cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
Søren Boisen
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.