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

WinUI3依赖注入问题:页面导航始终调用构造函数

WinUI3中Frame导航重复创建页面实例导致内存增长的解决方法

问题根源

Frame.Navigate(Type)方法的默认行为是自动创建传入页面类型的新实例,完全绕过你在DI容器中注册的页面实例——这就是为什么你用AddScoped或AddSingleton注册页面后,每次导航仍会触发构造函数的原因。旧页面实例被留在BackStack(即使手动Clear,Frame内部可能仍持有引用),加上页面绑定的资源、事件未正确释放,最终导致内存持续增长。

解决方案

1. 让Frame复用DI容器中的页面实例

修改NavigationService的NavigateTo方法,跳过Frame.Navigate的自动实例化逻辑,直接从DI获取已注册的页面实例并设置为Frame内容:

public bool NavigateTo(string pageKey, object? parameter = null, bool clearNavigation = false)
{
    var pageType = _pageService.GetPageType(pageKey);
    if (_frame == null || _frame.Content?.GetType() == pageType)
        return false;

    // 从DI容器获取预注册的页面实例
    var pageInstance = App.Host.Services.GetService(pageType);
    if (pageInstance == null)
        return false;

    // 处理导航前的ViewModel生命周期逻辑
    var vmBeforeNavigation = _frame.GetPageViewModel();
    if (vmBeforeNavigation is INavigationAware navigationAware)
    {
        navigationAware.OnNavigatedFrom();
    }

    // 释放当前页面资源(如果实现了IDisposable)
    var currentPage = _frame.Content as IDisposable;
    currentPage?.Dispose();

    // 替换Frame内容,而非使用Navigate方法
    _frame.Content = pageInstance;

    // 清理BackStack并释放其中的页面资源
    if (clearNavigation)
    {
        foreach (var entry in _frame.BackStack)
        {
            if (entry.Content is IDisposable disposable)
            {
                disposable.Dispose();
            }
        }
        _frame.BackStack.Clear();
    }

    // 处理导航后的ViewModel生命周期逻辑
    if (pageInstance is Page page && page.DataContext is INavigationAware targetNavAware)
    {
        targetNavAware.OnNavigatedTo(parameter);
    }

    return true;
}

同时确保页面在DI中注册为Singleton(全局复用)或Scoped(按作用域复用):

// 在App.xaml.cs的ConfigureServices中修改
services.AddSingleton<MainPage>();
services.AddSingleton<SettingsPage>();
// 其他页面同理

2. 实现页面资源释放逻辑

让页面实现IDisposable接口,手动释放非托管资源、解绑事件,避免内存泄漏:

public sealed partial class MainPage : Page, IDisposable
{
    public MainViewModel ViewModel { get; }

    public MainPage()
    {
        ViewModel = App.GetService<MainViewModel>();
        InitializeComponent();
        // 记录绑定的事件,后续在Dispose中解绑
        // ExampleControl.Loaded += ExampleControl_Loaded;
    }

    public void Dispose()
    {
        // 解绑事件
        // ExampleControl.Loaded -= ExampleControl_Loaded;
        // 释放其他资源(如文件句柄、网络连接等)
        GC.SuppressFinalize(this);
    }
}

3. 修正BackStack清理时机

不要在OnNavigated事件中清理BackStack,改为在导航前处理——确保旧页面资源被释放后再清理栈,避免Frame持有无效引用。

额外注意事项

  • Debug模式下的内存增长可能包含调试工具本身的开销,建议用Release模式测试,或使用Visual Studio内存分析工具(快照对比)确认真实泄漏点。
  • 避免在页面中持有静态变量、全局事件引用,这类强引用会导致GC无法回收页面实例。
  • 你的ViewModel复用逻辑是正确的(从DI获取),无需调整,重点放在页面实例的复用和资源释放上。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 21:20:22