Prácticas recomendadas para la administración de la memoria

En este documento, se asume que seguiste las prácticas recomendadas para las apps para Android en lo que respecta a la administración de la memoria, como la información en Cómo administrar la memoria de tu app.

Introducción

Una fuga de memoria es un tipo de fuga de recursos que ocurre cuando un programa informático no libera la memoria asignada que ya no resulta necesaria. Una fuga puede hacer que la app solicite más memoria al SO de la que tiene disponible y sufra una falla. Hay varias prácticas inadecuadas que pueden provocar fugas de memoria en las apps para Android, como no descartar correctamente los recursos o no cancelar el registro de los objetos de escucha cuando ya no resultan necesarios.

En este documento, se indican algunas prácticas recomendadas que pueden ayudar a prevenir, detectar y resolver las fugas de memoria en tu código. Si probaste los métodos de este documento y sospechas que hay una fuga de memoria en nuestros SDKs, consulta Cómo informar problemas relacionados con los SDKs de Google.

Antes de comunicarte con el servicio de asistencia

Antes de informar una fuga de memoria al equipo de Atención al cliente de Google, sigue las prácticas recomendadas junto con los pasos de depuración proporcionados en este documento para asegurarte de que el error no esté en tu código. Estos pasos pueden resolver tu problema y, de no ser así, generarán la información que el equipo de Atención al cliente de Google necesita para ayudarte.

Evita las fugas de memoria

Sigue estas prácticas recomendadas para evitar algunas de las causas más frecuentes de fugas de memoria en el código que utiliza los SDKs de Google.

Prácticas recomendadas para las apps para Android

Asegúrate de haber realizado todo lo siguiente en tu app para Android:

  1. Libera los recursos sin utilizar.
  2. Cancela el registro de los objetos de escucha cuando ya no sean necesarios.
  3. Cancela las tareas cuando ya no sean necesarias.
  4. Reenvía los métodos de ciclo de vida para liberar recursos.
  5. Utiliza las versiones más recientes de los SDKs.

Para obtener detalles específicos sobre cada una de estas prácticas, consulta las siguientes secciones.

Libera los recursos sin utilizar

Cuando tu app para Android utilice un recurso, asegúrate de liberarlo cuando ya no sea necesario. De lo contrario, el recurso seguirá ocupando memoria incluso después de que tu app ya no lo requiera. Para obtener más información, consulta Cómo interpretar el ciclo de vida de la actividad en la documentación para Android.

Libera las referencias de GoogleMap inactivas en los GeoSDKs

Un error habitual es que un GoogleMap puede causar una fuga de memoria si se lo almacena mediante NavigationView o MapView. Un GoogleMap tiene una relación 1 a 1 con la instancia de NavigationView o MapView desde la cual se lo obtiene. Debes asegurarte de que el GoogleMap no se almacene en la memoria caché, o bien de que la referencia se libere cuando se llame a NavigationView#onDestroy o MapView#onDestroy. Si usas NavigationSupportFragment, MapSupportFragment o tu propio fragmento para unir estas vistas, entonces la referencia debe liberarse en Fragment#onDestroyView.

class NavFragment : SupportNavigationFragment() {

  var googleMap: GoogleMap?

  override fun onCreateView(
    inflater: LayoutInflater,
    parent: ViewGroup?,
    savedInstanceState: Bundle?,
  ): View  {
    super.onCreateView(inflater,parent,savedInstanceState)
    getMapAsync{map -> googleMap = map}
  }

  override fun onDestroyView() {
    googleMap = null
  }
}

Cancela el registro de los objetos de escucha cuando ya no sean necesarios

Cuando tu app para Android registre un objeto de escucha para un evento, como un clic en un botón o un cambio en el estado de una vista, asegúrate de cancelar su registro cuando la app ya no necesite supervisar el evento. De lo contrario, los objetos de escucha seguirán ocupando memoria incluso después de que tu app ya no los requiera.

Por ejemplo, supongamos que tu app utiliza el SDK de Navigation y llama al objeto de escucha addArrivalListener para escuchar eventos de llegada. También debería llamar a removeArrivalListener cuando ya no necesite supervisar esos eventos.

var arrivalListener: Navigator.ArrivalListener? = null

