Skip to content
This repository has been archived by the owner on May 1, 2024. It is now read-only.

Commit

Permalink
[Android] Fix content pages not Garbage collected (#14717)
Browse files Browse the repository at this point in the history
* Create test to prove that navigated-from content pages are not garbage collected when using Shell

issue #14657

* Fix disposing Shell objects

removing drawer listener (_drawerToggle) allows shell pages to be collected by GC

issue #14657

* Correctly dispose views

Shell TitleView is explicitly set to null, because otherwise pages would not be disposed of. Also views are explicitly removed during disposal.

issue #14657
  • Loading branch information
danielkraut authored Jan 7, 2022
1 parent e95006e commit d59f34f
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
using Xamarin.Forms.CustomAttributes;
using Xamarin.Forms.Internals;
using System;
using System.Threading;
using System.Threading.Tasks;

#if UITEST
using Xamarin.Forms.Core.UITests;
using Xamarin.UITest;
using NUnit.Framework;
#endif

namespace Xamarin.Forms.Controls.Issues
{
#if UITEST
[Category(UITestCategories.Github10000)]
[Category(UITestCategories.Shell)]
[Category(UITestCategories.TitleView)]
#endif
[Preserve(AllMembers = true)]
[Issue(IssueTracker.Github, 14657, "Shell pages are not released from memory", PlatformAffected.Android)]
public class Issue14657 : TestShell
{
static int pageCount = 0;
protected override void Init()
{
Routing.RegisterRoute(nameof(Issue14657_ChildPage), typeof(Issue14657_ChildPage));

var rootPage = CreateRootPage();

AddContentPage(rootPage);
}

ContentPage CreateRootPage()
{
var rootPage = CreateContentPage("Home page");
rootPage.Content = new StackLayout()
{
Children =
{
new Button()
{
Command = new Command(CollectMemory),
Text = "Force GC",
AutomationId = "GC_14657"
},
new Button()
{
Command = new Command(async () => await GoToChild()),
Text = "Go to child page",
AutomationId = "GoToChild_14657"
}
}
};
Shell.SetTitleView(rootPage, new StackLayout()
{
Orientation = StackOrientation.Horizontal,
Children = { new Label { Text = "Root Page" } }
});

return rootPage;
}

public class Issue14657_ChildPage : ContentPage
{
public Issue14657_ChildPage()
{
Interlocked.Increment(ref pageCount);

Content = new StackLayout
{
Children =
{
new Label()
{
Text = $"{pageCount}",
AutomationId = "CountLabel",
TextColor = Color.Black
},
new Button()
{
Command = new Command(CollectMemory),
Text = "Force GC",
AutomationId = "GC_14657"
}
}
};
}

~Issue14657_ChildPage()
{
Interlocked.Decrement(ref pageCount);
}
}

async Task GoToChild()
{
await GoToAsync(nameof(Issue14657_ChildPage));
}

static void CollectMemory()
{
GC.Collect();
GC.WaitForPendingFinalizers();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
GC.WaitForPendingFinalizers();
GC.Collect();
}

#if UITEST
[Test]
public void Issue14657Test()
{
RunningApp.Tap("GoToChild_14657");
RunningApp.WaitForFirstElement("CountLabel")
.AssertHasText("1");
RunningApp.NavigateBack();
RunningApp.Tap("GC_14657");

RunningApp.Tap("GoToChild_14657");
RunningApp.WaitForFirstElement("CountLabel")
.AssertHasText("1");
RunningApp.NavigateBack();
RunningApp.Tap("GC_14657");

RunningApp.Tap("GoToChild_14657");
RunningApp.WaitForFirstElement("CountLabel")
.AssertHasText("1");
RunningApp.NavigateBack();
}

#endif
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -973,6 +973,7 @@
<SubType>Code</SubType>
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)Issue14544.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Issue14657.cs" />
<Compile Include="$(MSBuildThisFileDirectory)_TemplateMarkup.xaml.cs">
<DependentUpon>_TemplateMarkup.xaml</DependentUpon>
<SubType>Code</SubType>
Expand Down
1 change: 1 addition & 0 deletions Xamarin.Forms.Platform.Android/Renderers/ContainerView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ protected override void Dispose(bool disposing)

if (disposing)
{
RemoveAllViews();
_shellViewRenderer?.TearDown();
_view = null;
}
Expand Down
14 changes: 13 additions & 1 deletion Xamarin.Forms.Platform.Android/Renderers/ShellContentFragment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,15 @@ public override AView OnCreateView(LayoutInflater inflater, ViewGroup container,

void Destroy()
{
if (_page != null)
{
var titleView = Shell.GetTitleView(_page);
if (titleView != null)
{
Shell.SetTitleView(_page, null);
}
}

((IShellController)_shellContext.Shell).RemoveAppearanceObserver(this);

if (_shellContent != null)
Expand All @@ -180,6 +189,8 @@ void Destroy()

if (_root is ViewGroup vg)
vg.RemoveView(_shellPageContainer);

_shellPageContainer.Dispose();
}

_renderer?.Dispose();
Expand All @@ -194,6 +205,7 @@ void Destroy()
_root = null;
_renderer = null;
_shellContent = null;
_shellPageContainer = null;
}

protected override void Dispose(bool disposing)
Expand Down Expand Up @@ -223,4 +235,4 @@ public override void OnDestroy()

protected virtual void SetAppearance(ShellAppearance appearance) => _appearanceTracker.SetAppearance(_toolbar, _toolbarTracker, appearance);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,10 @@ protected override void Dispose(bool disposing)
_currentMenuItems?.Clear();
_currentToolbarItems?.Clear();

_drawerLayout.RemoveDrawerListener(_drawerToggle);
_drawerToggle?.Dispose();

_toolbar.RemoveAllViews();
}

_currentMenuItems = null;
Expand Down

0 comments on commit d59f34f

Please sign in to comment.