Manière statique d'obtenir le «contexte» dans Android?

970

Existe-t-il un moyen d'obtenir l' Contextinstance actuelle dans une méthode statique?

Je cherche de cette façon parce que je déteste enregistrer l'instance "Context" à chaque fois qu'elle change.

Andrea Baccega
la source
57
Ne pas enregistrer le contexte est une bonne idée, non seulement parce qu'il est gênant, mais surtout parce qu'il peut entraîner d'énormes fuites de mémoire!
Vikram Bodicherla
12
@VikramBodicherla Oui, mais les réponses ci-dessous supposent que nous parlons du contexte de l'application. Ainsi, les fuites de mémoire ne sont pas un problème, mais l'utilisateur ne doit utiliser ces solutions que lorsque c'est le bon contexte à utiliser.
Tom
Si vous devez utiliser un moyen statique pour obtenir Context, alors il pourrait y avoir un meilleur moyen de concevoir le code.
Anonsage
3
La documentation Android recommande de transmettre le contexte aux getters de singletons. developer.android.com/reference/android/app/Application.html
Marco Luglio
Pour préférer les singletons et le contexte passés avec getInstance () au contexte statique, veuillez jeter un œil, j'ai essayé d'expliquer mon raisonnement ici pris en charge avec le code de travail: stackoverflow.com/a/38967293/4469112
Alessio

Réponses:

1302

Faites ceci:

Dans le fichier manifeste Android, déclarez ce qui suit.

<application android:name="com.xyz.MyApplication">

</application>

Écrivez ensuite la classe:

public class MyApplication extends Application {

    private static Context context;

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

    public static Context getAppContext() {
        return MyApplication.context;
    }
}

Maintenant, appelez partout MyApplication.getAppContext()pour obtenir le contexte de votre application de manière statique.

