Advertencia: no coloque clases de contexto de Android en campos estáticos; esto es una pérdida de memoria (y también rompe Instant Run)

84

Estudio de Android:

No coloque clases de contexto de Android en campos estáticos; esto es una pérdida de memoria (y también rompe Instant Run)

Entonces, 2 preguntas:

# 1 ¿Cómo se llama a startServicedesde un método estático sin una variable estática para el contexto?
# 2 ¿Cómo se envía una transmisión local desde un método estático (mismo)?

Ejemplos:

public static void log(int iLogLevel, String sRequest, String sData) {
    if(iLogLevel > 0) {

        Intent intent = new Intent(mContext, LogService.class);
        intent.putExtra("UPDATE_MAIN_ACTIVITY_VIEW", "UPDATE_MAIN_ACTIVITY_VIEW");
        mContext.startService(intent);
    }
}

o

        Intent intent = new Intent(MAIN_ACTIVITY_RECEIVER_INTENT);
        intent.putExtra(MAIN_ACTIVITY_REQUEST_FOR_UPDATE, sRequest));
        intent.putExtra(MAIN_ACTIVITY_DATA_FOR_VIEW, sData);
        intent.putExtra(MAIN_ACTIVITY_LOG_LEVEL, iLogLevel);
        LocalBroadcastManager.getInstance(mContext).sendBroadcast(intent);

¿Cuál sería la forma correcta de hacer esto sin usar mContext?

NOTA: Creo que mi pregunta principal podría ser cómo pasar contexto a una clase de la que vive el método de llamada.

John Smith
fuente
¿No puedes pasar el contexto como parámetro en el método?
Juan Cruz Soler
Estaría llamando a esta rutina en lugares que tampoco tendrían contexto.
John Smith
# 1 páselo como parámetro # 2 igual.
njzk2
Luego, también debe pasar el contexto al método de la persona que llama. El problema es que los campos estáticos no son basura recolectada, por lo que podría filtrar una actividad con todas sus Vistas
Juan Cruz Soler
1
@JohnSmith Ponlo en cascada desde la actividad de inicio (a través de parámetros de constructor o parámetros de método) hasta el punto que lo necesites.
AndroidMechanic - Viral Patel

Respuestas:

56

Simplemente páselo como parámetro a su método. No tiene sentido crear una instancia estática de Contextúnicamente con el propósito de iniciar un Intent.

Así es como debería verse su método:

public static void log(int iLogLevel, String sRequest, String sData, Context ctx) {
    if(iLogLevel > 0) {

        Intent intent = new Intent(ctx, LogService.class);
        intent1.putExtra("UPDATE_MAIN_ACTIVITY_VIEW", "UPDATE_MAIN_ACTIVITY_VIEW");
        ctx.startService(intent);
    }
}

Actualización a partir de los comentarios sobre la pregunta: Conecte en cascada el contexto desde la actividad de inicio (a través de parámetros de constructor o parámetros de método) hasta el punto en que lo necesite.

