Install
dotnet add package Plugin.Maui.LeakAnalyserPackage ID: Plugin.Maui.LeakAnalyser. Registration is required for leak callbacks and global teardown defaults.
Debug: detect only
using Plugin.Maui.LeakAnalyser;
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder.UseMauiApp<App>();
#if DEBUG
builder.Logging.AddDebug();
builder.UseLeakAnalyser(options =>
{
options.OnLeaked = target => { /* alert / counter */ };
options.DefaultTearDownStrategy = TearDownStrategy.DetectOnly;
});
#endif
return builder.Build();
}
}<ContentPage xmlns:la="clr-namespace:Plugin.Maui.LeakAnalyser;assembly=Plugin.Maui.LeakAnalyser"
la:LeakMonitor.Cascade="True"
la:LeakMonitor.Name="OrdersPage">Debug: detect + disconnect handlers
la:LeakMonitor.Cascade="True"
la:TearDown.Cascade="True"
la:TearDown.Strategy="DisconnectHandlers"Put TearDown after LeakMonitor. DisconnectHandlers is the default DefaultTearDownStrategy when you omit Strategy.
Production: teardown without detection
Detection forces GC and is not for Release. Teardown can stay on:
la:TearDown.Cascade="True"Do not call UseLeakAnalyser or set LeakMonitor.Cascade in Release. Cached pages and tabs still need Suppress.
GC knobs
builder.UseLeakAnalyser(options =>
{
options.DefaultTearDownStrategy = TearDownStrategy.DisconnectHandlers;
options.MaxCollections = 10;
options.MillisecondsBetweenCollections = 200;
});Opt into Compartmentalize per page when DisconnectHandlers is not enough:
la:TearDown.Strategy="Compartmentalize"Compose with Diagnostics
LeakAnalyser is standalone. It does not reference Diagnostics or Observability. Wire callbacks yourself when the host already uses those plugins. Treat a leaked view as a breadcrumb, not a crash.
using Plugin.Maui.Diagnostics;
using Plugin.Maui.LeakAnalyser;
#if DEBUG
builder.Logging.AddDebug();
builder.UseMauiDiagnostics();
builder.UseLeakAnalyser(options =>
{
options.OnLeaked = target =>
{
MauiDiagnostics.TrackEvent($"Leak:{target.Name}");
MauiDiagnostics.TrackException(
new InvalidOperationException($"{target.Name} survived forced GC."));
};
options.OnCollected = target =>
MauiDiagnostics.TrackEvent($"Collected:{target.Name}");
});
#endifRegister Diagnostics first if you want its logger and timeline ready before the first leak callback. Do not call TrackException for every collected target — collection is success. Use OnLeaked only for errors.
Compose with Observability
Observability does not import leak events automatically. If the host already calls UseMauiObservability, keep LeakAnalyser registered beside it and forward OnLeaked into Diagnostics. Observability will pick up those breadcrumbs on the next report export if Diagnostics is part of that pipeline.
builder
.UseMauiApp<App>()
.UseMauiObservability();
#if DEBUG
builder.UseLeakAnalyser(options =>
{
options.OnLeaked = target =>
MauiDiagnostics.TrackEvent($"Leak:{target.Name}");
});
#endifWhat not to wire
| Signal | Why not |
|---|---|
| Forced GC counts as health | GC.Collect is a Debug probe, not a production metric. |
| Every OnCollected as an error | Collection is the healthy outcome. |
| Release LeakMonitor.Cascade | Expensive, and can hide real leaks behind GC noise. |
| Package reference LeakAnalyser → Diagnostics | Keeps this plugin usable without the telemetry suite. |