¿Cuándo es exactamente seguro usar clases anónimas internas?

He estado leyendo algunos artículos sobre fugas de memoria en Android y he visto este interesante video de Google I / O sobre el tema .

Sin embargo, no entiendo completamente el concepto, y especialmente cuando es seguro o peligroso para las clases internas del usuario dentro de una actividad .

Esto es lo que entendí:

Se producirá una pérdida de memoria si una instancia de una clase interna sobrevive más tiempo que su clase externa (una actividad). -> ¿ En qué situaciones puede ocurrir esto?

En este ejemplo, supongo que no hay riesgo de filtración, porque no hay manera de que la clase anónima extendiendo OnClickListener viva más tiempo que la actividad, ¿verdad?

  final Dialog dialog = new Dialog(this); dialog.setContentView(R.layout.dialog_generic); Button okButton = (Button) dialog.findViewById(R.id.dialog_button_ok); TextView titleTv = (TextView) dialog.findViewById(R.id.dialog_generic_title); // *** Handle button click okButton.setOnClickListener(new OnClickListener() { public void onClick(View v) { dialog.dismiss(); } }); titleTv.setText("dialog title"); dialog.show(); 

¿Ahora, este ejemplo es peligroso, y por qué?

 // We are still inside an Activity _handlerToDelayDroidMove = new Handler(); _handlerToDelayDroidMove.postDelayed(_droidPlayRunnable, 10000); private Runnable _droidPlayRunnable = new Runnable() { public void run() { _someFieldOfTheActivity.performLongCalculation(); } }; 

Tengo una duda sobre el hecho de que la comprensión de este tema tiene que ver con la comprensión en detalle lo que se mantiene cuando una actividad se destruye y se vuelve a crear.

¿Lo es?

Digamos que acabo de cambiar la orientación del dispositivo (que es la causa más común de fugas). Cuando super.onCreate(savedInstanceState) será llamado en mi onCreate() , esto restaurará los valores de los campos (como eran antes del cambio de orientación)? ¿Esto restaurará también los estados de las clases internas?

Me doy cuenta de que mi pregunta no es muy precisa, pero realmente apreciaría cualquier explicación que pudiera aclarar las cosas.

Sebastián,

Lo que usted está haciendo es una pregunta bastante difícil. Mientras que usted puede pensar que es sólo una pregunta, en realidad está haciendo varias preguntas a la vez. Haré mi mejor con el conocimiento que tengo que cubrirlo y, esperanzadamente, otros unirán adentro para cubrir lo que puedo faltar.

Clases Internas: Introducción

Como no estoy seguro de lo cómodo que estás con OOP en Java, esto afectará a un par de conceptos básicos. Una clase interna es cuando una definición de clase está contenida dentro de otra clase. Hay básicamente dos tipos: estático y no estático. La diferencia real entre estos son:

  • Clases internas estáticas:
    • Se consideran de "nivel superior".
    • No requiere que se construya una instancia de la clase que contiene.
    • No puede hacer referencia a los miembros de clase que contienen sin una referencia explícita.
    • Tienen su propia vida.
  • Clases internas no estáticas:
    • Requiera siempre una instancia de la clase que contiene que se construirá.
    • Automáticamente tiene una referencia implícita a la instancia de contención.
    • Puede acceder a los miembros de la clase del contenedor sin la referencia.
    • Se supone que la vida útil ya no es mayor que la del contenedor.

Recolección de basura y clases internas no estáticas

Garbage Collection es automático pero intenta eliminar objetos basándose en si piensa que están siendo utilizados. El recolector de basura es bastante inteligente, pero no impecable. Sólo puede determinar si algo está siendo utilizado por si existe o no una referencia activa al objeto.

El problema real aquí es cuando una clase interna no estática se ha mantenido viva más tiempo que su contenedor. Esto se debe a la referencia implícita a la clase que contiene. La única manera en que esto puede ocurrir es si un objeto fuera de la clase que contiene contiene una referencia al objeto interno, sin tener en cuenta el objeto que contiene.

Esto puede conducir a una situación en la que el objeto interno está vivo (a través de referencia), pero las referencias al objeto contenedor ya se han eliminado de todos los demás objetos. El objeto interior es, por lo tanto, mantener vivo el objeto contenedor porque siempre tendrá una referencia a él. El problema con esto es que a menos que esté programado, no hay manera de volver al objeto contenedor para comprobar si está incluso vivo.

El aspecto más importante a esta realización es que no hace ninguna diferencia si está en una actividad o es un drawable. Siempre tendrá que ser metódico al usar clases internas no estáticas y asegurarse de que nunca sobrevivirán a los objetos del contenedor. Por suerte, si no es un objeto central de su código, las filtraciones pueden ser pequeñas en comparación. Desafortunadamente, estas son algunas de las fugas más difíciles de encontrar, porque es probable que pasen desapercibidas hasta que muchos de ellos han filtrado.

