I have just found that enabling XAML Hot Reload keeps the pages in memory, so once disabled the pages are released.
How to stop memory leak when popping pages
I am trying to track down memory leaks in Xamarin forms (Android).
To illustrate the issue I have created the the sample project using Visual Studio 2019 and selecting the Flyout template for Android.
I have created a class to check for weak references as follows:
public static class Refs
{
private static readonly List<WeakReference> _refs = new List<WeakReference>();
public static readonly BindableProperty IsWatchedProperty = BindableProperty.CreateAttached("IsWatched", typeof(bool), typeof(Refs), false, propertyChanged: OnIsWatchedChanged);
public static bool GetIsWatched(BindableObject obj)
{
return (bool)obj.GetValue(IsWatchedProperty);
}
public static void SetIsWatched(BindableObject obj, bool value)
{
obj.SetValue(IsWatchedProperty, value);
}
private static void OnIsWatchedChanged(BindableObject bindable, object oldValue, object newValue)
{
AddRef(bindable);
}
public static void AddRef(object p)
{
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
_refs.Add(new WeakReference(p));
foreach (var ref1 in _refs)
{
if (ref1.IsAlive)
{
var obj = ref1.Target;
Debug.WriteLine("IsAlive: " + obj.GetType().Name);
}
else
{
Debug.WriteLine("IsAlive: False");
}
}
Debug.WriteLine("---------------");
}
}
}
I added Refs.AddRef(this); in all views and viewmodel constructors.
To release the model for a new item I added the following code to NewItemPage.xaml.cs
protected void DisposeBindingContext()
{
if (BindingContext is IDisposable disposableBindingContext)
{
disposableBindingContext.Dispose();
BindingContext = null;
}
}
protected override void OnParentSet()
{
base.OnParentSet();
if (Parent == null)
{
//Clear a bunch of bindings or dispose of ViewModel objects
BindingContext = null;
this.DisposeBindingContext();
}
}
When run, output window shows the page is kept alive.
[0:] ---------------
01-18 14:11:55.696 I/art (22155): Starting a blocking GC Explicit
01-18 14:11:55.713 I/art (22155): Explicit concurrent mark sweep GC freed 95(3016B) AllocSpace objects, 2(64KB) LOS objects, 64% free, 4MB/12MB, paused 179us total 16.566ms
01-18 14:11:55.714 D/Mono (22155): GC_TAR_BRIDGE bridges 388 objects 450 opaque 1 colors 388 colors-bridged 388 colors-visible 388 xref 4 cache-hit 0 cache-semihit 0 cache-miss 0 setup 0.08ms tarjan 0.19ms scc-setup 0.10ms gather-xref 0.01ms xref-setup 0.00ms cleanup 0.08ms
01-18 14:11:55.714 D/Mono (22155): GC_BRIDGE: Complete, was running for 19.45ms
01-18 14:11:55.714 D/Mono (22155): GC_MAJOR: (user request) time 14.11ms, stw 15.03ms los size: 1024K in use: 173K
01-18 14:11:55.714 D/Mono (22155): GC_MAJOR_SWEEP: major size: 5280K in use: 3989K
01-18 14:11:55.732 I/art (22155): Starting a blocking GC Explicit
01-18 14:11:55.749 I/art (22155): Explicit concurrent mark sweep GC freed 3(72B) AllocSpace objects, 0(0B) LOS objects, 64% free, 4MB/12MB, paused 179us total 16.626ms
01-18 14:11:55.750 D/Mono (22155): GC_TAR_BRIDGE bridges 388 objects 450 opaque 1 colors 388 colors-bridged 388 colors-visible 388 xref 4 cache-hit 0 cache-semihit 0 cache-miss 0 setup 0.08ms tarjan 0.17ms scc-setup 0.13ms gather-xref 0.01ms xref-setup 0.00ms cleanup 0.08ms
01-18 14:11:55.750 D/Mono (22155): GC_BRIDGE: Complete, was running for 19.54ms
01-18 14:11:55.750 D/Mono (22155): GC_MAJOR: (user request) time 13.32ms, stw 15.13ms los size: 1024K in use: 173K
01-18 14:11:55.750 D/Mono (22155): GC_MAJOR_SWEEP: major size: 5280K in use: 3977K
[0:] IsAlive: AboutViewModel
[0:] IsAlive: AboutPage
[0:] IsAlive: ItemsViewModel
[0:] IsAlive: ItemsPage
[0:] IsAlive: False
[0:] IsAlive: NewItemPage
[0:] IsAlive: False
[0:] IsAlive: NewItemPage
[0:] IsAlive: False
[0:] IsAlive: NewItemPage
[0:] IsAlive: False
[0:] IsAlive: NewItemPage
[0:] IsAlive: NewItemViewModel
[0:] IsAlive: NewItemPage
[0:] ---------------
01-18 14:12:00.162 W/IInputConnectionWrapper(22155): finishComposingText on inactive InputConnection
01-18 14:12:01.684 W/IInputConnectionWrapper(22155): finishComposingText on inactive InputConnection
Thread finished: <Thread Pool> #7
The thread 0x7 has exited with code 0 (0x0).
Can anyone advise how the page can be freed from memory?