0Pricing
Android Academy · Lección

Fugas de memoria y soluciones

Encuentre y detenga las fugas

Fugas de memoria y soluciones es una lección gratuita de Android Academy en CoddyKit. Esta es la lección 3 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de Android Academy, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de Android Academy incluye 4 lecciones en total.

¿Qué es una fuga de memoria?

Se produce una fuga de memoria cuando los objetos que ya no se necesitan no pueden ser recolectados por el recolector de basura porque algo aún conserva una referencia a ellos. En Android, la víctima clásica es una Activity o un Context que permanece después de que se destruye su pantalla.

Las fugas hacen crecer el montón con el tiempo, provocan recolecciones de basura más frecuentes (que pausan la interfaz y causan jank) y, finalmente, pueden bloquear la aplicación con un OutOfMemoryError. En esta lección aprenderá a encontrarlas y corregirlas.

Cómo decide el GC qué conservar

El recolector de basura de Android conserva cualquier objeto que sea alcanzable desde una raíz del GC (subprocesos activos, campos estáticos, etc.). Todo lo que no sea alcanzable se libera.

Una fuga no es más que una ruta de alcanzabilidad no deseada: un objeto de larga duración que conserva una referencia a otro de corta duración. La demostración en Kotlin puro siguiente muestra cómo la alcanzabilidad mantiene vivo un objeto.

object GlobalCache {
    val items = mutableListOf<ByteArray>()
}

fun cacheSomething() {
    // This 1MB array stays alive forever because GlobalCache
    // (a static singleton, a GC root) keeps referencing it.
    GlobalCache.items.add(ByteArray(1_000_000))
}

fun main() {
    repeat(3) { cacheSomething() }
    println("Held arrays: ${GlobalCache.items.size}") // never freed
}

La clásica: una Activity que se filtra

La fuga de memoria más común en Android se produce cuando un objeto estático o de larga duración mantiene un Context. Cuando se destruye la Activity (por una rotación o al navegar), el sistema intenta liberarla, pero la referencia estática la mantiene viva para siempre.

El código siguiente produce una fuga: un singleton almacena en caché el contexto de una Activity.

// LEAK: static field holds an Activity Context
object Analytics {
    var context: Context? = null   // <- keeps Activity alive
    fun init(ctx: Context) { context = ctx }
}

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Analytics.init(this) // passing the Activity -> leak on rotation
    }
}

Solución: usar el contexto de la aplicación

Cuando tenga que almacenar un Context en algo de larga duración, almacene el application context, que vive durante todo el proceso y se puede conservar de forma segura.

Conserve un contexto de Activity/View únicamente mientras exista esa pantalla. Esta sencilla regla evita la mayoría de las fugas de memoria en Android.

object Analytics {
    private var appContext: Context? = null
    fun init(ctx: Context) {
        // applicationContext is process-scoped and safe to keep
        appContext = ctx.applicationContext
    }
}

// Call site is now leak-free:
Analytics.init(this) // stores applicationContext, not the Activity

Clases internas y Handlers

Una clase interna no estática (incluidos la mayoría de los listeners, los Runnable y los callbacks de Handler) mantiene una referencia implícita a su clase externa. Si ese trabajo sobrevive a la pantalla, la pantalla se filtra.

Un Handler.postDelayed con retraso es un culpable frecuente: el mensaje pendiente mantiene viva la Activity hasta que se ejecuta.

// LEAK: the posted Runnable holds the Activity for 60 seconds
handler.postDelayed({ updateUi() }, 60_000)

// FIX: cancel pending work when the screen goes away
override fun onDestroy() {
    super.onDestroy()
    handler.removeCallbacksAndMessages(null)
}

Coroutines: delimite el ámbito del trabajo

Una coroutine que sobrevive a su pantalla filtra todo lo que captura. La solución es la concurrencia estructurada: inicie el trabajo en un ámbito vinculado a un ciclo de vida para que se cancele automáticamente.

En un ViewModel, use viewModelScope; en un controlador de UI, use lifecycleScope. Cuando se destruye el propietario, el ámbito cancela el trabajo y se liberan las referencias.

class FeedViewModel : ViewModel() {
    fun load() {
        // Cancelled automatically when the ViewModel is cleared
        viewModelScope.launch {
            val feed = repository.fetchFeed()
            _state.value = feed
        }
    }
}

// In Compose, collect tied to the lifecycle:
val state by viewModel.state.collectAsStateWithLifecycle()

DisposableEffect en Compose

En Compose, todo lo que registre debe anularse cuando el composable salga de la composición. DisposableEffect le proporciona un bloque onDispose exactamente para eso: listeners, observers, sensores y broadcast receivers.

Olvidar la limpieza aquí filtra el callback y todo lo que capture.