AndroidMechanic - Viral Patel
fuente
¿Puede proporcionar un ejemplo de constructor?
John Smith
si el nombre de su clase es, MyClassagregue un constructor público como un método public MyClass(Context ctx) { // put this ctx somewhere to use later }(Este es su constructor) Ahora cree una nueva instancia de MyClassuso de este constructor, por ejemploMyClass mc = new MyClass(ctx);
AndroidMechanic - Viral Patel
No creo que sea tan simple pasar bajo demanda. Aunque hay beneficios aparentes como no tener que preocuparse por un contexto obsoleto o como aquí, uno estático. Digamos que necesita contexto [puede ser que desee escribir en prefs] en una devolución de llamada de respuesta que se invocará de forma asincrónica. Entonces, a veces, se ve obligado a colocarlo en un campo de miembros. Y ahora tienes que pensar cómo no hacerlo estático. stackoverflow.com/a/40235834/2695276 parece funcionar.
Rajat Sharma
1
¿Está bien usar ApplicationContext como un campo estático? A diferencia de las actividades, el objeto de la aplicación no se destruye, ¿verdad?
NeoWang
50

Solo asegúrese de pasar context.getApplicationContext () o llamar a getApplicationContext () en cualquier contexto que se pase a través de métodos / constructor a su singleton si decide almacenarlo en cualquier campo miembro.

ejemplo a prueba de idiotas (incluso si alguien pasara una actividad, tomará el contexto de la aplicación y lo usará para crear una instancia del singleton):

public static synchronized RestClient getInstance(Context context) {
    if (mInstance == null) {
        mInstance = new RestClient(context.getApplicationContext());
    }
    return mInstance;
}

getApplicationContext () de acuerdo con los documentos: "Devuelve el contexto del único objeto de aplicación global del proceso actual".

Significa que el contexto devuelto por "getApplicationContext ()" vivirá durante todo el proceso y, por lo tanto, no importa si almacena una referencia estática en cualquier lugar, ya que siempre estará allí durante el tiempo de ejecución de su aplicación (y sobrevivirá a cualquier objeto / singletons instanciados por él).

Compare eso con el contexto dentro de las vistas / actividades que contienen grandes cantidades de datos, si filtra un contexto contenido por una actividad, el sistema no podrá liberar ese recurso que obviamente no es bueno.

Una referencia a una actividad por su contexto debe vivir el mismo ciclo de vida que la actividad en sí; de lo contrario, mantendrá el contexto como rehén y provocará una pérdida de memoria (que es la razón detrás de la advertencia de pelusa).

EDITAR: Para el tipo que critica el ejemplo de los documentos anteriores, incluso hay una sección de comentarios en el código sobre lo que acabo de escribir:

    // getApplicationContext() is key, it keeps you from leaking the
    // Activity or BroadcastReceiver if someone passes one in.
Marcus Gruneau
fuente
8
para el tipo que golpea al tipo que golpeó el ejemplo anterior: el punto de este hilo es la advertencia de Lint en conflicto con el patrón recomendado por Google para crear singleton.
Raphael C
7
Leer: "No coloques las clases de contexto de Android en campos estáticos; esto es una pérdida de memoria (y también rompe Instant Run)" ¿Sabes qué son las clases de contexto? La actividad es uno de ellos, y no debe almacenar la actividad como un campo estático, como se describió a sí mismo (o perderá memoria). Sin embargo, puede almacenar Contexto (siempre que sea el contexto de la aplicación) como un campo estático, ya que sobrevive a todo. (Y así ignore la advertencia). Estoy seguro de que podemos estar de acuerdo con este simple hecho, ¿verdad?
Marcus Gruneau
como un veterinario de iOS, en mi primera semana de Android ... Explicaciones como esta me ayudan a entender esta tontería del contexto .. Entonces, esa advertencia de pelusa (oh, cómo no me gustan las advertencias) se quedará, pero tu respuesta resuelve el problema real .
Eric
@Marcus, si su clase secundaria no sabe quién la instancia con qué contexto, entonces es una mala práctica almacenarla como un miembro estático. Además, el contexto de la aplicación vive como parte del objeto Aplicación de su aplicación, el objeto de la aplicación no permanecerá en la memoria para siempre, será eliminado. contrariamente a la creencia popular, la aplicación no se reiniciará desde cero. Android creará un nuevo objeto Aplicación e iniciará la actividad donde el usuario estaba antes para dar la ilusión de que la aplicación nunca fue eliminada en primer lugar.
Raphael C
@RaphaelC ¿tienes documentación de tal? Eso parece ser completamente incorrecto porque Android asegura solo un contexto de aplicación por ejecución de cada proceso.
HaydenKai
6

Es solo una advertencia. No se preocupe. Si desea utilizar un contexto de aplicación, puede guardarlo en una clase "singleton", que se utiliza para guardar todas las clases singleton en su proyecto.

Licat Julius
fuente
2

En tu caso no tiene mucho sentido tenerlo como campo estático pero no creo que sea malo en todos los casos. Si sabe qué está haciendo ahora, puede tener un campo estático que tenga contexto y anularlo más tarde. Estoy creando una instancia estática para mi clase de modelo principal que tiene contexto dentro, su contexto de aplicación, no contexto de actividad y también tengo un campo de instancia estática de clase que contiene Actividad en la que anulo al destruir. No veo que tenga pérdida de memoria. Entonces, si algún tipo inteligente piensa que estoy equivocado, no dude en comentar ...

También Instant Run funciona bien aquí ...

Renetik
fuente
No creo que esté equivocado en el principio, pero debe tener mucho cuidado de que la actividad de la que está hablando solo tenga un máximo de una instancia única en un momento dado antes de que pueda usar campos estáticos. Si su aplicación termina con más de una pila de actividades porque se puede iniciar desde diferentes lugares (notificación, enlaces profundos, ...), las cosas saldrán mal a menos que use alguna marca como singleInstance en el manifiesto. Por lo tanto, siempre es más fácil evitar campos estáticos de Actividades.
BladeCoder
android: launchMode = "singleTask" debería ser suficiente, así que voy a cambiar a eso, utilicé singleTop pero no sabía que no era suficiente porque quiero siempre instancias únicas de mis actividades principales, así es como están diseñadas mis aplicaciones.
Renetik
2
"singleTask" solo garantiza una instancia por tarea. Si su aplicación tiene varios puntos de entrada, como enlaces profundos o iniciarla desde una notificación, puede terminar con varias tareas.
BladeCoder
1

Generalmente, evite tener campos de contexto definidos como estáticos. La advertencia en sí misma explica por qué: es una pérdida de memoria. Sin embargo, romper la ejecución instantánea puede no ser el mayor problema del planeta.

Ahora, hay dos escenarios en los que recibiría esta advertencia. Por ejemplo (el más obvio):

public static Context ctx;

Y luego está el un poco más complicado, donde el contexto está envuelto en una clase:

public class Example{
    public Context ctx;
    //Constructor omitted for brievety 
}

Y esa clase se define como estática en algún lugar:

public static Example example;

Y recibirás la advertencia.

La solución en sí es bastante simple: no coloque campos de contexto en instancias estáticas , ya sea de una clase envolvente o declarándola estática directamente.

Y la solución a la advertencia es simple: no coloque el campo estáticamente. En su caso, pase el contexto como una instancia al método. Para las clases en las que se realizan múltiples llamadas de contexto, use un constructor para pasar el contexto (o una Actividad para el caso) a la clase.

Tenga en cuenta que es una advertencia, no un error. Si por alguna razón necesita un contexto estático, puede hacerlo. Aunque crea una pérdida de memoria cuando lo hace.

Zoe
fuente
¿Cómo podemos hacerlo sin crear una pérdida de memoria?
isJulian00
1
No puedes. Si es absolutamente necesario pasar contextos, puede buscar en un autobús de eventos
Zoe
ok, este era el problema que estaba teniendo, si pudiera, por favor, echarle un vistazo, tal vez haya otra forma de hacerlo, por cierto, el método tiene que ser estático porque lo estoy llamando desde el código c ++ stackoverflow.com/questions/54683863/…
isJulian00
0

Si se asegura de que sea un contexto de aplicación. No importa. Agrega esto

@SuppressLint("StaticFieldLeak")
Víctor Choy
fuente
1
No recomendaría hacer esto de todos modos. Si necesita contexto, puede usar el método requireContext (), si usa las bibliotecas de AndroidX. O puede pasar el contexto directamente al método que lo necesite. O incluso puede obtener la referencia de clase de la aplicación, pero prefiero recomendar no usar dicha sugerencia de SuppressLint.
Oleksandr Nos
0

Úselo WeakReferencepara almacenar el contexto en clases Singleton y la advertencia desaparecerá

private WeakReference<Context> context;

//Private contructor
private WidgetManager(Context context) {
    this.context = new WeakReference<>(context);
}

//Singleton
public static WidgetManager getInstance(Context context) {
    if (null == widgetManager) {
        widgetManager = new WidgetManager(context);
    }
    return widgetManager;
}

Ahora puede acceder al contexto como

  if (context.get() instanceof MainActivity) {
            ((MainActivity) context.get()).startActivityForResult(pickIntent, CODE_REQUEST_PICK_APPWIDGET);
        }
Hitesh Sahu
fuente