fun registerNavigationListeners() {
  arrivalListener =
    Navigator.ArrivalListener {
      ...
    }
  navigator.addArrivalListener(arrivalListener)
}

override fun onDestroy() {
  navView.onDestroy()
  if (arrivalListener != null) {
    navigator.removeArrivalListener(arrivalListener)
  }

  ...
  super.onDestroy()
}

Cancela las tareas cuando ya no sean necesarias

Cuando una app para Android inicie una tarea asíncrona, como una descarga o una solicitud de red, asegúrate de cancelarla cuando finalice. De lo contrario, seguirá ejecutándose en segundo plano incluso después de que la app no la requiera.

Para obtener más detalles sobre las prácticas recomendadas, consulta Cómo administrar la memoria de tu app en la documentación para Android.

Reenvía los métodos de ciclo de vida para liberar recursos

Si tu app utiliza el SDK de Navigation o Maps, asegúrate de liberar los recursos. Para ello, reenvía los métodos de ciclo de vida (en negrita) a navView. Puedes hacerlo con NavigationView en el SDK de Navigation, o bien con MapView en el SDK de Maps o Navigation. También puedes utilizar SupportNavigationFragment o SupportMapFragment en lugar de NavigationView y MapView, respectivamente. Los fragmentos de compatibilidad manejan el reenvío de los métodos de ciclo de vida.

class NavViewActivity : AppCompatActivity() {

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    ...
    navView = ...
    navView.onCreate(savedInstanceState)
    ...
  }

  override fun onSaveInstanceState(savedInstanceState: Bundle) {
    super.onSaveInstanceState(savedInstanceState)
    navView.onSaveInstanceState(savedInstanceState)
  }

  override fun onTrimMemory(level: Int) {
    super.onTrimMemory(level)
    navView.onTrimMemory(level)
  }

  /* Same with
    override fun onStart()
    override fun onResume()
    override fun onPause()
    override fun onConfigurationChanged(...)
    override fun onStop()
    override fun onDestroy()
  */
}

Utiliza las versiones más recientes de los SDKs

Los SDKs de Google se actualizan constantemente con nuevas funciones, correcciones de errores y mejoras de rendimiento. Mantén actualizados los SDKs de tu app para recibir estas correcciones.

Depura las fugas de memoria

Si sigues viendo fugas de memoria tras implementar todas las sugerencias correspondientes antes descritas en este documento, sigue este proceso de depuración.

Antes de comenzar, debes conocer cómo Android administra la memoria. Para obtener información, consulta Descripción general de la administración de memoria de Android.

Para depurar fugas de memoria, sigue este proceso:

  1. Recrea el problema. Este paso es fundamental para depurarlo.
  2. Verifica si el uso de memoria es el previsto. Verifica si el aumento en el uso que parece ser una fuga no es en realidad la memoria necesaria para ejecutar tu app.
  3. Depura el problema a nivel general. Hay varias utilidades que puedes usar para la depuración. Hay tres conjuntos diferentes de herramientas estándares que ayudan a depurar los problemas de memoria en Android: Android Studio, Perfetto y las utilidades de línea de comandos de Android Debug Bridge (adb).
  4. Verifica el uso de memoria de tu app. Obtén un volcado de montón y haz un seguimiento de la asignación de memoria. Luego, analiza los datos.
  5. Corrige las fugas de memoria.

En las siguientes secciones, se describen estos pasos en detalle.

Paso 1: Recrea el problema

Si no has podido recrear el problema, primero considera las situaciones que podrían llevar a la fuga de memoria. Pasar directamente a analizar el volcado de montón podría funcionar si sabes que se recreó el problema. Sin embargo, si obtienes un volcado de montón al iniciar la app o en otro momento aleatorio, es posible que no se hayan activado las condiciones que producen la fuga. Considera analizar diversas situaciones al tratar de recrear el problema:

  • ¿Qué conjunto de funciones se activan?

  • ¿Qué secuencia específica de acciones del usuario provoca la fuga?

    • ¿Probaste varias iteraciones para activar esta secuencia?
  • ¿Por qué estados del ciclo de vida pasó la app?

    • ¿Probaste varias iteraciones en los diferentes estados del ciclo de vida?

