Flutter – Rilevamento delle perdite di memoria – Linee guida
Mar 22nd, 2025
Questo è il secondo articolo della serie
Flutter – Rilevamento delle perdite di memoria
. Se ti sei perso il primo e vuoi saperne di più sul funzionamento interno del Garbage Collector di Dart, puoi farlo
.


In questo articolo, fornirò esempi di codice su come liberare manualmente oggetti che il Garbage Collector di Dart non gestirà automaticamente. Cercherò di essere il più breve possibile, con un esempio per sezione.
Modelli per liberare oggetti non raccoglibili
Gli esempi coprono tutti gli oggetti non raccoglibili menzionati nel primo articolo, tranne l'ultimo, poiché la liberazione delle librerie di terze parti è legata alla documentazione della libreria selezionata.
Oggetti con metodo dispose
Molti oggetti Flutter hanno un metodo
dispose
o
cancel
. Assicurati prima di controllare se l'oggetto che potrebbe causare perdite di memoria ne ha uno. L'AnimationController utilizzato in questo esempio è uno di questi ed è legato ai ticker del framework. Se queste risorse non vengono rilasciate correttamente, possono causare perdite di 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();
}
...
}
Variabili globali
Le variabili globali sono accessibili in tutta l'app e non hanno una fine del ciclo di vita definita. Ecco perché devi impostarle su
null
quando non sono più necessarie.
class AppState {
static var largeData = List<int>.filled(1000000, 0);
}
void cleanupGlobalVariables() {
AppState.largeData = null; // Free up memory if it is no longer needed
}
Ascoltatori di eventi/sottoscrizioni
Lo scopo principale di uno stream è quello di inviare i dati dello stream quando e dove sono necessari. Non è legato a un singolo uso, e il Garbage Collector di Dart non può sapere quando i dati dello stream non sono più necessari. Annulla le sottoscrizioni agli stream quando non sono più necessarie per prevenire perdite di 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();
}
...
}
Chiusure che catturano il contesto di build
Il contesto di build di Flutter (
BuildContext
) è un oggetto di breve durata e di grandi dimensioni. Non usare mai
BuildContext
all'interno di chiusure se la chiusura SOPRAVVIVE al widget. Se hai bisogno del contesto all'interno di una chiusura, assegna il contesto a una variabile e poi usa quella variabile all'interno della chiusura. Il Garbage Collector di Dart raccoglie automaticamente le variabili.
// 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);
…
}
Cache e mappe
La situazione con le cache e le mappe è un po' simile a quella delle variabili globali. Mantengono gli oggetti anche quando non sono più necessari mantenendo riferimenti forti a essi. Assicurarsi che le cache e le mappe non causino perdite di memoria indesiderate include diversi approcci, tutti validi e dipendenti dal caso d'uso.
- Svuotare la cache/mappa quando un oggetto o l'intera cache non è più necessaria
cache.remove('key'); // Remove an object
cache.clear(); // Clear the entire cache
- Usare riferimenti deboli per evitare che le cache creino riferimenti forti e impediscano al Garbage Collector di Dart di pulirla. Nota che non appena nessuno usa i valori della cache (riferendosi ad essi fortemente), diventano candidati per il Garbage Collector di Dart.
final weakCache = WeakMap<Key, Value>();
weakCache[key] = BigObject(); // Doesn’t prevent garbage collection
- Limitare la dimensione di una cache/mappa e rimuovere le voci più vecchie quando è piena.
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;
}
- Usare il meccanismo di caching integrato di Flutter.
final imageCache = PaintingBinding.instance.imageCache;
imageCache.clear(); // Clear the image cache
Timer e callback periodici
Quando viene creato un timer, gli oggetti nel suo callback formano un riferimento forte ad esso. Quando lo scopo del timer è superato, questi riferimenti forti esistono ancora e, senza una liberazione manuale, causeranno perdite di 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();
}
...
Risorse natives
I metodi specifici della piattaforma devono essere gestiti/liberati correttamente. Uno sviluppatore deve gestire manualmente l'acquisizione e poi il rilascio della risorsa per prevenire perdite di 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();
}
...
Il tuo codice sta ancora perdendo memoria?
Se credi di aver liberato tutti gli oggetti non raccoglibili, ma la tua app sta ancora perdendo memoria, è il momento di usare strumenti che ti aiutano a rilevare dove la tua app sta perdendo memoria. Prima di tutto, vediamo il più semplice,
.