¿Usar el contexto de la aplicación en todas partes?

476

En una aplicación de Android, ¿hay algún problema con el siguiente enfoque:

public class MyApp extends android.app.Application {

    private static MyApp instance;

    public MyApp() {
        instance = this;
    }

    public static Context getContext() {
        return instance;
    }

}

y pasarlo a todas partes (por ejemplo, SQLiteOpenHelper) donde se requiere contexto (y no se filtra, por supuesto)?

Yanchenko
fuente
23
Sólo para elaborar para otros aplicación de la presente, a continuación, puede modificar el <application>nodo del archivo AndroidManifest.xml incluir la siguiente definición de atributo: android:name="MyApp". MyApp debe estar bajo el mismo paquete que sus referencias de manifiesto.
Matt Huggins
66
¡Una manera INCREÍBLE de sortear el problema de proporcionar un contexto al SQLiteOpenHelper! Implementé un singleton "SQLiteManager" y me quedé atrapado en "¿cómo puedo obtener un contexto para el singleton?"
Alguien en algún lugar
8
Solo para que sepa que está devolviendo su aplicación a través de una de sus súper interfaces, de modo que si proporciona métodos adicionales dentro de MyApp, no podrá usarlos. Su getContext () debería tener un tipo de retorno de MyApp, y de esa manera puede usar métodos agregados más tarde, así como todos los métodos en ContextWrapper y Context.
55
Ver también goo.gl/uKcFn : es otra respuesta relacionada con publicaciones similares. Mejor establecer la variable estática en onCreate y no c'tor.
AlikElzin-kilaka
1
@ChuongPham Si el marco ha matado su aplicación, no habrá nada accediendo al contexto nulo ...
Kevin Krumwiede

Respuestas:

413

Hay un par de problemas potenciales con este enfoque, aunque en muchas circunstancias (como su ejemplo) funcionará bien.

En particular, debe tener cuidado al tratar con cualquier cosa que se ocupe de lo GUIque requiere a Context. Por ejemplo, si pasa el contexto de la aplicación LayoutInflater, obtendrá una excepción. En términos generales, su enfoque es excelente: es una buena práctica usar un Activity's Contextdentro de eso Activityy Application Contextal pasar un contexto más allá del alcance de un Activitypara evitar pérdidas de memoria .

Además, como alternativa a su patrón, puede usar el atajo de invocar getApplicationContext()un Contextobjeto (como una Actividad) para obtener el Contexto de la aplicación.

Reto Meier
fuente
22
Gracias por una respuesta inspiradora. Creo que usaré este enfoque únicamente para la capa de persistencia (ya que no quiero ir con proveedores de contenido). Preguntándose cuál fue la motivación detrás del diseño de SQLiteOpenHelper de una manera que espera que se proporcione un Contexto en lugar de adquirirlo de la propia Aplicación. PD: ¡Y tu libro es genial!
yanchenko
77
Usar el contexto de la aplicación LayoutInflatorsimplemente funcionó para mí. Debe haber sido cambiado en los últimos tres años.
Jacob Phillips,
55
@JacobPhillips Usar LayoutInflator sin un contexto de actividad se perderá el estilo de esa Actividad. Por lo tanto, funcionaría en un sentido, pero no en otro.
Marcar el
1
@ MarkCarter ¿Quiere decir que el uso del contexto de la aplicación se perderá el estilo de la actividad?
Jacob Phillips
1
@JacobPhillips sí, el contexto de la aplicación no puede tener el estilo porque cada actividad puede tener un estilo diferente.
Mark
28

En mi experiencia, este enfoque no debería ser necesario. Si necesita el contexto para cualquier cosa, generalmente puede obtenerlo mediante una llamada a View.getContext () y utilizando el Contextobtenido allí puede llamar a Context.getApplicationContext () para obtener el Applicationcontexto. Si está tratando de obtener el Applicationcontexto de esto Activity, siempre puede llamar a Activity.getApplication (), que debería poder pasarse según Contextsea ​​necesario para una llamada SQLiteOpenHelper().

En general, no parece haber un problema con su enfoque para esta situación, pero al tratar, Contextasegúrese de no perder memoria en ninguna parte, como se describe en el blog oficial de Google Android Developers .

snctln
fuente
13

Algunas personas han preguntado: ¿cómo puede el singleton devolver un puntero nulo? Estoy respondiendo esa pregunta. (No puedo responder en un comentario porque necesito publicar el código).

Puede devolver nulo entre dos eventos: (1) se carga la clase y (2) se crea el objeto de esta clase. Aquí hay un ejemplo:

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

Ejecutemos el código:

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

