¿Forma estática de obtener 'Contexto' en Android?

970

¿Hay alguna manera de obtener la Contextinstancia actual dentro de un método estático?

Estoy buscando de esa manera porque odio guardar la instancia de 'Contexto' cada vez que cambia.

Andrea Baccega
fuente
57
No guardar el contexto es una buena idea no solo porque es inconveniente, ¡sino más porque puede provocar grandes pérdidas de memoria!
Vikram Bodicherla
12
@VikramBodicherla Sí, pero las respuestas a continuación suponen que estamos hablando del contexto de la aplicación. Por lo tanto, las pérdidas de memoria no son un problema, pero el usuario solo debe usar estas soluciones donde sea el contexto correcto.
Tom
Si tiene que usar una forma estática de obtener Context, entonces podría haber una mejor manera de diseñar el código.
Anonsage
3
La documentación de Android recomienda pasar el contexto a los captadores de singletons. developer.android.com/reference/android/app/Application.html
Marco Luglio
Para preferir los singletons y el contexto pasado con getInstance () sobre el contexto estático, eche un vistazo, intenté explicar mi razonamiento aquí compatible con el código de trabajo: stackoverflow.com/a/38967293/4469112
Alessio

Respuestas:

1302

Hacer esto:

En el archivo de manifiesto de Android, declare lo siguiente.

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

</application>

Luego escribe la clase:

public class MyApplication extends Application {