Asegúrate de poder recrear el problema en la última versión de los SDKs. Es posible que el problema de una versión anterior ya se haya solucionado.

Paso 2: Verifica si el uso de memoria de la app es el previsto

Cada función requiere memoria adicional. Cuando depures las diferentes situaciones, considera si este uso podría o no ser el previsto, o si realmente se trata de una fuga de memoria. Por ejemplo, para las diferentes funciones o tareas de usuario, considera las siguientes posibilidades:

  • Probable fuga: Con el tiempo, activar la situación a través de varias iteraciones provoca un aumento en el uso de memoria.

  • Probable uso de memoria previsto: Se recupera la memoria tras detener la situación.

  • Posible uso de memoria previsto: El uso de memoria aumenta durante un tiempo y, luego, se estabiliza. Esto podría deberse a una caché limitada o a otro uso de memoria previsto.

Si el comportamiento de la app refleja un probable uso de memoria previsto, el problema se puede abordar administrando la memoria de tu app. Para obtener ayuda, consulta Cómo administrar la memoria de tu app.

Paso 3: Depura el problema a nivel general

Cuando depures una fuga de memoria, comienza a nivel general y, luego, procede a un nivel más específico una vez que hayas reducido las posibilidades. Utiliza una de estas herramientas de depuración a nivel general para analizar primero si se produce una fuga con el pasar del tiempo:

Generador de perfiles de memoria de Android Studio

Esta herramienta te proporciona un histograma visual del consumo de memoria. Los volcados de montón y el seguimiento de asignación también se pueden activar desde esta misma interfaz. Esta herramienta es la que se recomienda de forma predeterminada. Para obtener más información, consulta Generador de perfiles de memoria de Android Studio.

Contadores de memoria Perfetto

Perfetto te brinda un control preciso sobre el seguimiento de varias métricas y presenta todos los datos en un solo histograma. Para obtener más información, consulta el siguiente artículo sobre los contadores de memoria Perfetto.

Interfaz de usuario de Perfetto

Utilidades de línea de comandos de Android Debug Bridge (adb)

Gran parte de aquello de lo que puedes hacer un seguimiento con Perfetto también está disponible como una utilidad de línea de comandos adb que puedes consultar de forma directa. Estos son algunos ejemplos importantes:

  • Meminfo te permite ver información detallada sobre la memoria en un momento dado.

  • Procstats proporciona importantes estadísticas agregadas a lo largo del tiempo.

Una estadística crucial que se debe tener en cuenta aquí es el espacio máximo en memoria física (maxRSS) que la app necesita con el tiempo. El MaxPSS puede no ser tan preciso. Para aumentar la precisión, consulta el parámetro adb shell dumpsys procstats --help –start-testing.

Seguimiento de asignación

El seguimiento de asignación identifica el seguimiento de pila en el que se asignó la memoria y si no se liberó. Este paso resulta particularmente útil cuando se buscan fugas en el código nativo. Dado que esta herramienta identifica el seguimiento de pila, puede ofrecer una excelente manera de depurar rápidamente la causa raíz o de averiguar cómo recrear el problema. Si deseas conocer los pasos para utilizar el seguimiento de asignación, consulta Depura la memoria en código nativo con el seguimiento de asignación.

Paso 4: Verifica el uso de memoria de tu app con un volcado de montón

Una forma de detectar una fuga de memoria es obtener un volcado de montón de tu app y, luego, inspeccionarlo en busca de fugas. Un volcado de montón es un resumen de todos los objetos incluidos en la memoria de una app. Se puede utilizar para diagnosticar fugas de memoria y otros problemas relacionados con esta.

Android Studio puede detectar fugas de memoria que el GC no puede corregir. Cuando capturas un volcado de montón, Android Studio verifica si hay alguna actividad o algún fragmento a los que aún se puede acceder, pero que ya se destruyeron.

  1. Captura un volcado de montón.
  2. Analiza el volcado de montón en busca de fugas de memoria.
  3. Corrige las fugas de memoria.

Para obtener detalles, consulta las siguientes secciones.

Captura un volcado de montón

Para capturar un volcado de montón, puedes utilizar Android Debug Bridge (adb) o el Generador de perfiles de memoria de Android Studio.