Soluciones: Clases internas no estáticas

  • Obtener referencias temporales del objeto que contiene.
  • Permita que el objeto contenedor sea el único que mantenga referencias duraderas a los objetos internos.
  • Utilice patrones establecidos como la Fábrica.
  • Si la clase interna no requiere acceso a los miembros de la clase que lo contienen, considere convertirla en una clase estática.
  • Utilícelo con precaución, independientemente de si se encuentra en una actividad o no.

Actividades y vistas: Introducción

Las actividades contienen mucha información para poder ejecutar y mostrar. Las actividades se definen por la característica que deben tener una vista. También tienen ciertos manejadores automáticos. Ya sea que lo especifique o no, la Actividad tiene una referencia implícita a la Vista que contiene.

Para que se cree una vista, debe saber dónde crearla y si tiene hijos para poder mostrarla. Esto significa que cada vista tiene una referencia a la actividad (vía getContext() ). Además, cada vista mantiene referencias a sus hijos (es decir, getChildAt() ). Por último, cada vista mantiene una referencia al bitmap representado que representa su visualización.

Siempre que tenga una referencia a una Actividad (o Contexto de Actividad), esto significa que puede seguir la cadena ENTIRE en la jerarquía de diseño. Esta es la razón por la memoria fugas en relación con las actividades o vistas son un gran negocio. Puede ser una tonelada de memoria que se filtra todo de una vez.

Actividades, vistas y clases internas no estáticas

Dada la información anterior acerca de las clases internas, estas son las fugas de memoria más comunes, pero también las más comúnmente evitadas. Si bien es deseable que una clase interna tenga acceso directo a los miembros de una clase de Actividades, muchos están dispuestos a hacerlos estáticos para evitar problemas potenciales. El problema con las actividades y las vistas va mucho más profundo que eso.

Actividades, vistas y contextos de actividad filtrados

Todo se reduce al Contexto y al Ciclo de Vida. Hay ciertos eventos (como la orientación) que matarán un Contexto de Actividad. Dado que muchas clases y métodos requieren un Contexto, los desarrolladores a veces tratan de guardar algún código agarrando una referencia a un Contexto y sosteniéndolo. Simplemente sucede que muchos de los objetos que tenemos que crear para ejecutar nuestra Actividad deben existir fuera del Activity LifeCycle para permitir que la Actividad haga lo que necesita hacer. Si alguno de tus objetos tiene una referencia a una Actividad, su Contexto o cualquiera de sus Vistas cuando se destruye, acaba de filtrar esa Actividad y todo su árbol de Vista.

Soluciones: Actividades y vistas

  • Evite, a toda costa, hacer una referencia estática a una vista o actividad.
  • Todas las referencias a los Contextos de la Actividad deben ser de corta duración (la duración de la función)
  • Si necesita un contexto de larga duración, utilice el contexto de la aplicación ( getBaseContext() o getApplicationContext() ). Estos no guardan referencias implícitas.
  • De forma alternativa, puede limitar la destrucción de una actividad mediante la sustitución de cambios de configuración. Sin embargo, esto no impide que otros eventos potenciales destruyan la Actividad. Mientras que usted puede hacer esto, usted puede todavía querer referir a las prácticas antedichas.

Runnables: Introducción

Runnables no son realmente tan malo. Quiero decir, podrían ser, pero realmente ya hemos golpeado la mayoría de las zonas peligrosas. Un Runnable es una operación asincrónica que realiza una tarea independiente del subproceso en el que se creó. La mayoría de los ejecutables se instancian desde el subproceso de la interfaz de usuario. En esencia, el uso de un Runnable está creando otro hilo, sólo un poco más administrado. Si clasifica un Runnable como una clase estándar y sigue las directrices anteriores, debe ejecutar en pocos problemas. La realidad es que muchos desarrolladores no hacen esto.

Fuera de facilidad, legibilidad y flujo de programa lógico, muchos desarrolladores utilizan clases anónimas internas para definir sus Runnables, como el ejemplo que creó anteriormente. Esto da como resultado un ejemplo como el que escribió anteriormente. Una clase interna anónima es básicamente una clase interna discreta no estática. Simplemente no tiene que crear una definición totalmente nueva y simplemente anular los métodos apropiados. En todos los demás aspectos es una clase interna no estática, lo que significa que mantiene una referencia implícita a su contenedor.

Runnables y Actividades / Vistas

