Flutter – Rilevamento delle perdite di memoria – Dart DevTools
Apr 12th, 2025
Questo è il terzo articolo della serie
Flutter – Rilevamento delle perdite di memoria
. Se ti sei imbattuto in questo articolo per caso e vuoi iniziare dall'inizio, vai
.
Questo articolo coprirà il modo più semplice e consigliato per rilevare le perdite di memoria in Flutter, ovvero utilizzando lo strumento Memory View di Dart DevTools. Questo è un argomento ampiamente trattato nella documentazione di Flutter, in articoli di blog e video su YouTube, ma questa serie non sarebbe completa senza di esso.
Configurazione del codice
Il modo migliore per imparare una nuova abilità e conservare le conoscenze apprese è provare a farlo da soli, non solo leggere. Se la tua app non ha perdite e stai solo imparando a rilevare le perdite di memoria in Flutter, ti invito a utilizzare il mio codice per seguire l'esempio. Altrimenti, usa la tua app che presenta perdite.
Il codice seguente è una semplice app Flutter a due pagine con un pulsante che crea timer non raccoglibili.
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),
);
}
}
Codice dell'app a due pagine con timer che causano perdite
Rilevamento delle perdite di memoria con Dart DevTools
NON devi MAI cercare perdite di memoria eseguendo l'app Flutter in modalità debug, poiché l'utilizzo effettivo della memoria potrebbe essere più alto in questa modalità. Per risultati più precisi, usa la modalità
profile
.
Passaggi per il rilevamento:
- Passa alla scheda Memory View e seleziona la scheda Diff Snapshots
- Crea uno snapshot dell'heap di memoria
- Usa l'applicazione
- Crea un altro snapshot dell'heap di memoria
- Confronta per trovare gli oggetti che perdono memoria
Passo 1: Passa a Memory View e seleziona Diff Snapshots
Quando la tua app è in esecuzione in modalità profile, apri Dart DevTools e passa alla scheda
Memory View
. Qui troverai tutti gli strumenti relativi alla memoria offerti da Dart DevTools.
Quindi, clicca sulla scheda
Diff Snapshots
. Questo strumento ti consente di acquisire snapshot dell'heap di memoria durante il ciclo di vita dell'app e confrontarli tra loro. Se ricordi dal
di questa serie, gli oggetti che perdono memoria non vengono raccolti dal garbage collector e, in teoria, dovresti trovarli qui.


Dart DevTools - Scheda Memory View
Dart DevTools - Scheda Memory View
Passo 2: Crea uno snapshot dell'heap di memoria
Non appena la tua app si stabilizza, crea uno snapshot dell'heap di memoria. Questo servirà come base per il confronto.


Dart DevTools - Come creare uno snapshot dell'heap di memoria
Dart DevTools - Come creare uno snapshot dell'heap di memoria
Passo 3: Usa l'applicazione
Poiché non sei sicuro di cosa stia causando le perdite di memoria, questo passaggio consiste nel cercare di indurle. Usa l'app come farebbero gli utenti finali. Se stai seguendo questo articolo con il mio codice, ora è il momento di premere il pulsante
Add 100.000 timers
e poi
Navigate
per passare alla pagina successiva. Tornando alla pagina originale, il garbage collector avrebbe dovuto eliminare i timer se fossero stati rilasciati correttamente, ma non lo ha fatto.


Applicazione con perdite
Applicazione con perdite
Passo 4: Crea un altro snapshot dell'heap
Ora che l'app probabilmente perde memoria, è il momento di creare un nuovo snapshot da confrontare con quello iniziale. Prima di farlo, forza il Garbage Collection premendo il pulsante
GC
in Dart DevTools.


Dart DevTools - Forzare la garbage collection e creare un nuovo snapshot
Dart DevTools - Forzare la garbage collection e creare un nuovo snapshot
Passo 5: Confronta per trovare gli oggetti che perdono
Prima di confrontare gli snapshot, imposta i filtri delle classi per visualizzare tutti i tipi di oggetti. Solo così potrai essere "sicuro" di vedere tutto.


Dart DevTools - Filtraggio dei tipi di oggetti
Dart DevTools - Filtraggio dei tipi di oggetti
Infine, confronta l'ultimo snapshot con quello originale.


Dart DevTools - Confronto degli snapshot
Dart DevTools - Confronto degli snapshot
Osservando la differenza tra i due snapshot, possiamo vedere che, nel mio caso, la perdita era effettivamente causata da timer non raccolti. Puoi vedere quanti timer sono stati creati, che nessuno è stato rilasciato dalla memoria e che occupano
7,6 MB
in più.
Cosa fare se hai trovato il colpevole?
Se sei riuscito a individuare la causa della perdita, è il momento di monitorare gli oggetti e cercare l'origine del problema. Se stai usando il mio codice, sai già dove si trova, ma in un'app reale sarà più complicato. Passiamo alla scheda
Trace Instances
.
Dopo aver selezionato questa scheda, imposta i filtri per le classi degli oggetti che vuoi analizzare e seleziona la casella
Trace
per avviare il tracciamento. Quindi, usa l'app come hai fatto nella scheda
Diff Snapshots
tra i due snapshot. Quando sei sicuro che la perdita si è verificata, premi
Refresh
.


Dart DevTools - Tracciamento delle classi con perdite
Dart DevTools - Tracciamento delle classi con perdite
Quando il delta della classe cambia, inizia l'analisi. Clicca sulla classe e poi su
Expand All
per visualizzare le tracce. Leggi attentamente l'albero delle chiamate per trovare l'origine della perdita.


Dart DevTools - Origine dei timer con perdite trovata
Dart DevTools - Origine dei timer con perdite trovata
La tua app perde ancora memoria?
Questo strumento non è infallibile. La tua app potrebbe ancora perdere memoria senza che lo strumento abbia aiutato a trovare la causa. Questo accade per diversi motivi:
- Ritenzioni indirette: Oggetti mantenuti tramite callback, chiusure o meccanismi interni del framework potrebbero non mostrare percorsi chiari.
- Risorse non chiuse: StreamSubscription, AnimationController o ScrollController dimenticati possono causare perdite senza tracce evidenti.
- Riferimenti statici/globali: Oggetti memorizzati in variabili statiche o cache globali non appariranno come perdite (sono trattenuti intenzionalmente, ma potrebbero essere errori).
- Plugin di terze parti: Le perdite in librerie esterne potrebbero non essere visibili nel tracciamento.
- Grafici di oggetti complessi: Se un oggetto è trattenuto attraverso più livelli (widget, provider, stream), Dart DevTools potrebbe non evidenziare la causa principale.
Se questi metodi non hanno funzionato, la perdita potrebbe essere più subdola. È il momento di indossare il cappello da detective e provare il prossimo strumento: Dart Leak Tracker. Questo strumento è ancora in sviluppo e non è stato rilasciato ufficialmente, ma nel
prossimo articolo
ti mostrerò come usarlo
(a breve)
.