14

He estado estudiando las mejores prácticas para evitar fugas de memoria de contexto/actividad al crear vistas, y parece que no puedo encontrar una respuesta definitiva sobre lo que está o no permitido cuando llega a los campos estáticos en las clases.Android: campos estáticos y fugas de memoria

Digamos que tengo un código de esta forma:

public class MyOuterClass extends Activity{ 
    private MyInnerClass; 
    MyInnerClass = (MyInnerClass) findViewById(<XML call here>); 
    MyInnerClass.myXInt = 3; 

    // onCreate(), onResume(), etc. 

    public static class MyInnerClass extends SurfaceView implements Runnable{ 
     // Safe variables? 
     private static int myXInt, myYInt; 
     private static boolean myBoolean; 
     // Potentially safe? 
     private static Canvas myCanvas; 
     // Definitely bad. 
     private static Context myContext; 

     public MyInnerClass(Context context){ 
     myContext = context;  // This is bad. 
     } 
    } 
} 

Estoy un poco confundido sobre lo que la JVM en realidad considera que el cargador de clases para MyInnerClass. Técnicamente, dado que es un objeto SurfaceView, parece que las variables estáticas siempre deben existir una vez que la aplicación ha instanciado MyInnerClass una vez (lo que sucede cuando la vista se infló por primera vez), y luego permanecer allí hasta que la aplicación se termine. Si ese es el caso, ¿qué impide que los objetos Bitmaps y Canvas permanezcan abiertos y llenen el montón?

La única afirmación que veo repetida una y otra vez es que no se puede filtrar Contexto estático como el que he mostrado en el constructor, pero nunca va más allá. ¿Es eso realmente lo único que no puedes hacer?

+0

su 'Lienzo' etc. no necesita ser' estático'. De esa forma, de hecho permanecería en el montón para siempre – zapl

+1

Si ese es el caso, entonces ¿qué impide las constantes (es decir, privada estática final int MY_CONSTANT), desde también mantener abierta cualquier clase que amplíe la actividad (y su contexto)? – SeaNick

Respuesta

27

En Java/Android no se recogerá basura static variable o constante. Simplemente permanece allí una vez que la clase que lo contiene se carga a través de un cargador de clases. El cargador de clases es afaik siempre el mismo para todas las clases dentro de su aplicación y es el que tiene referencias estáticas a todas sus clases (por ejemplo, MyInnerClass.class). Como el cargador de clases no desaparece, tus clases tampoco lo harán, ya que están referenciadas como & y, por lo tanto, no son recolectables.

Al igual que en el ejemplo

public class SomeClass extends SurfaceView { 
    private static Context myContext; 

    public MyInnerClass(Context context){ 
    myContext = context;  // This is bad. 
    } 
} 

Eso es de hecho mal. Incluso si no existe ninguna referencia a SomeClass (por ejemplo, el Activity que mostró que su SurfaceView personalizado ha finalizado) la referencia estática al Context (y cualquier otra variable static/constante en SomeClass se mantendrá. Puede considerar que todos se han filtrado ya que no es posible a la basura recolectar ese Context etc. Si tiene una referencia de variable regular algo, una vez que la instancia que contiene esa variable no tiene más referencias, la instancia completa incluyendo sus referencias a otras cosas puede ser recolectada. JAV incluso puede manejar circular referencias finas.

Para las constantes que desee que suceda y generalmente no está mal, ya que la cantidad de constantes y la cantidad de memoria que ocupan no es grande. Las constantes tampoco (no deberían) referencia otras instancias que ocupan grandes cantidades de memoria como Context o Bitmap.

Además de la posibilidad de crear pérdidas de memoria a través de variables estáticas, también puede crear problemas si no desea tener una sola cosa para todas las instancias al mismo tiempo. Por ejemplo, si guarda el Bitmap de su SurfaceView en una variable static, no puede tener dos imágenes diferentes. Incluso si los dos SurfaceView s no se muestran al mismo tiempo, podría tener problemas ya que cada nueva instancia probablemente sobrescribirá la imagen anterior y si vuelve a la otra SurfaceView, inesperadamente muestra una imagen incorrecta. Estoy casi seguro de que no quiere usar static aquí.

El hecho de que su clase interna sea static class no significa que tenga que usar variables estáticas, simplemente significa que se comporta más como un método static ya que no puede usar las variables de instancia (las que no son static) en su clase.

Para evitar fugas de memoria, simplemente no debe usar variables estáticas en absoluto. No es necesario usarlos a menos que haga cosas especiales (por ejemplo, contar instancias de una clase). Las constantes están bien.

+0

Gracias. Creo que el razonamiento general para querer la estática se debe a que conceptualmente, la mayoría de los objetos de View solo necesitan una sola instancia, ya que generalmente representan una pantalla u objeto específico en la pantalla. Parece mucho más eficiente en cuanto a los códigos para hacerlos estáticos, pero entiendo el inconveniente allí. Lo único que me molesta de dejar las variables a merced total del GC es que no tengo control sobre cuándo será necesario liberar memoria, y de todo lo que he visto en mi aplicación, siempre es durante una operación de mapa de bits, que me temo que se convertirá en tasas de cuadros nerviosos. – SeaNick

+1

No necesita tener algo estático para mantenerlo vivo y reutilizarlo. Existirá mientras tenga una referencia a él. Y el uso de estática generalmente conducirá a un mayor consumo de memoria si olvida 'anular' todas sus referencias estáticas de manera apropiada. + Las instancias de la vista generalmente se recrearán si gira la pantalla, por ejemplo, – zapl

+1

. Mantengo la orientación de la pantalla bloqueada, pero ese es un buen punto. ¡Gracias de nuevo! – SeaNick