    private static Context context;

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

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

Ahora llame MyApplication.getAppContext()a todas partes para obtener el contexto de su aplicación estáticamente.

Rohit Ghatol
fuente
81
¿Hay alguna desventaja en este método? Esto parece una trampa. (¿Un truco?)
jjnguy
203
La desventaja es que no hay garantía de que se haya llamado a onCreate () no estático antes de que algún código de inicialización estático intente recuperar su objeto Context. Eso significa que su código de llamada deberá estar listo para lidiar con valores nulos que de alguna manera anulan el punto de esta pregunta.
Melinda Green
8
También quizás ... ¿deberíamos declarar esta static contextvariable como volatile?
Vladimir Sorokin
14
@Tom Esto no es un caso de un miembro de datos estático que está inicialmente estáticamente. En el código dado, el miembro estático se inicializa de forma no estática en onCreate (). Incluso los datos inicializados estáticamente no son lo suficientemente buenos en este caso porque nada asegura que la inicialización estática de la clase dada ocurrirá antes de que se acceda a ella durante la inicialización estática de alguna otra clase.
Melinda Green
10
@MelindaGreen De acuerdo con la documentación para la Aplicación, se llama a onCreate () antes de que se haya creado cualquier actividad, servicio o receptor (excluyendo los proveedores de contenido). Entonces, ¿no sería segura esta solución mientras no intentes acceder a getAppContext () desde un proveedor de contenido?
Magnus W
86

La mayoría de las aplicaciones que desean un método conveniente para obtener el contexto de la aplicación crean su propia clase que se extiende android.app.Application.

GUÍA

Puede lograr esto creando primero una clase en su proyecto como la siguiente:

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

Luego, en su AndroidManifest, debe especificar el nombre de su clase en la etiqueta de AndroidManifest.xml:

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

Luego puede recuperar el contexto de la aplicación en cualquier método estático utilizando lo siguiente:

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

ADVERTENCIA

Antes de agregar algo como lo anterior a su proyecto, debe considerar lo que dice la documentación:

Normalmente no hay necesidad de subclase Aplicación. En la mayoría de las situaciones, los singletons estáticos pueden proporcionar la misma funcionalidad de una manera más modular. Si su singleton necesita un contexto global (por ejemplo, para registrar receptores de difusión), la función para recuperarlo puede recibir un Context que internamente usa Context.getApplicationContext () cuando construye el singleton por primera vez.


REFLEXIÓN

También hay otra forma de obtener el contexto de la aplicación utilizando la reflexión. La reflexión a menudo es menospreciada en Android y personalmente creo que esto no debería usarse en la producción.

Para recuperar el contexto de la aplicación, debemos invocar un método en una clase oculta ( ActivityThread ) que ha estado disponible desde la API 1:

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

Hay una clase oculta más ( AppGlobals ) que proporciona una forma de obtener el contexto de la aplicación de forma estática. Obtiene el contexto utilizando, ActivityThreadpor lo que realmente no hay diferencia entre el siguiente método y el publicado anteriormente:

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

¡Feliz codificación!

Jared Rummler
fuente
56

Asumiendo que estamos hablando de obtener el Contexto de la Aplicación, lo implementé como lo sugirió @Rohit Ghatol extendiendo la Aplicación. Lo que sucedió entonces, es que no hay garantía de que el contexto recuperado de esa manera siempre sea no nulo. En el momento en que lo necesita, generalmente es porque desea inicializar un ayudante u obtener un recurso que no puede retrasar a tiempo; manejar el caso nulo no lo ayudará. Así que entendí que básicamente estaba luchando contra la arquitectura de Android, como se indica en los documentos

Nota: Normalmente no es necesario subclasificar la Aplicación. En la mayoría de las situaciones, los singletons estáticos pueden proporcionar la misma funcionalidad de una manera más modular. Si su singleton necesita un contexto global (por ejemplo, para registrar receptores de difusión), incluya Context.getApplicationContext () como argumento de Contexto al invocar el método getInstance () de su singleton.

y explicado por Dianne Hackborn

La única razón por la que la Aplicación existe como algo de lo que se puede derivar es porque durante el desarrollo anterior a la versión 1.0, uno de nuestros desarrolladores de aplicaciones continuamente me estaba molestando por la necesidad de tener un objeto de aplicación de nivel superior del que pudieran derivar para que pudieran tener un nivel más "normal". "para ellos modelo de aplicación, y finalmente cedí. Siempre lamentaré haber cedido en ese. :)

Ella también sugiere la solución a este problema:

Si lo que desea es un estado global que se pueda compartir en diferentes partes de su aplicación, use un singleton. [...] Y esto lleva más naturalmente a cómo debe administrar estas cosas: inicializándolas a pedido.

así que lo que hice fue deshacerme de extender la Aplicación y pasar el contexto directamente al getInstance () del ayudante singleton, mientras guardaba una referencia al contexto de la aplicación en el constructor privado:

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

la persona que llama luego pasará un contexto local al ayudante:

Helper.getInstance(myCtx).doSomething();

Por lo tanto, para responder esta pregunta correctamente: hay formas de acceder al contexto de la aplicación de forma estática, pero todos deben desalentarse, y debe preferir pasar un contexto local al getInstance () del singleton.


Para cualquier persona interesada, puede leer una versión más detallada en el blog de fwd

Alessio
fuente
1
@Alessio ¿Este método no genera pérdidas de memoria
Phillip Kigenyi
2
@codephillip No entiendo de qué estás hablando. El singleton hace referencia al contexto de la aplicación recuperado de la actividad pasada, no a la actividad del host. Eso es legítimo, y no causará ninguna pérdida de memoria. Ese es el punto principal del blog que escribí. Si realmente cree que tiene razón, envíeme un código de muestra donde pueda reproducir la pérdida de memoria de la que está hablando, porque ese no es el caso.
Alessio
1
Creo que @KigenyiPhillip es correcto, y esto todavía representa una pérdida de recursos. Imagen de la tabla de referencia después de su primera llamada a getInstance(ctx). Tiene una raíz instancede tipo GC MyHelper, que tiene un campo mContextde tipo privado Context, que hace referencia al contexto de la aplicación recopilada a través del contexto pasado getInstance(). instanceNunca se establece una segunda vez, ni aclarado, por lo GC nunca coger el appcontext al que hace referencia instance. No pierde ninguna actividad, por lo que es un IMO de bajo costo.
Mark McKenna
1
@MarkMcKenna como usted dice "que tiene un campo privado mContext de tipo Context, que hace referencia al contexto de la aplicación", por lo que tiene claro que mContext es una referencia al contexto de la aplicación, no a ningún contexto. En los documentos getApplicationContext () se lee: "un contexto cuyo ciclo de vida está separado del contexto actual, que está vinculado a la vida útil del proceso en lugar del componente actual". ¿Cómo puede esto crear una pérdida de memoria? El contexto de la aplicación es GC'd solo cuando el proceso finaliza.
Alessio
1
@Alessio si acepta que una referencia al contexto de la aplicación no califica como una fuga de recursos, entonces puede simplificar esto publicando una referencia estática en thisin Application.onCreate(), lo que mejora la respuesta aceptada.
Mark McKenna
49

No, no creo que la haya. Desafortunadamente, estás atrapado llamando getApplicationContext()desde Activityo una de las otras subclases de Context. Además, esta pregunta está algo relacionada.

Erich Douglass
fuente
8
El enlace correcto al artículo: android-developers.blogspot.co.il/2009/01/…
Tal Weiss el
38

Aquí hay una forma no documentada de obtener una Aplicación (que es un Contexto) desde cualquier parte del hilo de la interfaz de usuario. Se basa en el método estático oculto ActivityThread.currentApplication(). Debería funcionar al menos en 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
}

Tenga en cuenta que es posible que este método devuelva nulo, por ejemplo, cuando llama al método fuera del hilo de la interfaz de usuario, o la aplicación no está vinculada al hilo.