Rohit Ghatol
la source
81
Y a-t-il un inconvénient à cette méthode? Cela ressemble à de la triche. (Un hack?)
jjnguy
203
L'inconvénient est qu'il n'y a aucune garantie que le onCreate () non statique aura été appelé avant qu'un code d'initialisation statique essaie de récupérer votre objet Context. Cela signifie que votre code appelant devra être prêt à traiter les valeurs nulles, ce qui en quelque sorte défait tout le point de cette question.
Melinda Green
8
Peut-être aussi .. devrions-nous déclarer cette static contextvariable comme volatile?
Vladimir Sorokin
14
@Tom Il ne s'agit pas d'un cas où un membre de données statiques est initialement statique. Dans le code donné, le membre statique est initialisé de manière non statique dans onCreate (). Même les données initialisées statiquement ne sont pas suffisantes dans ce cas, car rien ne garantit que l'initialisation statique de la classe donnée se produira avant d'être accessible lors de l'initialisation statique d'une autre classe.
Melinda Green
10
@MelindaGreen Selon la documentation d'Application, onCreate () est appelé avant la création d'une activité, d'un service ou d'un récepteur (à l'exclusion des fournisseurs de contenu). Cette solution ne serait-elle donc pas sûre tant que vous n'essayez pas d'accéder à getAppContext () à partir d'un fournisseur de contenu?
Magnus W
86

La majorité des applications qui souhaitent une méthode pratique pour obtenir le contexte d'application créent leur propre classe qui s'étend android.app.Application.

GUIDER

Vous pouvez accomplir cela en créant d'abord une classe dans votre projet comme suit:

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

public class App extends Application {

    private static Application sApplication;

    public static Application getApplication() {
        return sApplication;
    }

    public static Context getContext() {
        return getApplication().getApplicationContext();
    }

    @Override
    public void onCreate() {
        super.onCreate();
        sApplication = this;
    }
}

Ensuite, dans votre AndroidManifest, vous devez spécifier le nom de votre classe dans la balise AndroidManifest.xml:

<application 
    ...
    android:name="com.example.App" >
    ...
</application>

Vous pouvez ensuite récupérer le contexte d'application dans n'importe quelle méthode statique à l'aide de ce qui suit:

public static void someMethod() {
    Context context = App.getContext();
}

ATTENTION

Avant d'ajouter quelque chose comme ce qui précède à votre projet, vous devez considérer ce que dit la documentation:

Il n'est normalement pas nécessaire de sous-classer Application. Dans la plupart des situations, les singletons statiques peuvent fournir les mêmes fonctionnalités de manière plus modulaire. Si votre singleton a besoin d'un contexte global (par exemple pour enregistrer des récepteurs de diffusion), la fonction pour le récupérer peut recevoir un contexte qui utilise en interne Context.getApplicationContext () lors de la première construction du singleton.


RÉFLEXION

Il existe également une autre façon d'obtenir le contexte de l'application à l'aide de la réflexion. La réflexion est souvent méprisée dans Android et je pense personnellement que cela ne devrait pas être utilisé en production.

Pour récupérer le contexte de l'application, nous devons appeler une méthode sur une classe cachée ( ActivityThread ) qui est disponible depuis l'API 1:

public static Application getApplicationUsingReflection() throws Exception {
    return (Application) Class.forName("android.app.ActivityThread")
            .getMethod("currentApplication").invoke(null, (Object[]) null);
}

Il existe une autre classe cachée ( AppGlobals ) qui fournit un moyen d'obtenir le contexte d'application de manière statique. Il obtient le contexte en utilisant ActivityThreaddonc il n'y a vraiment aucune différence entre la méthode suivante et celle publiée ci-dessus:

public static Application getApplicationUsingReflection() throws Exception {
    return (Application) Class.forName("android.app.AppGlobals")
            .getMethod("getInitialApplication").invoke(null, (Object[]) null);
} 

Bon codage!

Jared Rummler
la source
56

En supposant que nous parlons d'obtenir le contexte d'application, je l'ai implémenté comme suggéré par @Rohit Ghatol étendant l'application. Ce qui s'est passé alors, c'est qu'il n'y a aucune garantie que le contexte récupéré de cette manière sera toujours non nul. Au moment où vous en avez besoin, c'est généralement parce que vous voulez initialiser un assistant, ou obtenir une ressource, que vous ne pouvez pas retarder dans le temps; la gestion du cas nul ne vous aidera pas. J'ai donc compris que je me battais essentiellement contre l'architecture Android, comme indiqué dans la documentation

Remarque: Il n'est normalement pas nécessaire de sous-classer Application. Dans la plupart des situations, les singletons statiques peuvent fournir les mêmes fonctionnalités de manière plus modulaire. Si votre singleton a besoin d'un contexte global (par exemple pour enregistrer des récepteurs de diffusion), incluez Context.getApplicationContext () comme argument Context lors de l'appel de la méthode getInstance () de votre singleton.

et expliqué par Dianne Hackborn

La seule raison pour laquelle Application existe comme quelque chose dont vous pouvez dériver est parce que pendant le développement pré-1.0, l'un de nos développeurs d'applications me continuait à me déranger d'avoir besoin d'avoir un objet d'application de niveau supérieur dont ils pouvaient dériver afin d'avoir une image plus "normale". "Pour eux, le modèle d'application, et j'ai finalement cédé. Je regretterai à jamais de céder sur celui-là. :)

Elle propose également la solution à ce problème:

Si vous voulez un état global pouvant être partagé entre différentes parties de votre application, utilisez un singleton. [...] Et cela mène plus naturellement à la façon dont vous devriez gérer ces choses - en les initialisant à la demande.

donc ce que j'ai fait était de me débarrasser de l'extension d'Application et de passer le contexte directement à getInstance () de l'assistant singleton, tout en enregistrant une référence au contexte d'application dans le constructeur privé:

private static MyHelper instance;
private final Context mContext;    

private MyHelper(@NonNull Context context) {
    mContext = context.getApplicationContext();
}

public static MyHelper getInstance(@NonNull Context context) {
    synchronized(MyHelper.class) {
        if (instance == null) {
            instance = new MyHelper(context);
        }
        return instance;
    }
}

l'appelant transmettra ensuite un contexte local à l'assistant:

Helper.getInstance(myCtx).doSomething();

Donc, pour répondre correctement à cette question: il existe des moyens d'accéder statiquement au contexte d'application, mais ils doivent tous être découragés, et vous devriez préférer passer un contexte local à getInstance () du singleton.


Pour toute personne intéressée, vous pouvez lire une version plus détaillée sur le blog fwd

Alessio
la source
1
@Alessio Cette méthode ne conduit-elle pas à des fuites de mémoire
Phillip Kigenyi
2
@codephillip Je ne comprends pas de quoi vous parlez. Le singleton fait référence au contexte d'application récupéré de l'activité transmise, pas à l'activité hôte. C'est légitime et cela ne causera aucune fuite de mémoire. C'est le point principal du blog que j'ai écrit. Si vous pensez vraiment que vous avez raison, envoyez-moi un exemple de code où je peux reproduire la fuite de mémoire dont vous parlez, car ce n'est pas le cas.
Alessio
1
Je pense que @KigenyiPhillip est correct, et cela représente toujours une fuite de ressources. Imaginez le tableau de référence après votre premier appel à getInstance(ctx). Vous avez une racine GC instancede type MyHelper, qui possède un champ privé mContextde type Context, qui fait référence au contexte d'application collecté via le contexte transmis à getInstance(). instancen'est jamais défini une deuxième fois, ni effacé, donc GC n'attrapera jamais le contexte d'application référencé par instance. Vous ne fuyez aucune activité, c'est donc l'OMI à faible coût.
Mark McKenna
1
@MarkMcKenna comme vous le dites "qui a un champ privé mContext de type Context, qui fait référence au contexte d'application", il est donc clair pour vous que mContext est une référence au contexte d'application, pas à n'importe quel contexte. Dans les documents getApplicationContext (), vous lisez: "un contexte dont le cycle de vie est distinct du contexte actuel, qui est lié à la durée de vie du processus plutôt qu'au composant actuel". Comment cela peut-il créer une fuite de mémoire? Le contexte d'application n'est GC'd que lorsque le processus se termine.
Alessio
1
@Alessio si vous acceptez qu'une référence au contexte d'application ne constitue pas une fuite de ressources, vous pouvez simplifier cela en affichant une référence statique à thisin Application.onCreate(), ce qui améliore la réponse acceptée.
Mark McKenna
49

Non, je ne pense pas. Malheureusement, vous êtes bloqué getApplicationContext()depuis Activityou l'une des autres sous-classes de Context. En outre, cette question est quelque peu liée.

Erich Douglass
la source
8
Le lien droit vers l'article: android-developers.blogspot.co.il/2009/01/…
Tal Weiss
38

Voici une façon non documentée d'obtenir une application (qui est un contexte) de n'importe où dans le thread d'interface utilisateur. Il repose sur la méthode statique cachée ActivityThread.currentApplication(). Cela devrait fonctionner au moins sur Android 4.x.

try {
    final Class<?> activityThreadClass =
            Class.forName("android.app.ActivityThread");
    final Method method = activityThreadClass.getMethod("currentApplication");
    return (Application) method.invoke(null, (Object[]) null);
} catch (final ClassNotFoundException e) {
    // handle exception
} catch (final NoSuchMethodException e) {
    // handle exception
} catch (final IllegalArgumentException e) {
    // handle exception
} catch (final IllegalAccessException e) {
    // handle exception
} catch (final InvocationTargetException e) {
    // handle exception
}

Notez qu'il est possible que cette méthode retourne null, par exemple lorsque vous appelez la méthode en dehors du thread d'interface utilisateur, ou que l'application n'est pas liée au thread.

Il est toujours préférable d'utiliser la solution de @RohitGhatol si vous pouvez changer le code d'application.

kennytm
la source
1
J'ai utilisé la méthode KennyTM ci-dessus, mais parfois la méthode renvoie null. Y a-t-il une autre alternative à cela? Comme si nous obtenons un null ici, nous pouvons récupérer le contexte ailleurs. Dans mon cas, onCreate () of Application n'est pas appelé. Mais la méthode ci-dessus est appelée avant elle. Aide de Plzzz
AndroidGuy
Cela ne fonctionnera pas toujours dans le cas où le GC a nettoyé tous les éléments liés à l'activité.
AlexVPerl
32

Cela dépend de ce pour quoi vous utilisez le contexte. Je peux penser à au moins un inconvénient de cette méthode:

Si vous essayez de créer un AlertDialogavec AlertDialog.Builder, le Applicationcontexte ne fonctionnera pas. Je crois que vous avez besoin du contexte du courant Activity...

gulchrider
la source
6
C'est vrai. Si vous utilisez le contexte d'application pour cela, vous pouvez voir votre boîte de dialogue cachée sous les activités de premier plan.
Nate
3
+1 tout d'abord. Et l'erreur possible qui vient est impossible de démarrer l'activité ComponentInfo {com.samples / com.MyActivity}: android.view.WindowManager $ BadTokenException: impossible d'ajouter une fenêtre - le token null n'est pas pour une application
Govind
15

Façon Kotlin :

Manifeste:

<application android:name="MyApplication">

</application>

MyApplication.kt

class MyApplication: Application() {

    override fun onCreate() {
        super.onCreate()
        instance = this
    }

    companion object {
        lateinit var instance: MyApplication
            private set
    }
}

Vous pouvez ensuite accéder à la propriété via MyApplication.instance

phnmnn
la source
11

Si vous êtes prêt à utiliser RoboGuice , vous pouvez injecter le contexte dans la classe de votre choix . Voici un petit échantillon de la façon de le faire avec RoboGuice 2.0 (beta 4 au moment de la rédaction de cet article)

import android.content.Context;
import android.os.Build;
import roboguice.inject.ContextSingleton;

import javax.inject.Inject;

@ContextSingleton
public class DataManager {
    @Inject
    public DataManager(Context context) {
            Properties properties = new Properties();
            properties.load(context.getResources().getAssets().open("data.properties"));
        } catch (IOException e) {
        }
    }
}
user605331
la source
8