Captura un volcado de montón con adb

Para capturar un volcado de montón con adb, sigue estos pasos:

  1. Conecta tu dispositivo Android a tu computadora.
  2. Abre la ventana de símbolo del sistema y navega al directorio en el que se encuentran las herramientas adb.
  3. Para capturar un volcado de montón, ejecuta el siguiente comando:

    adb shell am dumpheap my.app.name $PHONE_FILE_OUT

  4. Para recuperar el volcado de montón, ejecuta el siguiente comando:

    adb pull $PHONE_FILE_OUT $LOCAL_FILE.

Captura un volcado de montón con Android Studio

Para capturar un volcado de montón con el Generador de perfiles de memoria de Android Studio, sigue los pasos de la sección Captura un volcado de montón de Android.

Analiza el volcado de montón en busca de fugas de memoria

Tras capturar un volcado de montón, puedes utilizar el Generador de perfiles de memoria de Android Studio para analizarlo. Para ello, sigue estos pasos:

  1. Abre tu proyecto de Android en Android Studio.

  2. Selecciona Run y, luego, la configuración de Debug.

  3. Abre la pestaña Android Profiler.

  4. Selecciona Memory.

  5. Selecciona Open heap dump y elige el archivo de volcado de montón que generaste. El generador de perfiles de memoria mostrará un gráfico del uso de memoria de tu app.

  6. Utiliza el gráfico para analizar el volcado de montón:

    • Identifica los objetos que ya no se están utilizando.

    • Identifica los objetos que consumen mucha memoria.

    • Consulta cuánta memoria utiliza cada objeto.

  7. Utiliza esta información para acotar o encontrar el origen de la fuga de memoria y corregirla.

Paso 5: Corrige las fugas de memoria

Tras identificar el origen de la fuga de memoria, puedes corregirla. Corregir las fugas de memoria en tus apps para Android ayuda a mejorar su rendimiento y estabilidad. Según la situación, los detalles pueden variar. Sin embargo, las siguientes sugerencias pueden ayudar:

Otras herramientas de depuración

Si aún no puedes encontrar ni corregir la fuga de memoria tras completar estos pasos, prueba con estas herramientas:

Depura la memoria en código nativo con el seguimiento de asignación

Incluso si no utilizas el código nativo de forma directa, varias bibliotecas comunes de Android lo hacen, incluidos los SDKs de Google. Si crees que la fuga de memoria está en el código nativo, hay varias herramientas que puedes utilizar para su depuración. Hacer un seguimiento de asignación con Android Studio o heapprofd (también compatible con Perfetto) es una excelente manera de identificar posibles causas de la fuga de memoria y suele ser el método de depuración más rápido.

Otra ventaja distintiva del seguimiento de asignación es que permite compartir los resultados sin incluir información sensible, que sí podría hallarse en un volcado de montón.

Identifica las fugas con LeakCanary

LeakCanary es una potente herramienta para identificar fugas de memoria en apps para Android. Si deseas obtener más información para utilizar LeakCanary en tu app, consulta LeakCanary.

Cómo informar problemas relacionados con los SDKs de Google

Si probaste los métodos de este documento y sospechas que hay una fuga de memoria en nuestros SDKs, comunícate con el equipo de asistencia al cliente y trata de proporcionarle la mayor cantidad posible de la siguiente información:

  • Pasos para recrear la fuga de memoria. Si los pasos requieren una programación compleja, te sugerimos copiar el código que replica el problema en nuestra app de ejemplo y proporcionar los pasos adicionales que deben seguirse en la IU para activar la fuga

  • Volcados de montón capturados en tu app tras recrear la fuga. Captura volcados de montón en dos momentos diferentes para mostrar que el uso de memoria aumentó notablemente

  • Si se prevé una fuga de memoria en el código nativo, comparte los resultados del seguimiento de asignación de heapprofd

  • Un informe de errores generado tras recrear la condición de fuga

  • Seguimiento de pila de cualquier falla relacionada con la memoria

    Nota importante: Los seguimientos de pila no suelen ser suficientes por sí solos para depurar un problema de memoria, así que asegúrate de proporcionar uno de los otros tipos de información.