La segunda línea muestra que Y.xinstance y X.yinstance son nulos ; son nulos porque las variables X.xinstance y Y.yinstance se leyeron cuando eran nulas.

¿Se puede arreglar esto? Si,

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

y este código no muestra anomalía:

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

PERO esto no es una opción para el Applicationobjeto Android : el programador no controla el momento en que se crea.

Una vez más: la diferencia entre el primer ejemplo y el segundo es que el segundo ejemplo crea una instancia si el puntero estático es nulo. Pero un programador no puede crear el objeto de la aplicación de Android antes de que el sistema decida hacerlo.

ACTUALIZAR

Un ejemplo más desconcertante donde los campos estáticos inicializados resultan ser 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());
    }
}

Y obtienes:

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

Tenga en cuenta que no puede mover la declaración de variable estática una línea hacia arriba, el código no se compilará.

18446744073709551615
fuente
3
Ejemplo útil; Es bueno saber que hay tal agujero. Lo que quito de esto es que uno debe evitar referirse a una variable estática durante la inicialización estática de cualquier clase.
ToolmakerSteve
10

Clase de aplicación:

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

}

Declare la aplicación en el AndroidManifest:

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

Uso:

MyApplication.getAppContext()
que ha
fuente
1
Propenso a fugas de memoria. Nunca deberías hacer esto.
Dragas
9

Está intentando crear un contenedor para obtener el Contexto de la aplicación y existe la posibilidad de que devuelva el " null" puntero.

Según tengo entendido, supongo que es un mejor enfoque para llamar a cualquiera de los 2 Context.getApplicationContext() o Activity.getApplication().

Prasanta
fuente
13
¿Cuándo debería volver nulo?
Pegado
25
No hay ningún método estático Context.getApplicationContext () que yo sepa. ¿Me estoy perdiendo de algo?
dalcantara
También implemento el mismo enfoque en mi aplicación, pero al llamar a SQLiteOpenHelper, devuelve el puntero nulo. Cualquier respuesta para este tipo de situación.
ashutosh
2
Este puede ser el caso si llama a SQLiteOpenHelper en un proveedor de contenido que se carga antes de la aplicación.
Gunnar Bernstein
5

Es un buen enfoque. Yo también lo uso. Solo sugeriría anular onCreatepara establecer el singleton en lugar de usar un constructor.

Y como mencionaste SQLiteOpenHelper : onCreate ()también puedes abrir la base de datos.

Personalmente, creo que la documentación se equivocó al decir que normalmente no hay necesidad de subclasificar Aplicación . Creo que lo contrario es cierto: siempre debe subclasificar Aplicación.

Martín
fuente
3

Usaría el contexto de aplicación para obtener un servicio del sistema en el constructor. Esto facilita las pruebas y los beneficios de la composición.

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 clase de prueba usaría el constructor sobrecargado.

Android usaría el constructor predeterminado.

Blundell
fuente
1

Me gusta, pero sugeriría un singleton en su lugar:

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
fuente
31
La extensión de android.app.application ya garantiza el singleton, por lo que esto es innecesario
Vincent
8
¿Qué pasa si quieres acceder a clases que no son de actividad?
Maxrunner
99
Nunca debe usar newla Aplicación usted mismo (con la posible excepción de las pruebas unitarias). El sistema operativo lo hará. Tampoco deberías tener un constructor. Para eso es eso onCreate.
Martin
@ Vincent: ¿puedes publicar algún enlace sobre esto? preferiblemente código - Estoy preguntando aquí: stackoverflow.com/questions/19365797/…
Mr_and_Mrs_D
@radzio ¿por qué no deberíamos hacerlo en constructor?
Miha_x64
1

Estoy usando el mismo enfoque, sugiero escribir el singleton un poco mejor:

public static MyApp getInstance() {

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

    return instance;
}

¡pero no lo estoy usando en todas partes, lo uso getContext()y getApplicationContext()donde puedo hacerlo!

Serafines
fuente
Entonces, por favor escriba un comentario para explicar por qué ha rechazado la respuesta para que yo pueda entender. El enfoque singleton es ampliamente utilizado para obtener un contexto válido fuera de las actividades o vistas del cuerpo ...
Seraphim's
1
No es necesario ya que el sistema operativo garantiza que la aplicación se instancia exactamente una vez. Si hubiera, sugeriría configurar el Singelton en onCreate ().
Martin
1
Una buena forma segura de subprocesos para perezoso inicializar un singleton, pero no es necesario aquí.
naXa
2
Wow, justo cuando pensaba que la gente finalmente había dejado de usar el bloqueo de doble verificación ... cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
Søren Boisen el