.NET MAUI Shell绝对路由导航内存泄漏问题求助
.NET MAUI Shell导航后页面内存未释放问题
问题背景
为排查主项目内存问题,我创建了一个简单的.NET MAUI测试项目,遇到Shell路由跳转后页面无法被GC回收的问题。
路由配置
注册了3个绝对路由:
//Main//Main/UserProfile//Main/Login
复现步骤
- 从MainPage导航至Login页面
- 从Login页面导航至UserProfile页面
- 从UserProfile页面返回至Login页面
预期结果
UserProfile页面应被释放并从内存中移除。
实际结果
运行gcdump查看堆视图后,发现UserProfile页面仍驻留在内存中,且确认该页面不在Visual Tree内。
环境
- 测试设备:Android设备
相关代码
AppShell.cs
public partial class AppShell : Shell { public AppShell() { InitializeRouting(); InitializeComponent(); } protected override async void OnHandlerChanged() { base.OnHandlerChanged(); await Shell.Current.GoToAsync("//Main"); } private static void InitializeRouting() { Routing.RegisterRoute($"//Main/{nameof(LoginPage)}", typeof(LoginPage)); Routing.RegisterRoute($"//Main/{nameof(UserProfilePage)}", typeof(UserProfilePage)); } }
AppShell.xaml
<?xml version="1.0" encoding="UTF-8" ?> <Shell x:Class="TestProfile.AppShell" xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:TestProfile" Shell.FlyoutBehavior="Disabled" Title="TestProfile"> <ShellContent Title="Home" ContentTemplate="{DataTemplate local:MainPage}" Route="Main" /> </Shell>
MainViewModel.cs
public partial class MainViewModel:ObservableObject { public MainViewModel() { } [RelayCommand] private async Task GoToLogin() { await Shell.Current.GoToAsync($"//Main/{nameof(LoginPage)}"); } }
UserProfileViewModel.cs
public partial class UserProfileViewModel : ObservableObject { public UserProfileViewModel() { } [RelayCommand] private async Task GoToLogin() { await Shell.Current.GoToAsync($"//Main/{nameof(LoginPage)}"); } }
问题分析与修复方案
核心原因
使用绝对路由跳转到//Main/Login时,Shell会将LoginPage作为Main的新ShellContent替换原有内容,但UserProfilePage的实例可能被Shell导航堆栈或路由注册的强引用持有,无法被GC回收。另外,页面或ViewModel若存在未释放的事件订阅、委托引用,也会导致内存泄漏。
修复方案
改用相对路由返回,避免绝对路由跳转
从UserProfile返回Login时,使用相对返回操作替代绝对路由跳转,让Shell正确管理导航堆栈:[RelayCommand] private async Task GoBackToLogin() { // 相对返回会从导航堆栈移除当前页面,触发正确生命周期事件 await Shell.Current.GoToAsync(".."); }绝对路由跳转不会清理之前页面的引用,而相对返回能让框架正确标记页面实例为可回收状态。
清理未释放的事件订阅
在UserProfilePage的OnDisappearing方法中取消所有事件订阅、Messenger注册等:protected override void OnDisappearing() { base.OnDisappearing(); // 示例:取消MessagingCenter订阅 // MessagingCenter.Unsubscribe<YourMessageType>(this); // 取消其他自定义事件订阅 }调整Shell路由注册结构
改用Shell层级结构定义路由,更符合框架导航逻辑:<ShellContent Route="Main" ContentTemplate="{DataTemplate local:MainPage}"> <ShellContent Route="Login" ContentTemplate="{DataTemplate local:LoginPage}" /> <ShellContent Route="UserProfile" ContentTemplate="{DataTemplate local:UserProfilePage}" /> </ShellContent>这种层级结构能让Shell更好地管理页面实例的创建与销毁。
排查强引用持有情况
使用gcdump的对象引用链功能,查看UserProfilePage被哪些对象持有,针对性解除强引用(比如使用WeakReference包装跨页面引用)。
内容的提问来源于stack exchange,提问作者user12999789
相关产品推荐
相关产品推荐

