Apr 12th, 2025
Este es el tercer artículo de la serie
Flutter – Detección de fugas de memoria
. Si llegaste a este artículo por casualidad y quieres comenzar desde el principio, dirígete
.
Este artículo cubrirá la forma más simple y recomendada de detectar fugas de memoria en Flutter, que es utilizando la herramienta Memory view de Dart DevTools. Este es un tema ampliamente cubierto por la documentación de Flutter, artículos de blog y videos de YouTube, pero esta serie no estaría completa sin él.
La mejor manera de aprender una nueva habilidad y retener el conocimiento adquirido es intentándolo por ti mismo, no solo leyendo sobre ello. Si tu aplicación no tiene fugas y solo estás aprendiendo sobre detección de fugas de memoria en Flutter, te insto a que uses mi configuración de código para seguir el ejemplo. De lo contrario, usa tu aplicación con fugas.
El código a continuación es una aplicación simple de Flutter de dos páginas con un botón que crea temporizadores no recolectables.
import 'dart:async';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Memory Leaks demo app',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.teal),
useMaterial3: true,
),
home: const HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
void _induceMemoryLeak() {
for (int i = 0; i < 100000; i++) {
Timer.periodic(const Duration(seconds: 1), (timer) {
// do nothing
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: const Text("Memory Leaks demo page"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CustomTextButton(onPressed: _induceMemoryLeak, text: "Add 100.000 timers"),
const SizedBox(height: 30),
CustomTextButton(
onPressed: () => Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => const SecondPage())),
text: "Navigate",
),
],
),
),
);
}
}
class SecondPage extends StatelessWidget {
const SecondPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(backgroundColor: Theme.of(context).colorScheme.inversePrimary, title: const Text("Memory Leaks demo page - 2")),
body: Center(
child: CustomTextButton(
onPressed: () => Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => const HomePage())),
text: "Navigate back",
),
),
);
}
}
class CustomTextButton extends StatelessWidget {
const CustomTextButton({super.key, required this.text, required this.onPressed});
final String text;
final Function() onPressed;
@override
Widget build(BuildContext context) {
return TextButton(
style: TextButton.styleFrom(
foregroundColor: Colors.white,
backgroundColor: Colors.teal,
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
onPressed: onPressed,
child: Text(text),
);
}
}
Una aplicación de dos páginas con temporizadores con fugas
Primero lo primero. NUNCA debes buscar fugas de memoria ejecutando la aplicación de Flutter en modo debug, ya que el uso absoluto de memoria puede ser mayor en este modo. Para resultados más precisos, usa el modo
profile
.
Pasos de detección:
Cuando tu aplicación se esté ejecutando en modo profile, abre Dart DevTools y cambia a la pestaña
Memory view
. Esto abre todas las herramientas relacionadas con la memoria que ofrece Dart DevTools.
Luego haz clic en la pestaña
Diff Snapshots
. Esta herramienta te permite tomar snapshots del montón de memoria durante el ciclo de vida de la aplicación y compararlos entre sí. Si recuerdas del
de esta serie, los objetos con fugas no son recolectados por el garbage collector, y en teoría, deberías detectarlos aquí.
Dart DevTools - Pestaña Memory View
Tan pronto como tu aplicación se estabilice, necesitas crear un snapshot del montón de memoria. Este servirá como base para la comparación de memoria.
Dart DevTools - Cómo crear un snapshot del montón de memoria
Como no estás completamente seguro de qué está causando tus fugas de memoria, este paso trata de inducir fugas de memoria. Debes usar tu aplicación de la misma manera que lo hacen tus usuarios finales. Algo durante su operación está causando las fugas. Si estás siguiendo este artículo usando mi código proporcionado, ahora es el momento de presionar el botón
Add 100.000 timers
y luego el botón
Navigate
para ir a la siguiente página. Al volver a la página original, el garbage collector debería haber recolectado los temporizadores si se liberaron correctamente, pero no lo hizo.
Aplicación con fugas
Ahora, tu aplicación probablemente tiene fugas, y es hora de crear un nuevo snapshot del montón de memoria que se comparará con el snapshot inicial. Antes de hacer el snapshot, fuerza el Garbage Collection presionando el botón
GC
dentro de la pestaña Memory View de Dart DevTools.
Dart DevTools - Forzar la recolección de basura y crear un nuevo snapshot
Antes de comparar los snapshots, asegúrate de configurar los filtros de clase para mostrar todos los tipos de objetos. Solo así puedes estar "seguro" de ver todos los objetos.
Dart DevTools - Filtrado de tipos de objetos
Finalmente, comparas tu último snapshot del montón con el original.
Dart DevTools - Comparación de snapshots
Observando la diferencia entre los dos snapshots, podemos ver que, en mi caso, la fuga fue causada efectivamente por temporizadores no recolectados. Puedes ver la cantidad de temporizadores creados, que ninguno fue liberado de la memoria y que ahora ocupan 7.6MB adicionales de memoria.
Si tuviste la suerte de encontrar tu(s) culpable(s), es hora de monitorear el/los objeto(s) e intentar encontrar el origen de la fuga en tu app. Si usas mi código, ya conoces el origen, pero en la vida real será más difícil. Te presento la pestaña
Trace Instances
.
Al cambiar a la pestaña Trace Instances, debes configurar filtros para las clases de objetos que quieres observar y marcar la casilla
Trace
para que comience el rastreo. Luego debes usar tu app como lo hiciste en la pestaña
Diff Snapshots
entre tus dos snapshots. Cuando estés razonablemente seguro de que la fuga ocurrió, presiona
Refresh
.
Dart DevTools - Rastreo de clases con fugas
Cuando cambie el delta de la clase, es hora de analizar. Haz clic en la clase y luego en
Expand All
para ver las trazas. Lee cuidadosamente el árbol de llamadas para encontrar el origen de tu fuga.
Dart DevTools - Origen de los temporizadores con fugas encontrado
Esta herramienta no es omnipotente. Tu app podría seguir con fugas sin que la herramienta haya ayudado. Esto ocurre por varias razones:
Recuerda: si las técnicas anteriores no ayudaron, tu fuga podría ser sutil. Es hora de ponerte el sombrero de detective y probar la siguiente herramienta de mi cinturón de utilidades: el Dart Leak Tracker. Esta herramienta aún está en desarrollo y no se ha lanzado oficialmente, pero te mostraré cómo usarla en el
próximo artículo
de la serie
(próximamente)
.