¡Hurra! Esta sección puede ser corta! Debido al hecho de que Runnables se ejecutan fuera del subproceso actual, el peligro con estos se produce en operaciones asíncronas de larga duración. Si el runnable se define en una actividad o una vista como una clase interna anónima o una clase interna no estática, hay algunos peligros muy serios. Esto es porque, como se ha dicho anteriormente, tiene que saber quién es su contenedor. Introduzca el cambio de orientación (o el sistema de matar). Ahora refiérase a las secciones anteriores para entender lo que acaba de suceder. Sí, su ejemplo es bastante peligroso.

Soluciones: Runnables

  • Intenta extender Runnable, si no rompe la lógica de tu código.
  • Haga lo posible para que los Runnables extendidos sean estáticos, si deben ser clases internas.
  • Si debe utilizar Runnables anónimo, evite crearlos en cualquier objeto que tenga una referencia de larga duración a una actividad o vista que está en uso.
  • Muchos Runnables podrían haber sido AsyncTasks. Considere el uso de AsyncTask como los que se administran VM por defecto.

Respondiendo a la pregunta final Ahora para responder a las preguntas que no fueron abordadas directamente por las otras secciones de este post. Usted preguntó: "¿Cuándo puede un objeto de una clase interna sobrevivir más tiempo que su clase exterior?" Antes de llegar a esto, permítanme enfatizar de nuevo: aunque tenga razón al preocuparse por esto en Actividades, puede causar una fuga en cualquier lugar. Voy a proporcionar un ejemplo simple (sin usar una actividad) sólo para demostrar.

A continuación se muestra un ejemplo común de una fábrica básica (falta el código).

 public class LeakFactory {//Just so that we have some data to leak int myID = 0; // Necessary because our Leak class is non-static public Leak createLeak() { return new Leak(); } // Mass Manufactured Leak class public class Leak {//Again for a little data. int size = 1; } } 

Este es un ejemplo no tan común, pero lo suficientemente simple como para demostrarlo. La clave aquí es el constructor …

 public class SwissCheese {//Can't have swiss cheese without some holes public Leak[] myHoles; public SwissCheese() {//Gotta have a Factory to make my holes LeakFactory _holeDriller = new LeakFactory() // Now, let's get the holes and store them. myHoles = new Leak[1000]; for (int i = 0; i++; i<1000) {//Store them in the class member myHoles[i] = _holeDriller.createLeak(); } // Yay! We're done! // Buh-bye LeakFactory. I don't need you anymore... } } 

Ahora, tenemos Fugas, pero no hay Fábrica. A pesar de que lanzamos la fábrica, permanecerá en la memoria porque cada fuga tiene una referencia a ella. Ni siquiera importa que la clase externa no tenga datos. Esto sucede con mucha más frecuencia de lo que uno podría pensar. No necesitamos al creador, solo a sus creaciones. Así que creamos uno temporalmente, pero usamos las creaciones indefinidamente.

Imagine lo que sucede cuando cambiamos el constructor ligeramente.

 public class SwissCheese {//Can't have swiss cheese without some holes public Leak[] myHoles; public SwissCheese() {//Now, let's get the holes and store them. myHoles = new Leak[1000]; for (int i = 0; i++; i<1000) {//WOW! I don't even have to create a Factory... // This is SOOOO much prettier.... myHoles[i] = new LeakFactory().createLeak(); } } } 

Ahora, cada uno de esos nuevos LeakFactories acaba de ser filtrado. ¿Qué piensa usted de eso? Esos son dos ejemplos muy comunes de cómo una clase interna puede sobrevivir a una clase externa de cualquier tipo. Si esa clase exterior hubiera sido una Actividad, imagínense cuánto peor hubiera sido.

Conclusión

Estos enumeran los peligros principalmente conocidos de usar estos objetos de manera inapropiada. En general, este post debería haber cubierto la mayoría de sus preguntas, pero entiendo que era un post loooong, así que si necesita aclaración, avíseme. Mientras siga las prácticas anteriores, tendrá muy poca preocupación de fugas.

Espero que esto ayude,

FuzzicalLogic

  • Android ¿Cómo se obtiene memoria RAM total en el dispositivo?
  • OutOfMemoryError al cargar actividades
  • Fragmentos de memoria de Android Fragment
  • Problemas de memoria - fragmentos
  • Razones por las que mi aplicación Android se bloquea en mi teléfono de forma consistente, pero no en mi emulador
  • Pruebas de Android onTrimMemory
  • Cómo asignar memoria para el simulador de Android más de 768M en Windows?
  • ¿Cuál es el uso de MemoryFile en android
  • Android: Estrategia de caché de imágenes y tamaño de caché de memoria
  • Gráficos Android gran consumo de memoria montón? - LibGDX
  • Android Universal Image Loader - ¿Cómo configuro las especificaciones correctamente?
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.