最近在搞MAUI的时候,遇到了几次程序挂在App.g.i.cs中的这里:
UnhandledException += (sender, e) =>
{
if (global::System.Diagnostics.Debugger.IsAttached)
global::System.Diagnostics.Debugger.Break();
};
这个App.g.i.cs是MAUI自动生成的文件,而且此时的堆栈如下:
可以看到都是系统底层的调用,所以不怎么能try-catch这个异常。
我用一个简单的项目复现了报这个异常的两种方式:
1. 在页面加载完成前导航走
首先创建两个Page,如MainPage.xaml和NewPage1.xaml。并且将它们添加到AppShell.xaml中:
<?xml version="1.0" encoding="UTF-8" ?>
<Shell
x:Class="MAUIAppForTest.AppShell"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MAUIAppForTest"
Shell.FlyoutBehavior="Flyout">
<FlyoutItem Title="New"
Route="NewPage1">
<ShellContent
Title="NewPage1"
ContentTemplate="{DataTemplate local:NewPage1}"/>
</FlyoutItem>
<FlyoutItem x:Name="MainPageItem" Title="Repos"
Route="MainPage">
<ShellContent
Title="Github"
ContentTemplate="{DataTemplate local:MainPage}"/>
</FlyoutItem>
</Shell>
这样程序打开时会默认导航到NewPage1中。随后在NewPage1中如下写:
namespace MAUIAppForTest;
public partial class NewPage1 : ContentPage
{
public NewPage1()
{
InitializeComponent();
}
protected override async void OnNavigatedTo(NavigatedToEventArgs args)
{
base.OnNavigatedTo(args);
//await Task.Delay(500);
await Shell.Current.GoToAsync("//MainPage");
}
}
即当导航到NewPage1后,立刻导航到MainPage。如此运行程序,就会触发最开始提到的异常。
关于这个问题,我猜测是页面没加载完就被导航走了,通过这个[帖子]大家可以了解更多详细信息,里面也有人提到了通过Task.Delay的方式,延迟一会切换页面就可以。虽然我这里写的是Task.Delay(500)
但实际上经过我的测试Task.Delay(1)
就可以避免这个异常。
补充一个[Discussion],也是讨论这个问题的。
2. 用AppShell管理页面的时候使用DI的方式不对
这一点可能是个bug也可能是我用得不对,具体来说,我写了一个AppSettings类,想要通过依赖注入的方式,使得MainPage可以在构造函数中获得一个AppSettings的实例,具体的代码如下:
// MauiProgram.cs:
using MAUIAppForTest.Models;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace MAUIAppForTest;
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
});
builder.Services.AddSingleton<AppSettings>();
// builder.Services.AddTransient<MainPage>();
#if DEBUG
builder.Logging.AddDebug();
#endif
return builder.Build();
}
}
// MainPage.xaml.cs:
using MAUIAppForTest.Models;
namespace MAUIAppForTest;
public partial class MainPage : ContentPage
{
private AppSettings _appSettings;
public MainPage(AppSettings appSettings)
{
InitializeComponent();
_appSettings = appSettings;
_appSettings.Print("MainPage");
}
}
同时将AppShell.xaml中的MainPage移动到NewPage1前面,这样程序启动时默认打开的是MainPage,并在需要构造MainPage时,抛出前面提到的异常。
如果将AppShell.xaml中的NewPage1提到MainPage前面,也即是与第一个例子的逻辑相同,通过NewPage1导航到MainPage中,当然,这显然会报错的:
System.MissingMethodException:
“Cannot dynamically create an instance of type 'MAUIAppForTest.MainPage'.
Reason: No parameterless constructor defined.”
不过这个错很容易理解,就是因为GoToAsync("//MainPage")
调用的是无参的构造函数,而现在MainPage没有无参的构造函数。不过这里也能够猜测到大概是MAUI的DI并没有起到作用,去调用无参的构造函数了,所以报错了。
如果这时候给MainPage加一个无参的构造函数,在其中调用Debug.Write("MainPage");
的话,程序是可以正常运行并输出Debug的。并且如果我不是在MainPage的构造函数中添加AppSettings参数,而是在App的构造函数中添加AppSettings,那程序也能正常运行,其中App.xaml.cs的代码和运行效果如下:
using MAUIAppForTest.Models;
namespace MAUIAppForTest;
public partial class App : Application
{
public App()
{
InitializeComponent();
MainPage = new AppShell();
}
public App(AppSettings settings)
{
settings.Print("App From DI");
InitializeComponent();
MainPage = new AppShell();
}
}
那如何解决这个问题?我从这个[帖子]找到了解决方案,就是将Page也添加到builder.Services
中,即在MauiProgram.cs中添加:builder.Services.AddTransient<MainPage>();
在上面那个帖子中,那个人遇到的错误是No parameterless constructor defined
,然后有人发了几个issue和pr的链接,我看现在已经closed和merged了,那个bug已经修复了,不过好像使用AppShell管理页面的时候,给页面使用DI仍是有些问题的。
DI(Dependence Injection) 依赖注入
详细可参考:[Dependency injection]