Las referencias estáticas se borran – Android descarga clases en tiempo de ejecución si no se utiliza?
Tengo una pregunta específica sobre cómo funciona la recaudación de clases / recolección de basura en Android. Hemos tropezado con este tema unas cuantas veces, y por lo que puedo decir, Android se comporta diferente aquí de una JVM ordinaria.
El problema es el siguiente: en la actualidad estamos tratando de reducir las clases singleton en la aplicación a favor de una única raíz singleton de fábrica, cuyo único propósito es administrar otras clases de gestor. Un gerente de nivel superior si lo desea. Esto nos facilita sustituir las implementaciones en las pruebas sin optar por una solución DI completa, ya que todas las actividades y servicios comparten la misma referencia a esa fábrica raíz.
- Singletons vs. Contexto de la aplicación en Android?
- Tiempo de vida único estático en Android
- ¿Cuándo / por qué se destruye mi instancia de singleton de Java?
- Singletons vs. Contexto de la aplicación en Android?
- Obtenga el contexto de aplicación de la clase singleton de no actividad
Así es como se ve:
public class RootFactory { private static volatile RootFactory instance; @SuppressWarnings("unused") private Context context; // I'd like to keep this for now private volatile LanguageSupport languageSupport; private volatile Preferences preferences; private volatile LoginManager loginManager; private volatile TaskManager taskManager; private volatile PositionProvider positionManager; private volatile SimpleDataStorage simpleDataStorage; public static RootFactory initialize(Context context) { instance = new RootFactory(context); return instance; } private RootFactory(Context context) { this.context = context; } public static RootFactory getInstance() { return instance; } public LanguageSupport getLanguageSupport() { return languageSupport; } public void setLanguageSupport(LanguageSupport languageSupport) { this.languageSupport = languageSupport; } // ... }
initialize
se llama una vez, en Application.onCreate
, es decir, antes de iniciar cualquier actividad o servicio. Ahora, aquí está el problema: el método getInstance
veces vuelve como null
– incluso cuando se invoca en el mismo hilo! Parece que no es un problema de visibilidad; En su lugar, la retención de referencia de singleton estática en el nivel de clase parece haber sido realmente eliminada por el recolector de basura. Tal vez estoy saltando a las conclusiones de aquí, pero ¿podría ser esto porque el recolector de basura de Android o mecanismo de carga de clase realmente puede descargar clases cuando la memoria se vuelve escasa, en cuyo caso la única referencia a la instancia singleton desaparecerá? No estoy muy profundamente en el modelo de memoria de Java, pero supongo que no debería suceder, de lo contrario esta forma común de implementar singletons no funcionaría en cualquier derecho JVM?
¿Alguna idea de por qué esto está sucediendo exactamente?
PS: uno puede trabajar alrededor de esto manteniendo referencias "globales" en la sola instancia de la aplicación en lugar de otro. Eso ha demostrado ser confiable cuando uno debe mantener el objeto a lo largo de toda la vida útil de una aplicación.
ACTUALIZAR
Al parecer mi uso de volátil aquí causó cierta confusión. Mi intención era asegurar que el estado actual de la referencia estática siempre esté visible para todos los subprocesos que acceden a él. Debo hacerlo porque estoy escribiendo y leyendo esa referencia desde más de un hilo: En una ejecución de aplicación normal, solo en el hilo principal de la aplicación, pero en una prueba de instrumentación, donde los objetos son reemplazados por mocks, lo escribo desde el Instrumentación y lo lee en el hilo de la interfaz de usuario. También podría haber sincronizado la llamada a getInstance
, pero eso es más caro ya que requiere reclamar un bloqueo de objetos. Vea ¿Cuál es una manera eficiente de implementar un patrón singleton en Java? Para una discusión más detallada alrededor de esto.
- Obtener contexto en la biblioteca de Android
- Servicio de Android Creación de una nueva instancia de clase Singleton
- Cómo declarar variables globales en Android?
- Cómo separar la actividad principal y el selector de fechas con las propias clases
- ¿Por qué la clase singleton sobrevive a la actividad?
- La forma más segura de usar SharedPreferences
- Métodos estáticos o Singletons rendimiento-sabio (Android)?
- Excepción de puntero nulo durante la instrucción 'monitor-enter v1'
Nunca he visto en mi vida un miembro de datos estáticos declarado volatile
. Ni siquiera estoy seguro de lo que eso significa.
Los miembros de datos estáticos existirán hasta que se termine el proceso o hasta que se deshaga de ellos (por ejemplo, null
la referencia estática). El proceso puede ser terminado una vez que todas las actividades y servicios sean cerrados proactivamente por el usuario (por ejemplo, el botón BACK) y su código (por ejemplo, stopService()
). El proceso puede ser terminado incluso con componentes en vivo si Android es desesperadamente corto en RAM, pero esto es bastante inusual. El proceso se puede terminar con un servicio en vivo si Android piensa que su servicio ha estado en segundo plano demasiado tiempo, aunque puede reiniciar ese servicio dependiendo de su valor de retorno de onStartCommand()
.
Las clases no se descargan, período, antes de finalizar el proceso.
Para tratar el otro de los puntos de @ sergui, las actividades pueden ser destruidas, con el estado de la instancia almacenado (aunque en RAM, no "almacenamiento fijo"), para liberar RAM. Android tiende a hacerlo antes de finalizar los procesos activos, aunque si destruye la última actividad de un proceso y no hay servicios en ejecución, ese proceso será un candidato principal para la terminación.
La única cosa significativamente extraña sobre su aplicación es su uso de volatile
.
Tanto usted (@Matthias) y Mark Murphy (@CommonsWare) son correctos en lo que usted dice, pero la esencia parece perdido. (El uso de volatile
es correcto y las clases no se descargan.)
El quid de la pregunta es de donde se initialize
desde.
Esto es lo que creo que está sucediendo:
- Estás llamando inicializar desde una
Activity
- Android necesita más memoria, mata todo el
Process
- Android reinicia la
Application
y laActivity
principal - Llamar
getInstance
que devolveránull
, ya queinitialize
no se llamó
Corrígeme si estoy equivocado.
Las referencias estáticas se borran siempre que el sistema se sienta como él y su aplicación no es de alto nivel (el usuario no lo está ejecutando explícitamente). Cada vez que su aplicación se minimice y el sistema operativo quiere un poco más de memoria, ya sea matar su aplicación o serializar en almacenamiento fijo para su uso posterior, pero en ambos casos las variables estáticas se borran. Además, cada vez que su aplicación obtiene un error de Force Close
, todas las estadísticas se borran también. En mi experiencia vi que siempre es mejor usar variables en el objeto Application que variables estáticas.
He visto similar comportamiento extraño con mi propio código que implica la desaparición de variables estáticas (no creo que este problema tiene nada que ver con la palabra clave volátil). En particular, esto ha surgido cuando he inicializado un marco de registro (por ejemplo, Crashlytics, log4j), y luego, después de algún período de actividad, parece no haber sido inicializado. La investigación ha demostrado que esto sucede después de que el sistema operativo llama onSaveInstanceState(Bundle b)
.
El Classloader contiene las variables estáticas contenidas en el proceso de la aplicación. Según google:
Una característica inusual y fundamental de Android es que la vida útil de un proceso de aplicación no está controlada directamente por la propia aplicación. En su lugar, el sistema lo determina mediante una combinación de las partes de la aplicación que el sistema sabe están en ejecución, la importancia de estas cosas para el usuario y la cantidad de memoria disponible en el sistema.
http://developer.android.com/guide/topics/processes/process-lifecycle.html
Lo que esto significa para un desarrollador es que no puede esperar que las variables estáticas permanezcan inicializadas indefinidamente. Necesitas confiar en un mecanismo diferente para la persistencia.
Una solución que he utilizado para mantener mi marco de registro iniciado es para todas mis actividades para extender una clase base en la que sustituir onCreate
y comprobar la inicialización y volver a inicializar si es necesario.
Creo que la solución oficial es usar la devolución de llamada onSaveInstanceState(Bundle b)
para persistir cualquier cosa que su actividad necesite más tarde, y luego reinicializar en onCreate(Bundle b)
cuando b != null
.
Google lo explica mejor:
http://developer.android.com/training/basics/activity-lifecycle/recreating.html
- Videoview Pausar y reanudar
- OnActivityResult () no llamado cuando la actividad se inició desde el fragmento