Je l'ai utilisé à un moment donné:

ActivityThread at = ActivityThread.systemMain();
Context context = at.getSystemContext();

C'est un contexte valide que j'ai utilisé pour obtenir des services système et travailler.

Mais, je ne l'ai utilisé que dans les modifications de framework / base et je ne l'ai pas essayé dans les applications Android.

Un avertissement que vous devez savoir: lors de votre inscription à des récepteurs de diffusion dans ce contexte, cela ne fonctionnera pas et vous obtiendrez:

java.lang.SecurityException: Étant donné que le package d'appelant android n'est pas en cours d'exécution dans le processus ProcessRecord

ungalcrys
la source
7

Kotlin

open class MyApp : Application() {
    override fun onCreate() {
        super.onCreate()
        mInstance = this
    }

    companion object {
        lateinit var mInstance: MyApp
        fun getContext(): Context? {
            return mInstance.applicationContext
        }
    }
}

et obtenez le contexte comme

MyApp.mInstance

ou

MyApp.getContext()
Khemraj
la source
4

Vous pouvez utiliser les éléments suivants:

MainActivity.this.getApplicationContext();

MainActivity.java:

...
public class MainActivity ... {
    static MainActivity ma;
...
    public void onCreate(Bundle b) {
         super...
         ma=this;
         ...

Toute autre classe:

public ...
    public ANY_METHOD... {
         Context c = MainActivity.ma.getApplicationContext();
barwnikk
la source
3
Cela ne fonctionne que si vous êtes dans une classe interne, ce qui n'est guère le cas dans l'OP.
Richard J. Ross III
3
Cela fonctionnerait tant que ANY_METHOD est appelé après la création de MainActivity, mais garder les références statiques aux activités introduit presque inévitablement des fuites de mémoire (comme d'autres réponses à la question de OP le mentionnent déjà), donc si vous devez vraiment garder une référence statique, utilisez l'application contexte uniquement.
handtwerk
1
Les classes intérieures sont mauvaises. Le pire est que beaucoup de gens le font pour AsyncTasks et des choses comme ça, parce que de nombreux tutoriels le font de cette façon ...
Reinherd
4

Si vous ne souhaitez pas modifier le fichier manifeste, vous pouvez stocker manuellement le contexte dans une variable statique dans votre activité initiale:

public class App {
    private static Context context;

    public static void setContext(Context cntxt) {
        context = cntxt;
    }

    public static Context getContext() {
        return context;
    }
}

Et définissez simplement le contexte lorsque votre activité (ou vos activités) commence:

// MainActivity

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    // Set Context
    App.setContext(getApplicationContext());

    // Other stuff
}

Remarque: comme toutes les autres réponses, il s'agit d'une fuite de mémoire potentielle.

Sheharyar
la source
1
Que fuira-t-il exactement puisque le contexte dans ce cas est lié à l'application? Si l'application meurt, il en va de même pour tout le reste.
TheRealChx101
3

Je pense que vous avez besoin d'un corps pour la getAppContext()méthode:

public static Context getAppContext()
   return MyApplication.context; 
Kognos
la source
3

Selon cette source, vous pouvez obtenir votre propre contexte en étendant ContextWrapper

public class SomeClass extends ContextWrapper {

    public SomeClass(Context base) {
      super(base);
    }

    public void someMethod() {
        // notice how I can use "this" for Context
        // this works because this class has it's own Context just like an Activity or Service
        startActivity(this, SomeRealActivity.class);

        //would require context too
        File cacheDir = getCacheDir();
    }
}

JavaDoc pour ContextWrapper

Implémentation proxy de Context qui délègue simplement tous ses appels à un autre Context. Peut être sous-classé pour modifier le comportement sans changer le contexte d'origine.

BlueWizard
la source
1
C'est intéressant. Bon pour en savoir plus sur ContextWrapper. Cependant, si vous devez passer dans le contexte d'application à ce constructeur, vous devez toujours l'obtenir quelque part.
jk7
2

Si, pour une raison quelconque, vous voulez le contexte d'application dans n'importe quelle classe, pas seulement ceux qui étendent l'application / l'activité, peut-être pour certaines classes d'usine ou d'assistance. Vous pouvez ajouter le singleton suivant à votre application.

public class GlobalAppContextSingleton {
    private static GlobalAppContextSingleton mInstance;
    private Context context;

    public static GlobalAppContextSingleton getInstance() {
        if (mInstance == null) mInstance = getSync();
        return mInstance;
    }

    private static synchronized GlobalAppContextSingleton getSync() {
        if (mInstance == null) mInstance = 
                new GlobalAppContextSingleton();
        return mInstance;
    }

    public void initialize(Context context) {
        this.context = context;
    }

    public Context getApplicationContext() {
        return context;
    }
}

puis l'initialiser dans onCreate de votre classe d'application avec

GlobalAppContextSingleton.getInstance().initialize(this);

utilisez-le n'importe où en appelant

GlobalAppContextSingleton.getInstance().getApplicationContext()

Je ne recommande cependant pas cette approche à autre chose qu'au contexte d'application. Comme cela peut provoquer des fuites de mémoire.

Versa
la source
Ce n'est pas comme si les noms de classe / méthode étaient gravés dans la pierre, les gardaient longtemps et (espérons-le) descriptifs pour une Q&R, les raccourcissaient pour mon propre usage.
Versa
1

J'utilise une variation du modèle de conception Singleton pour m'aider avec cela.

import android.app.Activity;
import android.content.Context;

public class ApplicationContextSingleton {
    private static Activity gContext;

    public static void setContext( Activity activity) {
        gContext = activity;
    }

    public static Activity getActivity() {
        return gContext;
    }

    public static Context getContext() {
        return gContext;
    }
}

Je demande alors ApplicationContextSingleton.setContext( this );à mon activity.onCreate () et ApplicationContextSingleton.setContext( null );dans OnDestroy () ;

Bamaco
la source
Si vous n'avez besoin que de contexte, vous pouvez appeler activity.getApplicationContext (); Cela peut être maintenu statiquement sans avoir à se soucier des fuites.
MinceMan
2
cela produira des fuites de mémoire
BlueWizard
1

Je viens de publier un framework inspiré de jQuery pour Android appelé Vapor API qui vise à simplifier le développement d'applications.

La $classe de façade centrale maintient un WeakReference(lien vers un impressionnant article de blog Java à ce sujet par Ethan Nicholas) sur le Activitycontexte actuel que vous pouvez récupérer en appelant:

$.act()

A WeakReferenceconserve une référence sans empêcher la récupération de place de récupérer l'objet d'origine, vous ne devriez donc pas avoir de problème avec les fuites de mémoire.

L'inconvénient est bien sûr que vous courez le risque qui $.act()pourrait retourner nul. Je n'ai pas encore rencontré ce scénario, donc c'est peut-être juste un risque minimal, qui mérite d'être mentionné.

Vous pouvez également définir le contexte manuellement si vous n'utilisez pas VaporActivityvotre Activityclasse:

$.act(Activity);

En outre, une grande partie du cadre de l' API Vapor utilise ce contexte stocké de manière inhérente, ce qui pourrait signifier que vous n'avez pas besoin de le stocker vous-même du tout si vous décidez d'utiliser le cadre. Consultez le site pour plus d'informations et d'échantillons.

J'espère que ça aide :)

Darius
la source
1
Apparemment, cela vient d'être voté ... une explication serait bien !?
Darius
1
Je n'ai pas dévalué cela, mais Javascript n'a rien à voir avec la question à portée de main, cela expliquerait les votes négatifs que vous pourriez avoir! À votre santé.
Ernani Joppert du
Ce serait assez insensé étant donné qu'il est inspiré par certains aspects de jQuery comme une interface fluide et ses abstractions .. ce sont des principes agnostiques du langage sous-jacent!
Darius
1
Vous le votez donc parce qu'il a été inspiré par la sémantique API d'un framework qui n'est pas sur la même plateforme?! Je pense que vous manquez le point d'appliquer des principes de plate-forme agnostique .....................................
Darius
3
cette réponse est totalement indépendante de JavaScript. Lisez la réponse avant de downvote: /
BlueWizard
1

La réponse de Rohit semble correcte. Cependant, sachez que "Instant Run" d'AndroidStudio dépend de ne pas avoir d' static Contextattributs dans votre code, pour autant que je sache.

payne
la source
1
Tu as raison. Et cela entraînera également des fuites de mémoire!
user1506104
1

dans Kotlin, mettre Context / App Context dans un objet compagnon produit toujours un avertissement Do not place Android context classes in static fields; this is a memory leak (and also breaks Instant Run)

ou si vous utilisez quelque chose comme ça:

    companion object {
        lateinit var instance: MyApp
    }

C'est simplement duper les peluches pour ne pas découvrir la fuite de mémoire, l'instance App peut toujours produire une fuite de mémoire, car la classe Application et son descendant est un contexte.

Vous pouvez également utiliser une interface fonctionnelle ou des propriétés fonctionnelles pour vous aider à obtenir le contexte de votre application.

Créez simplement une classe d'objets:

object CoreHelper {
    lateinit var contextGetter: () -> Context
}

ou vous pouvez l'utiliser plus en toute sécurité en utilisant le type nullable:

object CoreHelper {
    var contextGetter: (() -> Context)? = null
}

et dans votre classe App, ajoutez cette ligne:


class MyApp: Application() {

    override fun onCreate() {
        super.onCreate()
        CoreHelper.contextGetter = {
            this
        }
    }
}

et dans votre manifeste, déclarez le nom de l'application . MyApp


    <application
            android:name=".MyApp"

Lorsque vous voulez obtenir le contexte, appelez simplement:

CoreHelper.contextGetter()

// or if you use the nullable version
CoreHelper.contextGetter?.invoke()

J'espère que cela vous aidera.

Hayi Nukman
la source
La classe d'objets de ce corehelper sera initialisée et pourra être utilisée à travers des activités à un stade ultérieur? Désolé, je suis nouveau sur kotlin
Dr. aNdRO
Oui, exactement.
Hayi Nukman
-1

Essayez quelque chose comme ça

import androidx.appcompat.app.AppCompatActivity;  
import android.content.Context; 
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {
    private static Context context;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        context = getApplicationContext();
    }

    public static void getContext(View view){
        Toast.makeText(context, "Got my context!",
                    Toast.LENGTH_LONG).show();    
    }
}
chandra shekar
la source