Todavía es mejor usar la solución de @RohitGhatol si puede cambiar el código de la aplicación.

kennytm
fuente
1
Utilicé el método anterior KennyTM, pero a veces el método devuelve nulo. ¿Hay alguna otra alternativa a esto? Al igual que si obtenemos un valor nulo aquí, podemos recuperar el contexto desde otro lugar. En mi caso, no se llama a onCreate () de Application. Pero el método anterior se llama antes. Ayuda de Plzzz
AndroidGuy
Esto no siempre funcionará en el caso en que GC limpió todas las cosas relacionadas con la actividad.
AlexVPerl
32

Depende de para qué esté utilizando el contexto. Puedo pensar en al menos una desventaja de ese método:

Si está intentando crear un AlertDialogcon AlertDialog.Builder, el Applicationcontexto no funcionará. Creo que necesitas el contexto para el actual Activity...

gulchrider
fuente
66
Así es. Si usa el contexto de la aplicación para eso, puede ver su diálogo oculto debajo de las actividades en primer plano.
Nate
3
+1 en primer lugar. Y el posible error que aparece es No se puede iniciar la actividad ComponentInfo {com.samples / com.MyActivity}: android.view.WindowManager $ BadTokenException: No se puede agregar la ventana - el token nulo no es para una aplicación
Govind
15

Camino Kotlin :

Manifiesto:

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

Luego puede acceder a la propiedad a través de MyApplication.instance

phnmnn
fuente
11

Si está abierto a usar RoboGuice , puede inyectar el contexto en la clase que desee. Aquí hay una pequeña muestra de cómo hacerlo con RoboGuice 2.0 (beta 4 al momento de escribir esto)

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) {
        }
    }
}
usuario605331
fuente
8

He usado esto en algún momento:

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

Este es un contexto válido que utilicé para obtener servicios del sistema y funcionó.

Pero, lo usé solo en modificaciones de marco / base y no lo probé en aplicaciones de Android.

Una advertencia que debe saber: cuando se registre para recibir receptores de difusión con este contexto, no funcionará y obtendrá:

java.lang.SecurityException: dado el paquete de llamadas, Android no se está ejecutando en el proceso ProcessRecord

ungalcrys
fuente
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
        }
    }
}

y obtener contexto como

MyApp.mInstance

o

MyApp.getContext()
Khemraj
fuente
4

Puedes usar lo siguiente:

MainActivity.this.getApplicationContext();

MainActivity.java:

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

Cualquier otra clase:

