You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET MAUI Shell绝对路由导航内存泄漏问题求助

.NET MAUI Shell导航后页面内存未释放问题

问题背景

为排查主项目内存问题,我创建了一个简单的.NET MAUI测试项目,遇到Shell路由跳转后页面无法被GC回收的问题。

路由配置

注册了3个绝对路由:

  • //Main
  • //Main/UserProfile
  • //Main/Login

复现步骤

  1. 从MainPage导航至Login页面
  2. 从Login页面导航至UserProfile页面
  3. 从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若存在未释放的事件订阅、委托引用,也会导致内存泄漏。

修复方案

  1. 改用相对路由返回,避免绝对路由跳转
    从UserProfile返回Login时,使用相对返回操作替代绝对路由跳转,让Shell正确管理导航堆栈:

    [RelayCommand]
    private async Task GoBackToLogin()
    {
        // 相对返回会从导航堆栈移除当前页面,触发正确生命周期事件
        await Shell.Current.GoToAsync("..");
    }
    

    绝对路由跳转不会清理之前页面的引用,而相对返回能让框架正确标记页面实例为可回收状态。

  2. 清理未释放的事件订阅
    在UserProfilePage的OnDisappearing方法中取消所有事件订阅、Messenger注册等:

    protected override void OnDisappearing()
    {
        base.OnDisappearing();
        // 示例:取消MessagingCenter订阅
        // MessagingCenter.Unsubscribe<YourMessageType>(this);
        // 取消其他自定义事件订阅
    }
    
  3. 调整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更好地管理页面实例的创建与销毁。

  4. 排查强引用持有情况
    使用gcdump的对象引用链功能,查看UserProfilePage被哪些对象持有,针对性解除强引用(比如使用WeakReference包装跨页面引用)。

内容的提问来源于stack exchange,提问作者user12999789

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 10:42:46