Vous utilisez le contexte d'application partout?

476

Dans une application Android, y a-t-il un problème avec l'approche suivante:

public class MyApp extends android.app.Application {

    private static MyApp instance;

    public MyApp() {
        instance = this;
    }

    public static Context getContext() {
        return instance;
    }

}

et le passer partout (par exemple SQLiteOpenHelper) où le contexte est requis (et non pas une fuite bien sûr)?

yanchenko
la source
23
Juste pour élaborer d' autres pour la mise en œuvre, vous pouvez alors modifier le <application>nœud de votre fichier AndroidManifest.xml pour inclure la définition d'attribut suivant: android:name="MyApp". MyApp doit se trouver sous le même package que vos références de manifeste.
Matt Huggins
6
IMPRESSIONNANT moyen de contourner le problème de la fourniture d'un contexte à SQLiteOpenHelper !! J'ai implémenté un "SQLiteManager" singleton et j'ai été bloqué sur "comment le F puis-je obtenir un contexte pour le singleton?"
Quelqu'un quelque part
8
Juste pour que vous sachiez que vous retournez votre application par l'une de ses super interfaces, donc si vous fournissez des méthodes supplémentaires dans MyApp, vous ne pourrez pas les utiliser. Votre getContext () devrait à la place avoir un type de retour de MyApp, et de cette façon, vous pouvez utiliser des méthodes ajoutées plus tard, ainsi que toutes les méthodes de ContextWrapper et Context.
5
Voir aussi goo.gl/uKcFn - c'est une autre réponse liée à un message similaire. Mieux vaut définir la variable statique dans onCreate et non c'tor.
AlikElzin-kilaka
1
@ChuongPham Si le framework a tué votre application, il n'y aura rien qui accède au contexte nul ...
Kevin Krumwiede

Réponses:

413

Il y a quelques problèmes potentiels avec cette approche, bien que dans de nombreuses circonstances (comme votre exemple), cela fonctionnera bien.

En particulier, vous devez être prudent lorsque vous traitez avec tout ce qui concerne le GUIqui nécessite un Context. Par exemple, si vous passez le contexte d'application dans le, LayoutInflatervous obtiendrez une exception. De manière générale, votre approche est excellente: c'est une bonne pratique d'utiliser un Activity's Contextdedans Activity, et Application Contextquand vous passez un contexte au-delà de la portée d'un Activitypour éviter les fuites de mémoire .

En outre, comme alternative à votre modèle, vous pouvez utiliser le raccourci pour appeler getApplicationContext()un Contextobjet (tel qu'une activité) pour obtenir le contexte d'application.

Reto Meier
la source
22
Merci pour une réponse inspirante. Je pense que je vais utiliser cette approche uniquement pour la couche de persistance (car je ne veux pas aller avec des fournisseurs de contenu). Vous vous demandez quelle était la motivation derrière la conception de SQLiteOpenHelper d'une manière qui attend qu'un contexte soit fourni au lieu de l'acquérir à partir de l'application elle-même. PS Et votre livre est génial!
yanchenko
7
Utiliser le contexte de l'application avec LayoutInflatorvient de fonctionner pour moi. Doit avoir été changé au cours des trois dernières années.
Jacob Phillips
5
@JacobPhillips L'utilisation de LayoutInflator sans contexte d'activité manquera le style de cette activité. Cela fonctionnerait donc dans un sens, mais pas dans un autre.
Mark
1
@MarkCarter Voulez-vous dire que l'utilisation du contexte d'application manquera le style de l'activité?
Jacob Phillips
1
@JacobPhillips oui, le contexte d'application ne peut pas avoir le style car chaque activité peut être stylée d'une manière différente.
Mark
28

D'après mon expérience, cette approche ne devrait pas être nécessaire. Si vous avez besoin du contexte pour quoi que ce soit, vous pouvez généralement l'obtenir via un appel à View.getContext () et en utilisant le Contextobtenu, vous pouvez appeler Context.getApplicationContext () pour obtenir le Applicationcontexte. Si vous essayez d'obtenir le Applicationcontexte à partir d'un, Activityvous pouvez toujours appeler Activity.getApplication () qui devrait pouvoir être transmis comme Contextnécessaire pour un appel à SQLiteOpenHelper().

Dans l'ensemble, il ne semble pas y avoir de problème avec votre approche pour cette situation, mais lorsque vous traitez, Contextassurez-vous de ne pas laisser fuir la mémoire, comme décrit sur le blog officiel des développeurs Android de Google .

snctln
la source
13

Certaines personnes ont demandé: comment le singleton peut-il renvoyer un pointeur nul? Je réponds à cette question. (Je ne peux pas répondre dans un commentaire car j'ai besoin de poster du code.)

Il peut retourner null entre deux événements: (1) la classe est chargée et (2) l'objet de cette classe est créé. Voici un exemple:

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

Exécutons le code:

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

La deuxième ligne montre que Y.xinstance et X.yinstance sont nuls ; ils sont nuls car les variables X.xinstance et Y.yinstance ont été lues quand elles étaient nulles.

Cela peut-il être corrigé? Oui,

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

et ce code ne montre aucune anomalie:

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

MAIS ce n'est pas une option pour l' Applicationobjet Android : le programmeur ne contrôle pas l'heure de sa création.

Encore une fois: la différence entre le premier exemple et le second est que le deuxième exemple crée une instance si le pointeur statique est nul. Mais un programmeur ne peut pas créer l' objet d'application Android avant que le système ne décide de le faire.

MISE À JOUR

Un autre exemple déroutant où se trouvent des champs statiques initialisés 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());
    }
}

