Lo que preguntas es una pregunta bastante difícil. Si bien puede pensar que es solo una pregunta, en realidad está haciendo varias preguntas a la vez. Haré lo mejor que pueda con el conocimiento de que tengo que cubrirlo y, con suerte, algunos otros se unirán para cubrir lo que pueda extrañar.
Clases Anidadas: Introducción
Como no estoy seguro de qué tan cómodo se siente con OOP en Java, esto afectará algunos aspectos básicos. Una clase anidada es cuando una definición de clase está contenida dentro de otra clase. Básicamente hay dos tipos: Clases anidadas estáticas y Clases internas. La verdadera diferencia entre estos son:
- Clases Estáticas Anidadas:
- Se consideran de "nivel superior".
- No requiere que se construya una instancia de la clase que lo contiene.
- No puede hacer referencia a los miembros de la clase que los contiene sin una referencia explícita.
- Tener su propia vida.
- Clases anidadas internas:
- Siempre se requiere construir una instancia de la clase que lo contiene.
- Tener automáticamente una referencia implícita a la instancia que lo contiene.
- Puede acceder a los miembros de la clase del contenedor sin la referencia.
- Se supone que la vida útil no debe ser más larga que la del contenedor.
Recolección de basura y clases internas
La recolección de basura es automática, pero intenta eliminar objetos según si cree que se están utilizando. El recolector de basura es bastante inteligente, pero no perfecto. Solo puede determinar si se está usando algo si existe o no una referencia activa al objeto.
El problema real aquí es cuando una clase interna se ha mantenido viva más tiempo que su contenedor. Esto se debe a la referencia implícita a la clase que lo contiene. La única forma en que esto puede ocurrir es si un objeto fuera de la clase que contiene mantiene una referencia al objeto interno, sin tener en cuenta el objeto que lo contiene.
Esto puede conducir a una situación en la que el objeto interno está vivo (por referencia) pero las referencias al objeto que lo contiene ya se han eliminado de todos los demás objetos. El objeto interno, por lo tanto, mantiene vivo al objeto contenedor porque siempre tendrá una referencia a él. El problema con esto es que, a menos que esté programado, no hay forma de volver al objeto que lo contiene para verificar si está vivo.
El aspecto más importante para esta realización es que no importa si se trata de una Actividad o de un sorteo. Usted siempre tiene que ser metódico cuando se utilizan las clases internas y asegurarse de que los objetos no sobreviven del contenedor. Afortunadamente, 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 filtraciones más difíciles de encontrar, ya que es probable que pasen desapercibidas hasta que muchas de ellas se hayan filtrado.
Soluciones: clases internas
- Obtenga referencias temporales del objeto contenedor.
- Permita que el objeto contenedor sea el único que mantenga referencias duraderas a los objetos internos.
- Use patrones establecidos como la Fábrica.
- Si la clase interna no requiere acceso a los miembros de la clase que lo contiene, considere convertirla en una clase estática.
- Úselo con precaución, independientemente de si está en una Actividad o no.
Actividades y vistas: Introducción
Las actividades contienen mucha información para poder ejecutarse y visualizarse. Las actividades se definen por la característica de 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 elementos secundarios (es decir getChildAt()
). Finalmente, cada vista mantiene una referencia al mapa de bits representado que representa su visualización.
Siempre que tenga una referencia a una Actividad (o Contexto de Actividad), esto significa que puede seguir TODA la cadena en la jerarquía de diseño. Es por eso que las pérdidas de memoria con respecto a las actividades o vistas son tan importantes. Puede ser una tonelada de memoria que se filtró de una vez.
Actividades, vistas y clases internas
Dada la información anterior sobre las clases internas, estas son las pérdidas de memoria más comunes, pero también las que se evitan con mayor frecuencia. 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 posibles problemas. El problema con Actividades y Vistas es mucho más profundo que eso.
Actividades filtradas, vistas y contextos de actividad
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 intentarán guardar algo de código tomando una referencia a un Contexto y reteniéndolo. Sucede que muchos de los objetos que tenemos que crear para ejecutar nuestra Actividad tienen que existir fuera del Activity LifeCycle para permitir que la Actividad haga lo que necesita hacer. Si alguno de sus 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 Vistas.
Soluciones: actividades y puntos de vista
- Evite, a toda costa, hacer una referencia estática a una vista o actividad.
- Todas las referencias a los contextos de actividad deben ser de corta duración (la duración de la función)
- Si necesita un contexto de larga duración, use el contexto de la aplicación (
getBaseContext()
o getApplicationContext()
). Estos no guardan referencias implícitamente.
- Alternativamente, puede limitar la destrucción de una actividad anulando los cambios de configuración. Sin embargo, esto no impide que otros eventos potenciales destruyan la Actividad. Si bien puede hacer esto, es posible que desee consultar las prácticas anteriores.
Runnables: Introducción
Los runnables en realidad no son tan malos. Quiero decir, podrían ser, pero realmente ya hemos golpeado la mayoría de las zonas de peligro. Un Runnable es una operación asincrónica que realiza una tarea independiente del hilo en el que se creó. La mayoría de las ejecutables se instancian desde el hilo de la interfaz de usuario. En esencia, usar un Runnable es crear otro hilo, solo un poco más administrado. Si clasifica un Runnable como una clase estándar y sigue las pautas anteriores, debería tener pocos problemas. La realidad es que muchos desarrolladores no hacen esto.
Debido a la facilidad, la legibilidad y el flujo lógico del programa, muchos desarrolladores utilizan clases internas anónimas 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. Simplemente no tiene que crear una definición completamente nueva y simplemente anular los métodos apropiados. En todos los demás aspectos, es una clase interna, 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 los Runnables se ejecutan fuera del hilo actual, el peligro con estos viene a ser operaciones asincrónicas de larga duración. Si el ejecutable se define en una Actividad o Vista como una Clase interna anónima O una Clase interna anidada, existen algunos peligros muy graves. Esto se debe a que, como se indicó anteriormente, tiene que saber quién es su contenedor. Ingrese el cambio de orientación (o la eliminación del sistema). Ahora solo consulte las secciones anteriores para comprender lo que acaba de suceder. Sí, tu ejemplo es bastante peligroso.
Soluciones: Runnables
- Intente extender Runnable, si no rompe la lógica de su código.
- Haga su mejor esfuerzo para hacer que los Runnables extendidos sean estáticos, si deben ser clases anidadas.
- Si debe usar Runnables anónimos, 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 con la misma facilidad. Considere usar AsyncTask, ya que esos son VM Managed por defecto.
Respondiendo la pregunta final
ahora para responder las preguntas que no fueron abordadas directamente por las otras secciones de esta publicación. Usted preguntó "¿Cuándo puede un objeto de una clase interna sobrevivir más tiempo que su clase externa?" Antes de llegar a esto, permítanme enfatizar de nuevo: aunque tiene razón en preocuparse por esto en Actividades, puede causar una fuga en cualquier lugar. Proporcionaré un ejemplo simple (sin usar una Actividad) solo para demostrarlo.
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 an Inner class
public Leak createLeak()
{
return new Leak();
}
// Mass Manufactured Leak class
public class Leak
{//Again for a little data.
int size = 1;
}
}
Este no es un ejemplo común, pero es 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 fábrica. Aunque lanzamos Factory, permanecerá en la memoria porque cada Leak tiene una referencia a ella. Ni siquiera importa que la clase externa no tenga datos. Esto sucede mucho más a menudo de lo que uno podría pensar. No necesitamos al creador, solo sus creaciones. Entonces creamos uno temporalmente, pero usamos las creaciones indefinidamente.
Imagina lo que sucede cuando cambiamos el constructor solo 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 una de esas nuevas LeakFactories acaba de filtrarse. ¿Qué piensas 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 externa hubiera sido una Actividad, imagina lo peor que hubiera sido.
Conclusión
Estos enumeran los peligros principalmente conocidos de usar estos objetos de manera inapropiada. En general, esta publicación debería haber cubierto la mayoría de sus preguntas, pero entiendo que fue una publicación muuuy larga, así que si necesita aclaraciones, hágamelo saber. Siempre que siga las prácticas anteriores, tendrá muy poca preocupación por las fugas.