web-dev-qa-db-fra.com

Utiliser le contexte d'application partout?

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 transmettre partout (par exemple, SQLiteOpenHelper) où le contexte est requis (et ne fuyant pas, bien sûr)?

462
yanchenko

Cette approche présente quelques problèmes potentiels, même si dans de nombreuses circonstances (comme dans votre exemple), cela fonctionnera bien.

En particulier, vous devez faire attention lorsque vous traitez avec tout ce qui concerne la GUI qui nécessite un Context. Par exemple, si vous passez le contexte de l'application dans le LayoutInflater, vous obtiendrez une exception. En règle générale, votre approche est excellente: il est recommandé d’utiliser un Activity's_ Context dans cette Activity, et le Application Context lors du passage d'un contexte sortant du cadre d'un Activity à éviter les fuites de mémoire .

En outre, en tant que alternative à votre modèle, vous pouvez utiliser le raccourci d'appel getApplicationContext() sur un objet Context (tel qu'une activité) pour obtenir le contexte d'application.

402
Reto Meier

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 Context obtenu ici, vous pouvez appeler Context.getApplicationContext () pour obtenir le contexte Application. Si vous essayez d'obtenir le contexte Application à partir d'un Activity, vous pouvez toujours appeler Activity.getApplication () qui devrait pouvoir être passé en tant que Context nécessaire pour un appel à SQLiteOpenHelper().

Globalement, votre approche ne semble pas poser de problème dans cette situation, mais lorsque vous traitez avec Context assurez-vous simplement que vous ne perdez pas de mémoire, comme décrit sur le document officiel Google Android Blog des développeurs .

28
snctln

Certaines personnes ont demandé: comment le singleton peut-il retourner un pointeur nul? Je réponds à cette question. (Je ne peux pas répondre à un commentaire car je dois poster un code.)

Il peut renvoyer 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);
    }
}

Lançons 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 null ; elles sont nulles car les variables X.xinstance et Y.yinstance étaient lu quand ils étaient nuls.

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'objet Android Application: le programmeur ne contrôle pas l'heure à laquelle il a été créé.

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

UPDATE

Un autre exemple déroutant où les champs statiques initialisés sont 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 supérieure, le code ne sera pas compilé.

12

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 le Manifeste Android:

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

Usage:

MyApplication.getAppContext()
9
toha

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

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

9
Prasanta

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

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

Personnellement, je pense que la documentation a eu tort de dire que Il n’est normalement pas nécessaire de sous-classer Application. Je pense que le contraire est vrai: vous devriez toujours sous-classe Application.

4
Martin

J'utiliserais le contexte d'application pour obtenir un service système dans le constructeur. Cela facilite les tests et profite 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.

3
Blundell

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;
    }
}
1
Franklin Peña

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() là où je peux le faire!

1
Seraphim's

Définir le contexte de manière statique provoquera la fuite de mémoire

Manière standard d’obtenir le contexte partout:

public class App extends Application {

    public static transient SoftReference<Context> contextReference;

    @Override
    public void onCreate() {
        super.onCreate();
        contextReference = new SoftReference<Context>(getApplicationContext());
    }
}

De cette façon, vous aurez un contexte n'importe où dans le code comme ceci:

App.contextReference.get();

Toute autre manière réduira les performances et provoquera une fuite de mémoire

J'espère être utile ...

0
Mr.Hosseini