@Composable
fun LocationDisplay(manager: LocationManager) {
    DisposableEffect(manager) {
        val listener = LocationListener { /* update */ }
        manager.register(listener)
        onDispose { manager.unregister(listener) } // prevents the leak
    }
}

Cómo encontrar fugas con LeakCanary

LeakCanary es la herramienta estándar para detectar fugas automáticamente en las compilaciones de depuración. Añada la dependencia y la herramienta supervisará los objetos destruidos; si alguno no se recolecta mediante el garbage collector, volcará el heap y le mostrará la cadena de referencias exacta que lo mantiene vivo.

Esa cadena indica la solución: señala directamente al campo o listener responsable.

// build.gradle.kts (app module)
dependencies {
    debugImplementation("com.squareup.leakcanary:leakcanary-android:2.14")
}
// No code needed: on a debug build, navigate away from a screen and
// LeakCanary posts a notification with the leak trace, e.g.:
//   Analytics.context -> MainActivity (leaked)

Volcados del heap en el Profiler

Para las fugas que LeakCanary no detecta (crecimiento lento, fugas nativas o cachés grandes), use el Memory Profiler. Capture un heap dump, fuerce el GC e inspeccione qué clases tienen un número de instancias inesperadamente alto.

Un número creciente de instancias de su Activity, Fragment o ViewModel después de navegar a otra pantalla es una señal inequívoca. El profiler también muestra la ruta hasta las raíces del GC para que pueda rastrear la referencia.

Bitmaps y asignaciones grandes

No todos los problemas de memoria son fugas de referencias. Los bitmaps grandes y las cachés sin límites pueden agotar el heap por sí solos. Una foto a resolución completa puede ocupar decenas de megabytes en memoria.

Haga que una biblioteca de imágenes (Coil/Glide) reduzca la imagen al tamaño mostrado y establezca un límite máximo explícito para cualquier caché que cree. El fragmento muestra una caché LRU limitada por tamaño.

// Bound memory: keep at most ~1/8 of available app memory
val maxKb = (Runtime.getRuntime().maxMemory() / 1024 / 8).toInt()
val bitmapCache = object : LruCache<String, Bitmap>(maxKb) {
    override fun sizeOf(key: String, value: Bitmap) = value.byteCount / 1024
}
// An unbounded HashMap<String, Bitmap> would grow until OutOfMemoryError.

Lista de comprobación para prevenir fugas

Adquiera estos hábitos y evitará la mayoría de las fugas:

  • Almacene applicationContext en objetos de larga duración, nunca una Activity/View.
  • Ejecute el trabajo asíncrono en una coroutine con un ámbito vinculado al ciclo de vida (viewModelScope, lifecycleScope).
  • Siempre anule el registro de listeners/receivers (onDispose, onDestroy).
  • Cancele los Handlers con retraso y los temporizadores.
  • Establezca límites para las cachés y reduzca el tamaño de los bitmaps.
  • Mantenga LeakCanary en las compilaciones de depuración y actúe según sus trazas.

Comprobación rápida

Necesita que un objeto singleton conserve un Context durante toda su vida útil. ¿Qué contexto debería mantener para evitar filtrar una pantalla?

Repaso: mantenga la memoria limpia

Ha aprendido qué son las fugas y cómo detenerlas:

  • Las fugas son rutas de alcanzabilidad no deseadas que mantienen vivos objetos que deberían haberse liberado.
  • La fuga clásica es una referencia estática o de larga duración a una Activity/Context; use applicationContext en su lugar.
  • Los listeners de clases internas, los Handlers con retraso y las coroutines sin ámbito filtran a su propietario; delimite su ámbito y cancélelos.
  • Use DisposableEffect en Compose para anular el registro de callbacks.
  • LeakCanary y el Memory Profiler muestran la cadena de referencias.
  • Establezca límites para las cachés y reduzca el tamaño de los bitmaps.

A continuación: hacer que la aplicación se inicie rápidamente mediante la optimización del inicio y los perfiles de referencia.

Preguntas frecuentes

¿La lección «Fugas de memoria y soluciones» es gratis?

Sí — el texto completo de «Fugas de memoria y soluciones» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de Android Academy, actualiza a CoddyKit PRO. El curso de Android Academy incluye 4 lecciones en total.

¿Qué aprenderé en «Fugas de memoria y soluciones»?

Encuentre y detenga las fugas Practicas Android Academy con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.

¿Necesito experiencia previa para empezar Android Academy?

No se requiere experiencia previa. Android Academy en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 3 de 4.

¿Cuánto tiempo toma la lección «Fugas de memoria y soluciones»?

La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.

¿Puedo escribir y ejecutar código en esta lección de Android Academy?

Sí. Cada lección de Android Academy incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.

Todas las lecciones de este curso

  1. Medición del rendimiento
  2. Cómo controlar la recomposición
  3. Fugas de memoria y soluciones
  4. Perfiles de inicio y de referencia
← Volver a Android Academy