Et vous obtenez:

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

Notez que vous ne pouvez pas déplacer la déclaration de variable statique d'une ligne vers le haut, le code ne sera pas compilé.

18446744073709551615
la source
3
Exemple utile; il est bon de savoir qu'il existe un tel trou. Ce que je retiens de cela, c'est qu'il faut éviter de se référer à une telle variable statique lors de l'initialisation statique d'une classe.
ToolmakerSteve
10

Classe d'application:

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

}

Déclarez l'application dans AndroidManifest:

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

Usage:

MyApplication.getAppContext()
toha
la source
1
Sujet aux fuites de mémoire. Tu ne devrais jamais faire ça.
Dragas
9

Vous essayez de créer un wrapper pour obtenir le contexte d'application et il est possible qu'il renvoie un nullpointeur " ".

Selon ma compréhension, je suppose que sa meilleure approche pour appeler l'un des 2 Context.getApplicationContext() ou Activity.getApplication().

Prasanta
la source
13
quand doit-il retourner nul?
Stuck
25
Il n'y a pas de méthode statique Context.getApplicationContext () à ma connaissance. Suis-je en train de manquer quelque chose?
dalcantara
J'implémente également la même approche dans mon application, mais lors de l'appel dans SQLiteOpenHelper, il retourne le pointeur nul. N'importe quelle réponse pour ce genre de situation.
ashutosh
2
Cela peut être le cas si vous appelez SQLiteOpenHelper dans un fournisseur de contenu qui est chargé avant l'application.
Gunnar Bernstein
5

C'est une bonne approche. Je l'utilise moi aussi. Je suggérerais simplement de remplacer onCreatepour définir le singleton au lieu d'utiliser un constructeur.

Et puisque vous avez mentionné SQLiteOpenHelper:onCreate () vous pouvez également ouvrir la base de données.

Personnellement, je pense que la documentation s'est trompée en disant qu'il n'y a normalement pas besoin de sous-classer Application . Je pense que le contraire est vrai: vous devez toujours sous-classe Application.

Martin
la source
3

J'utiliserais le contexte d'application pour obtenir un service système dans le constructeur. Cela facilite les tests et bénéficie de la composition

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 de test utiliserait alors le constructeur surchargé.

Android utiliserait le constructeur par défaut.

Blundell
la source
1

J'aime ça, mais je suggérerais plutôt 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;
    }
}
Franklin Peña
la source
31
L'extension de android.app.application garantit déjà singleton, donc cela n'est pas nécessaire
Vincent
8
Et si vous voulez avoir accès aux cours hors activité?
Maxrunner
9
Vous ne devez jamais newappliquer l'application vous-même (à l'exception peut-être des tests unitaires). Le système d'exploitation le fera. Vous ne devez pas non plus avoir de constructeur. C'est pour ça onCreate.
Martin
@Vincent: pouvez-vous publier un lien à ce sujet? code de préférence - je demande ici: stackoverflow.com/questions/19365797/…
Mr_and_Mrs_D
@radzio pourquoi nous ne devrions pas le faire en constructeur?
Miha_x64
1

J'utilise la même approche, je suggère d'écrire un peu mieux le singleton:

public static MyApp getInstance() {

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

    return instance;
}

mais je n'utilise pas partout, j'utilise getContext()et getApplicationContext()où je peux le faire!

Seraphim's
la source
Alors, veuillez écrire un commentaire pour expliquer pourquoi vous avez rejeté la réponse afin que je puisse comprendre. L'approche singleton est largement utilisée pour obtenir un contexte valide en dehors des activités ou des vues du corps ...
Seraphim's
1
Pas besoin car le système d'exploitation garantit que l'application est instanciée exactement une fois. Le cas échéant, je suggère de définir le Singelton dans onCreate ().
Martin
1
Un bon moyen sans fil pour initialiser paresseusement un singleton, mais pas nécessaire ici.
naXa
2
Wow, juste au moment où je pensais que les gens avaient finalement cessé d'utiliser le verrouillage à double vérification ... cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
Søren Boisen