public ...
    public ANY_METHOD... {
         Context c = MainActivity.ma.getApplicationContext();
Barwnikk
fuente
3
Esto solo funciona si estás dentro de una clase interna, lo cual no es el caso en el OP.
Richard J. Ross III
3
Esto funcionaría siempre que se llame ANY_METHOD después de que se crea MainActivity, pero mantener referencias estáticas a actividades casi inevitablemente introduce fugas de memoria (como ya mencionan otras respuestas a la pregunta de OP), por lo que si realmente debe mantener una referencia estática, use la aplicación solo contexto.
2013
1
Las clases internas son malvadas. Lo peor es que mucha gente hacer eso por AsyncTasks y cosas por el estilo, porque muchos tutoriales lo hacen de esa manera ...
Reinherd
4

Si no desea modificar el archivo de manifiesto, puede almacenar manualmente el contexto en una variable estática en su actividad inicial:

public class App {
    private static Context context;

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

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

Y simplemente establezca el contexto cuando comience su actividad (o actividades):

// MainActivity

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

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

    // Other stuff
}

Nota: Como todas las otras respuestas, esta es una pérdida potencial de memoria.

Sheharyar
fuente
1
¿Qué será exactamente la fuga ya que el contexto en este caso está vinculado a la aplicación? Si la aplicación muere, también lo hace todo lo demás.
TheRealChx101
3

Creo que necesitas un cuerpo para el getAppContext()método:

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

Según esta fuente , puede obtener su propio contexto extendiendo 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 para ContextWrapper

Implementación proxy de Context que simplemente delega todas sus llamadas a otro Context. Se puede subclasificar para modificar el comportamiento sin cambiar el contexto original.

BlueWizard
fuente
1
Esto es interesante. Es bueno aprender sobre ContextWrapper. Sin embargo, si necesita pasar el contexto de la aplicación a este constructor, debe obtenerlo desde algún lugar.
jk7
2

Si por alguna razón desea el contexto de la Aplicación en cualquier clase, no solo en aquellas que extienden la aplicación / actividad, tal vez para algunas clases de fábrica o auxiliares. Puede agregar el siguiente singleton a su aplicación.

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

luego inicialícelo en onCreate de su clase de aplicación con

GlobalAppContextSingleton.getInstance().initialize(this);

úsalo en cualquier lugar llamando

GlobalAppContextSingleton.getInstance().getApplicationContext()

Sin embargo, no recomiendo este enfoque para nada más que el contexto de la aplicación. Como puede causar pérdidas de memoria.

Versa
fuente
No es como si los nombres de clase / método estuvieran escritos en piedra, lo mantuvieron largo y (con suerte) descriptivo para un Q&A, lo acorté para mi propio uso.
Versa
1

Utilizo una variación del patrón de diseño Singleton para ayudarme con esto.

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

Luego llamo ApplicationContextSingleton.setContext( this );a mi actividad.onCreate () y ApplicationContextSingleton.setContext( null );en onDestroy () ;

Bamaco
fuente
Si todo lo que necesita es contexto, puede llamar a activity.getApplicationContext (); Eso se puede retener estáticamente sin tener que preocuparse por las fugas.
MinceMan
2
esto producirá pérdidas de memoria
BlueWizard
1

Acabo de lanzar un marco inspirado en jQuery para Android llamado Vapor API que tiene como objetivo simplificar el desarrollo de aplicaciones.

La $clase de fachada central mantiene un WeakReference(enlace a una impresionante publicación de blog de Java sobre esto por Ethan Nicholas) en el Activitycontexto actual que puede recuperar llamando a:

$.act()

A WeakReferencemantiene una referencia sin evitar que la recolección de basura recupere el objeto original, por lo que no debería tener problemas con las pérdidas de memoria.

La desventaja, por supuesto, es que corre el riesgo de que $.act()pueda volver nulo. Sin embargo, aún no me he encontrado con este escenario, por lo que quizás sea solo un riesgo mínimo, vale la pena mencionar.

También puede establecer el contexto manualmente si no lo está utilizando VaporActivitycomo su Activityclase:

$.act(Activity);

Además, gran parte del marco de la API de Vapor usa este contexto almacenado inherentemente, lo que puede significar que no necesita almacenarlo usted mismo si decide usar el marco. Consulte el sitio para obtener más información y muestras.

Espero que eso ayude :)

Darius
fuente
1
Aparentemente esto acaba de ser rechazado ... ¿una explicación sería buena?
Darius el
1
No rechacé esto, pero Javascript no tiene nada que ver con la pregunta en cuestión, ¡eso explicaría cualquier voto negativo que hayas tenido! Salud.
Ernani Joppert
Eso sería bastante absurdo dado que está inspirado en algunos aspectos de jQuery como una interfaz fluida y sus abstracciones ... ¡esos son principios agnósticos del lenguaje subyacente!
Darius
1
¿Entonces lo está votando porque se inspiró en la semántica API de un marco que no está en la misma plataforma? Creo que ustedes se pierden el punto de aplicar principios agnósticos de plataforma .....................................
Darius
3
Esta respuesta no tiene ninguna relación con JavaScript. Lea la respuesta antes de votar: /
BlueWizard
1

La respuesta de Rohit parece correcta. Sin embargo, tenga en cuenta que "Instant Run" de AndroidStudio depende de no tener static Contextatributos en su código, que yo sepa.

Payne
fuente
1
Tienes razón. ¡Y también dará lugar a pérdidas de memoria!
user1506104
1

en Kotlin, poner Contexto / Contexto de aplicación en objeto complementario todavía produce advertencia Do not place Android context classes in static fields; this is a memory leak (and also breaks Instant Run)

o si usas algo como esto:

    companion object {
        lateinit var instance: MyApp
    }

Simplemente está engañando a la pelusa para no descubrir la pérdida de memoria, la instancia de la aplicación aún puede producir pérdida de memoria, ya que la clase de aplicación y su descendiente es un contexto.

Alternativamente, puede usar la interfaz funcional o las propiedades funcionales para ayudarlo a obtener el contexto de su aplicación.

Simplemente cree una clase de objeto:

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

o podría usarlo de manera más segura usando el tipo anulable:

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

y en tu clase de aplicación agrega esta línea:


class MyApp: Application() {

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

y en tu manifiesto declara el nombre de la aplicación a . MyApp


    <application
            android:name=".MyApp"

Cuando quieras obtener el contexto simplemente llama:

CoreHelper.contextGetter()

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

Espero que ayude.

Hayi Nukman
fuente
¿Esta clase de objeto de corehelper se inicializará y se puede usar a través de actividades en una etapa posterior? Lo siento, soy nuevo en Kotlin
Dr. ANDRO
sí exactamente.
Hayi Nukman
-1

Intenta algo como esto

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
fuente