Causas comunes y soluciones de pérdidas de memoria
(Memory Leak, pérdida de memoria)
Cuando un objeto ya no es necesario y debe reciclarse, otro objeto en uso mantiene una referencia a él, lo que hace que falle en su reciclaje. , lo que da como resultado que el objeto que debería reciclarse no se recicle y permanezca en la memoria del montón, lo que provoca una pérdida de memoria.
Las pérdidas de memoria son una de las principales causas de la aplicación OOM. Sabemos que la memoria asignada por el sistema Android para cada aplicación es limitada, y cuando hay muchas pérdidas de memoria en una aplicación, inevitablemente hará que la memoria requerida por la aplicación exceda el límite de memoria asignado por el sistema, lo que provocará que la memoria fugas. El desbordamiento hace que la aplicación se bloquee.
Debido a que la pérdida de memoria está en la memoria del montón, no es visible para nosotros. Normalmente podemos utilizar herramientas como MAT y LeakCanary para detectar si hay pérdidas de memoria en la aplicación.
1. MAT es una poderosa herramienta de análisis de memoria con muchas y complejas funciones.
2. LeakCanary es una herramienta ligera de detección de fugas de memoria de terceros y de código abierto de Square. Cuando se detecta una pérdida de memoria en el programa, nos dirá dónde ocurrió de la manera más intuitiva. Se produce una fuga y hace que la memoria se pierda y no se pueda reciclar.
Debido a la naturaleza estática del singleton, su ciclo de vida es tan largo como el ciclo de vida de la aplicación. Si un objeto ya no es necesario y el objeto singleton todavía contiene una referencia al objeto. provocará El objeto no se puede reciclar normalmente, lo que provocará una pérdida de memoria.
Ejemplo: evitar que las instancias singleton causen pérdidas de memoria
De esta manera, no importa qué contexto se pase, eventualmente usará el contexto de la aplicación y el ciclo de vida del singleton. es tan largo como el de la aplicación, por lo que esto evita pérdidas de memoria. ?
Por ejemplo, a veces podemos iniciar una Actividad con frecuencia Para evitar crear repetidamente los mismos recursos de datos, podemos escribirlo de la siguiente manera:
Esto crea una actividad dentro de la Actividad. Un singleton de una clase interna no estática, los datos de este singleton se utilizarán cada vez que se inicie la Actividad. Aunque esto evita la creación repetida de recursos, esta forma de escribir puede provocar pérdidas de memoria. Debido a que la clase interna no estática mantendrá una referencia a la clase externa de forma predeterminada, y la clase interna no estática crea una instancia estática, el ciclo de vida de la instancia es tan largo como la aplicación, lo que resulta en la instancia estática siempre se mantiene. Referencia a la Actividad, lo que resulta en que los recursos de memoria de la Actividad no se reciclen normalmente.
Solución: establezca la clase interna como una clase interna estática o extraiga la clase interna y encapsúlela en un singleton. Si necesita usar Contexto, use el Contexto de la aplicación.
Ejemplo: crear un objeto estático de una clase interna anónima
1. Desde la perspectiva de Android
Cuando se inicia una aplicación de Android, el hilo principal de la La aplicación creará automáticamente un objeto Looper y su MessageQueue asociado. Cuando se crea una instancia de un objeto Handler en el hilo principal, se asociará automáticamente con el MessageQueue del Looper del hilo principal. Todos los mensajes enviados a MessageQueue contendrán una referencia al Handler, por lo que Looper volverá a llamar al método handleMessage() del Handle en consecuencia para procesar el mensaje. Mientras haya mensajes sin procesar en MessageQueue, Looper continuará sacándolos y entregándolos al controlador para su procesamiento. Además, el objeto Looper del hilo principal acompañará todo el ciclo de vida de la aplicación.
2. Perspectiva de Java
En Java, las clases internas no estáticas y las clases internas anónimas potencialmente contendrán referencias a las clases externas a las que pertenecen, pero las clases internas estáticas no.
Analizando el ejemplo anterior, cuando MainActivity finaliza, el mensaje sin procesar contiene una referencia al controlador, y el controlador contiene una referencia a la clase externa a la que pertenece, que es MainActivity. Esta relación de referencia permanecerá hasta que se procese el mensaje, lo que evita que el recolector de basura recicle MainActivity, lo que provoca una pérdida de memoria.
Solución: Separe la clase Handler o utilice una clase interna estática para evitar pérdidas de memoria.
Ejemplo: AsyncTask y Runnable
AsyncTask y Runnable utilizan clases internas anónimas, por lo que contendrán referencias implícitas a la Actividad en la que se encuentran. Si la tarea no se completa antes de que se destruya la Actividad, los recursos de memoria de la Actividad no se reciclarán, lo que provocará una pérdida de memoria.
Solución: Separe las clases AsyncTask y Runnable o utilice clases internas estáticas para evitar pérdidas de memoria.
Para recursos como BroadcastReceiver, ContentObserver, File, Cursor, Stream, Bitmap, etc., deben cerrarse o cerrarse a tiempo cuando se destruye la Actividad; de lo contrario, estos recursos no se reciclarán, lo que provocará pérdidas de memoria.
1) Por ejemplo, un BraodcastReceiver está registrado en la Actividad, pero el BraodcastReceiver no se cancela del registro una vez finalizada la Actividad.
2) Los objetos de recursos como Cursor, Stream, File, etc. a menudo usan algunos buffers. Cuando no los usamos, debemos cerrarlos a tiempo para que sus buffers puedan recuperar memoria a tiempo. Sus buffers no sólo existen dentro de la máquina virtual Java, sino que también existen fuera de la máquina virtual Java. Si simplemente establecemos sus referencias en nulas sin cerrarlas, a menudo se producirán pérdidas de memoria.
3) Cuando un objeto de recurso no está en uso, se debe llamar a su función close() para cerrarlo y luego establecerlo en nulo. Debemos asegurarnos de que nuestros objetos de recursos estén cerrados cuando nuestro programa salga.
4) Llame a recycle() para liberar memoria cuando el objeto Bitmap ya no esté en uso. Los mapas de bits posteriores a 2.3 ya no deberían necesitar reciclarse manualmente, ya que la memoria ya está en la capa de Java.
Inicialmente, ListView creará instancias de una cierta cantidad de objetos Ver desde BaseAdapter según el diseño de pantalla actual, y ListView almacenará en caché estos objetos Ver. Cuando ListView se desplaza hacia arriba, el objeto Ver del elemento originalmente ubicado en la parte superior se reciclará y luego se usará para construir el nuevo elemento que aparece debajo. Este proceso de construcción se completa mediante el método getView (). El segundo parámetro formal convertView de getView () es el objeto Ver del elemento almacenado en caché (si no hay ningún objeto Ver en el caché durante la inicialización, convertView es nulo).
Al construir el Adaptador, no se utiliza convertView en caché.
Solución: utilice convertView en caché al construir el adaptador.
Generalmente agregamos algunas referencias de objetos al contenedor de la colección (como ArrayList). Cuando no necesitamos el objeto, no borramos sus referencias de la colección, por lo que la colección se hará cada vez más grande. .Ven más grande. Si esta colección es estática, la situación es aún más grave.
Solución: antes de salir del programa, borre los elementos de la colección, luego configúrelos en nulos y luego salga del programa.
Cuando no utilizamos el objeto WebView, debemos llamar a su función destroy() para destruirlo y liberar la memoria que ocupa, de lo contrario la memoria que ha ocupado durante mucho tiempo no podrá reciclarse, provocando que. una pérdida de memoria.
Solución: abra otro proceso para WebView y comuníquese con el hilo principal a través de AIDL. El proceso en el que se encuentra WebView puede elegir el momento adecuado para destruirlo según las necesidades del negocio, logrando así la liberación completa de la memoria.
1. Cuando se trata de usar Contexto, el Contexto de la Aplicación debe usarse para objetos con un ciclo de vida más largo que la Actividad. Siempre que utilice el Contexto, dé prioridad al Contexto de la Aplicación. Por supuesto, no es omnipotente. En algunos lugares, se debe utilizar el Contexto de la Actividad. Los escenarios de aplicación de Contexto para Aplicación, Servicio y Actividad son los siguientes:
Entre ellos, NO1 significa que la Aplicación y el Servicio pueden iniciar una Actividad, pero es necesario crear una nueva cola de tareas. En cuanto al Diálogo, solo se puede crear en Actividad. Además, se pueden utilizar los tres.
2. Si necesita utilizar variables miembro externas no estáticas (como Contexto, Vista) en una clase interna estática, puede usar referencias débiles en la clase interna estática para hacer referencia a las variables de la clase externa para evitar pérdidas de memoria.
3. Para los objetos que ya no necesitan usarse, asígnelos explícitamente a nulo. Por ejemplo, después de usar Bitmap, primero llame a recycle() y luego asígnelo a nulo.
4. Sea sensible al ciclo de vida de los objetos, prestando especial atención a los ciclos de vida de singletons, objetos estáticos, colecciones globales, etc.
5. Para objetos de clase interna cuyo ciclo de vida es más largo que Actividad, y la clase interna usa variables miembro de la clase externa, puede hacer esto para evitar pérdidas de memoria:
1 ) Cambiar la clase interna se cambia a una clase interna estática
2) Se utilizan referencias débiles en clases internas estáticas para referirse a variables miembro de clases externas
Resumen de pérdidas de memoria de Android