Flutter – Detección de fugas de memoria – Guia
Mar 22nd, 2025
Este es el segundo artículo de la serie
Flutter – Detección de fugas de memoria
. Si te perdiste el primero y quieres aprender sobre el funcionamiento interno del Garbage Collector de Dart, puedes hacerlo
.


En este artículo, proporcionaré ejemplos de código sobre cómo liberar manualmente objetos que el Garbage Collector de Dart no manejará automáticamente. Intentaré ser lo más breve posible, con un ejemplo por sección.
Planos para liberar objetos no recolectables
Los ejemplos cubren todos los objetos no recolectables mencionados en el primer artículo, excepto el último, ya que la liberación de bibliotecas de terceros está ligada a la documentación de la biblioteca seleccionada.
Objetos con método dispose
Muchos objetos de Flutter tienen un método
dispose
o
cancel
. Asegúrate primero de verificar si tu objeto potencialmente fugador tiene uno. El AnimationController utilizado en este ejemplo es uno de ellos y está ligado a los tickers del framework. Si estos recursos no se liberan correctamente, pueden causar fugas de memoria.
class MyWidget extends StatefulWidget {
@override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> with SingleTickerProviderStateMixin {
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: Duration(seconds: 1),
)..repeat();
}
@override
void dispose() {
_controller.dispose(); // Dispose the AnimationController
super.dispose();
}
...
}
Variables globales
Las variables globales son accesibles en toda la aplicación y no tienen un fin de ciclo de vida definido. Por eso debes establecerlas en
null
cuando ya no sean necesarias.
class AppState {
static var largeData = List<int>.filled(1000000, 0);
}
void cleanupGlobalVariables() {
AppState.largeData = null; // Free up memory if it is no longer needed
}
Escuchas de eventos/suscripciones
El propósito principal de un stream es transmitir los datos del stream cuando y donde se necesiten. No está ligado a un solo uso, y el Garbage Collector de Dart no puede saber cuándo los datos del stream ya no son necesarios. Cancela las suscripciones a los streams cuando ya no sean necesarias para evitar fugas de memoria.
class MyWidget extends StatefulWidget {
@override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
StreamSubscription<int>? _subscription;
@override
void initState() {
super.initState();
_subscription = someStream.listen((data) {
print('Stream sent data: $data');
});
}
@override
void dispose() {
_subscription?.cancel(); // Cancel the subscription
super.dispose();
}
...
}
Cierres que capturan el contexto de build
El contexto de build de Flutter (
BuildContext
) es un objeto de corta duración y de gran tamaño. Nunca uses
BuildContext
dentro de cierres si el cierre SOBREVIVE al widget. Si necesitas el contexto dentro de un cierre, asigna el contexto a una variable y luego usa esa variable dentro del cierre. El Garbage Collector de Dart recoge automáticamente las variables.
// BAD
Widget build(BuildContext context) {
final printer = () => print(someFunctionUsingContext(context));
usePrinter(printer);
…
}
// GOOD
Widget build(BuildContext context) {
final result = someFunctionUsingContext(context); // variable gets GCed
final printer = () => print(result);
usePrinter(printer);
…
}
Cachés y mapas
La situación con los cachés y mapas es un poco similar a la de las variables globales. Mantienen los objetos incluso cuando ya no los necesitas al mantener referencias fuertes a ellos. Asegurarse de que los cachés y mapas no causen fugas de memoria no deseadas incluye varios enfoques, todos viables y dependientes de tu caso de uso.
- Limpiar el caché/mapa cuando un objeto o todo el caché ya no sea necesario.
cache.remove('key'); // Remove an object
cache.clear(); // Clear the entire cache
- Usar referencias débiles para evitar que los cachés creen referencias fuertes y eviten que el Garbage Collector de Dart lo limpie. Ten en cuenta que tan pronto como nadie use los valores del caché (refiriéndose a ellos fuertemente), se convierten en candidatos para el Garbage Collector de Dart.
final weakCache = WeakMap<Key, Value>();
weakCache[key] = BigObject(); // Doesn’t prevent garbage collection
- Limitar el tamaño de un caché/mapa y eliminar las entradas más antiguas cuando esté lleno.
final cache = LinkedHashMap<String, BigObject>();
void addToCache(String key, BigObject value) {
if (cache.length >= 100) {
cache.remove(cache.keys.first); // Remove the oldest object
}
cache[key] = value;
}
- Usar el mecanismo de caché incorporado de Flutter.
final imageCache = PaintingBinding.instance.imageCache;
imageCache.clear(); // Clear the image cache
Temporizadores y devoluciones de llamada periódicas
Cuando se crea un temporizador, los objetos en su devolución de llamada forman una referencia fuerte a él. Cuando el propósito del temporizador ha terminado, estas referencias fuertes aún existen y, sin una liberación manual, causarán fugas de memoria.
class MyWidget extends StatefulWidget {
@override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
Timer _timer;
@override
void initState() {
super.initState();
_timer = Timer.periodic(Duration(seconds: 1), (timer) {
print("Tick-tock!");
});
}
@override
void dispose() {
_timer.cancel(); // Cancel the timer when the widget is disposed
super.dispose();
}
...
Recursos nativos
Los métodos específicos de la plataforma deben manejarse/liberarse correctamente. Un desarrollador debe manejar manualmente la adquisición y luego la liberación del recurso para evitar fugas de memoria.
class WakeLockScreen extends StatefulWidget {
@override
_WakeLockScreenState createState() => _WakeLockScreenState();
}
class _WakeLockScreenState extends State<WakeLockScreen> {
static const platform = MethodChannel('com.example.wakelock');
@override
void initState() {
super.initState();
_acquireWakeLock();
}
Future<void> _acquireWakeLock() async {
try {
await platform.invokeMethod('acquireWakeLock');
} on PlatformException catch (e) {
print("Failed to acquire WakeLock: ${e.message}");
}
}
Future<void> _releaseWakeLock() async {
try {
await platform.invokeMethod('releaseWakeLock');
} on PlatformException catch (e) {
print("Failed to release WakeLock: ${e.message}");
}
}
@override
void dispose() {
_releaseWakeLock(); // Release the WakeLock when the widget is disposed
super.dispose();
}
...
¿Tu código sigue fugando?
Si crees que has liberado todos los objetos no recolectables, pero tu aplicación sigue fugando, es hora de usar herramientas que te ayuden a detectar dónde está fugando tu aplicación. Primero, veamos la más simple,
.