java - una - que clases internas no llevan nombre



¿Cuándo es exactamente seguro usar fugas(anónimas) clases internas? (1)

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 de los usuarios 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 pasar esto?

En este ejemplo, supongo que no hay riesgo de fuga, porque no hay forma de que la clase anónima que extiende 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, ¿es peligroso este ejemplo, 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 con respecto al hecho de que la comprensión de este tema tiene que ver con la comprensión en detalle de lo que se mantiene cuando una actividad se destruye y se recrea.

¿Lo es?

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

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


Lo que estás preguntando 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 lo cómodo que está con la POO en Java, esto afectará un par de aspectos básicos. Una clase anidada es cuando una definición de clase está contenida dentro de otra clase. Hay básicamente dos tipos: clases anidadas estáticas y clases internas. La diferencia real entre estos son:

  • Clases anidadas estáticas:
    • Se consideran de "nivel superior".
    • No es necesario que se construya una instancia de la clase contenedora.
    • No puede hacer referencia a los miembros de la clase que contiene sin una referencia explícita.
    • Tienen su propia vida.
  • Clases anidadas internas:
    • Siempre se requiere una instancia de la clase contenedora para construirse.
    • Tener automáticamente una referencia implícita a la instancia que contiene.
    • Puede acceder a los miembros de la clase del contenedor sin la referencia.
    • Se supone que la vida útil no es 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 en función de si cree que se están utilizando. El recolector de basura es bastante inteligente, pero no impecable. Solo puede determinar si algo está siendo usado por si hay o no una referencia activa al objeto.

El verdadero problema aquí es cuando una clase interna se ha mantenido con vida por más tiempo que su contenedor. Esto se debe a la referencia implícita a la clase contenedora. La única forma en que esto puede ocurrir es si un objeto fuera de la clase contenedora mantiene una referencia al objeto interno, sin tener en cuenta el objeto contenedor.

Esto puede llevar a una situación en la que el objeto interno está vivo (a través de una referencia) pero las referencias al objeto que contiene ya se han eliminado de todos los demás objetos. El objeto interno es, por lo tanto, mantener vivo el objeto que contiene 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 que contiene para verificar si está vivo.

El aspecto más importante para esta realización es que no hace ninguna diferencia si está en una Actividad o es un dibujo. Siempre deberá ser metódico al usar clases internas y asegurarse de que nunca sobrevivan a los objetos del contenedor. Afortunadamente, si no es un objeto central de su código, las fugas pueden ser pequeñas en comparación. Desafortunadamente, estas son algunas de las fugas 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 que contiene sea el único que mantenga referencias duraderas a los objetos internos.
  • Utilizar patrones establecidos como el de fábrica.
  • Si la clase interna no requiere acceso a los miembros de la clase contenedora, 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 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 pueda crear una Vista, debe saber dónde crearla y si tiene algún elemento secundario para poder mostrarla. Esto significa que cada Vista tiene una referencia a la Actividad (a través de getContext() ). Además, cada Vista mantiene referencias a sus hijos (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 hacia abajo en la jerarquía de diseño. Esta es la razón por la que las fugas de memoria relacionadas con Actividades o Vistas son tan importantes. Puede ser una tonelada de memoria que se filtra de una vez.

Actividades, vistas y clases internas.

Dada la información anterior sobre Clases internas, estas son las fugas de memoria más comunes, pero también las más 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 simplemente 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 tantas clases y métodos requieren un Contexto, los desarrolladores a veces intentan guardar algo de código al tomar una referencia a un Contexto y aferrarse a él. Da la casualidad de que muchos de los objetos que tenemos que crear para ejecutar nuestra Actividad tienen que existir fuera del Ciclo de Vida de la Actividad para permitir que la Actividad haga lo que tiene que hacer. Si alguno de sus objetos tiene una referencia a una Actividad, su Contexto o alguna de sus Vistas cuando se destruye, acaba de filtrar esa Actividad y todo su árbol de Vistas.

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 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 ejecutables no son realmente tan malos. Quiero decir, podrían ser, pero en realidad ya hemos golpeado la mayoría de las zonas de peligro. Un Runnable es una operación asíncrona que realiza una tarea independiente del hilo en el que se creó. La mayoría de los ejecutables se crean instancias del subproceso 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 algunos problemas. La realidad es que muchos desarrolladores no hacen esto.

Fuera de la facilidad, la legibilidad y el flujo lógico del programa, muchos desarrolladores utilizan las Clases internas anónimas para definir sus Runnables, como el ejemplo que crea arriba. 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.

Ejecutables y Actividades / Vistas

¡Hurra! Esta sección puede ser corta! Debido al hecho de que los Runnables se ejecutan fuera del subproceso actual, el peligro con estos se debe a operaciones asíncronas de larga ejecució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 serios. Esto se debe a que, como se dijo anteriormente, tiene que saber quién es su contenedor. Introduzca el cambio de orientación (o muerte del sistema). Ahora solo consulte las secciones anteriores para comprender lo que acaba de suceder. Sí, tu ejemplo es bastante peligroso.

Soluciones: Runnables

  • Intente y extienda Runnable, si no rompe la lógica de su código.
  • Haz tu 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 tan fácilmente como AsyncTasks. Considere la posibilidad de usar AsyncTask, ya que son VM Managed de forma predeterminada.

Responda la pregunta final ahora para responder a las preguntas que no fueron abordadas directamente por las otras secciones de esta publicación. Preguntó "¿Cuándo puede un objeto de una clase interna sobrevivir más tiempo que su clase externa?" Antes de que lleguemos a esto, permítame enfatizarlo: aunque tiene razón en preocuparse por esto en Actividades, puede causar una fuga en cualquier parte. Daré un ejemplo simple (sin usar una actividad) solo 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 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 hay fábrica. Aunque lanzamos Factory, permanecerá en la memoria porque cada una de las Fugas 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. Así que creamos uno temporalmente, pero usamos las creaciones indefinidamente.

Imagina lo que sucede cuando cambiamos el constructor solo un poco.

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 se acaba de filtrar. ¿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 externa hubiera sido una Actividad, imagina cuánto peor habría sido.

Conclusión

Estos enumeran los peligros conocidos principalmente 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 muy larga, así que si necesita una aclaración, hágamelo saber. Mientras siga las prácticas anteriores, tendrá muy poca preocupación de fugas.





inner-classes