我创建了一个简单的 MAUI 项目来对内置 MAUI AppShell 导航进行故障排除。
我有一个
MainPage
(绑定到 MainPageViewModel
),其 CollectionView
为 ItemViewModel
。
我在 XAML 中添加了一个手势行为,以响应双击每个呈现的 ItemViewModel
,这将触发导航到我的第二页:SelectPage
。
我已经以相对方式完成了导航的路由,因此我可以使用导航栏中的内置返回按钮返回。
我在两个页面之间来回测试此导航,直到它在 App.g.I.cs 生成的文件中出现
Microsoft.UI.Xaml.Controls.Frame.NavigationFailed was unhandled.
未处理的异常。
这可以通过任何一种导航方式发生(转到我的第二页或返回到我的主页)。 此异常在一段时间后发生,但没有特殊模式(例如可能是第 5 次或第 13 次导航)。
(请注意,当使用ScreenToGif在视频上记录此问题时,到目前为止它总是在第二次导航时崩溃?!)
App.g.i.cs 中的第二个调试器中断是关于关闭应用程序之前的
System.Runtime.InteropServices.COMException
异常。
这是我在 GitHub 上的简单项目,用于代码审查。
这是我的视频来说明这个问题
最后显示的堆栈跟踪是:
at WinRT.ExceptionHelpers.<ThrowExceptionForHR>g__Throw|38_0(Int32 hr)
at ABI.Microsoft.UI.Xaml.Controls.IFrameMethods.Navigate(IObjectReference _obj, Type sourcePageType, Object parameter, NavigationTransitionInfo infoOverride)
at Microsoft.Maui.Platform.StackNavigationManager.NavigateTo(NavigationRequest args)
at Microsoft.Maui.CommandMapper.InvokeCore(String key, IElementHandler viewHandler, IElement virtualView, Object args)
at Microsoft.Maui.Controls.Handlers.ShellSectionHandler.SyncNavigationStack(Boolean animated, NavigationRequestedEventArgs e)
at Microsoft.Maui.Controls.Handlers.ShellSectionHandler.OnNavigationRequested(Object sender, NavigationRequestedEventArgs e)
at Microsoft.Maui.Controls.ShellSection.InvokeNavigationRequest(NavigationRequestedEventArgs args)
at Microsoft.Maui.Controls.ShellSection.OnPushAsync(Page page, Boolean animated)
at Microsoft.Maui.Controls.ShellSection.PushStackOfPages(List`1 pages, Nullable`1 animate)
at Microsoft.Maui.Controls.ShellSection.GoToAsync(ShellNavigationRequest request, ShellRouteParameters queryData, IServiceProvider services, Nullable`1 animate, Boolean isRelativePopping)
at Microsoft.Maui.Controls.ShellNavigationManager.GoToAsync(ShellNavigationParameters shellNavigationParameters, ShellNavigationRequest navigationRequest)
at TestinMAUIPageNavigationPerf.Sources.ViewModels.ItemViewModel.SelectItem()
请注意,当我的第二页根目录中没有元素时
<ContentPage>
它似乎不会崩溃(至少在我测试它的很长一段时间内)。
但仅仅在内部添加一个简单的 <Label Text="TEST!"/>
就会再次崩溃。
编辑 我简化了代码,只在
Button
中有一个 MainPage
来触发导航,在 Label
上有一个 SelectPage
。在相同的情况下,应用程序仍然会崩溃(在 2 个页面之间来回)。
为了参考和满足您的好奇心,这里有这个超级简化版本的视频,但最终仍然崩溃。
AddTransient
的建议。不管怎样,它通常会在大约 5 个周期内引发
NavigationFailed
异常。使用循环重现 Bug
public partial class MainPage : ContentPage
{
.
.
.
protected override async void OnNavigatedTo(NavigatedToEventArgs args)
{
base.OnNavigatedTo(args);
#if SELF_TEST
await Task.Delay(AppShell.TestInterval);
// After first 'long' setup interval, test at smaller increments.
AppShell.TestInterval = TimeSpan.FromSeconds(1);
await Shell.Current.GoToAsync(nameof(SelectPage));
Debug.WriteLine($"Count = {_debugCount++}");
#endif
}
int _debugCount = 1;
}
public SelectPage()
{
InitializeComponent();
}
protected override
async // Added for test
void OnNavigatedTo(NavigatedToEventArgs args)
{
base.OnNavigatedTo(args);
Task.Delay(1).Wait(); // < Per original design
BindingContext = MauiProgram.MainPage?.SelectedItemViewModel;
#if SELF_TEST
await Task.Delay(AppShell.TestInterval);
_ = Shell.Current.GoToAsync($"///{nameof(MainPage)}");
#endif
}
ItemViewModel
中选择随机
Items
并使用它来调用
SelectItem
。
克隆:windows-machine-successful-test
SelectItem
命令移至
MainPageViewModel
:
public partial class MainPageViewModel : ObservableObject
{
public static ItemViewModel? SelectedItemViewModel { get; set; }
[RelayCommand]
private async Task SelectItem(ItemViewModel item)
{
SelectedItemViewModel = item;
try
{
if (App.Current?.MainPage?.Handler != null)
{
await Shell.Current.GoToAsync(nameof(SelectPage));
}
}
catch (Exception e)
{
Debug.Fail(e.Message);
}
}
public ItemViewModel[] Items { get; } = new[]
{
new ItemViewModel{ Title = "One" },
new ItemViewModel{ Title = "Two" },
new ItemViewModel{ Title = "Three" },
new ItemViewModel{ Title = "Four" },
new ItemViewModel{ Title = "Five" },
};
}
这样做时,SelectPage
需要做的就是在
OnAppearing
方法中拉取当前的BC。
public partial class SelectPage : ContentPage
{
public SelectPage()
{
InitializeComponent();
}
protected override void OnAppearing()
{
base.OnAppearing();
if(MainPageViewModel.SelectedItemViewModel is ItemViewModel valid)
{
BindingContext = valid;
}
}
#if SELF_TEST
protected override async void OnNavigatedTo(NavigatedToEventArgs args)
{
base.OnNavigatedTo(args);
await Task.Delay(AppShell.TestInterval);
if (Handler != null)
{
// Discard task to keep self-test stack from creeping from recursion
_ = Shell.Current.GoToAsync($"//{nameof(MainPage)}");
}
}
#endif
}
这反映了对 MainPage.xaml 的这些更改:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:views="clr-namespace:TestinMAUIPageNavigationPerf.Sources.Views"
xmlns:viewmodels="clr-namespace:TestinMAUIPageNavigationPerf.Sources.ViewModels"
x:Class="TestinMAUIPageNavigationPerf.Sources.Views.MainPage"
x:DataType="viewmodels:MainPageViewModel">
<!--Set BC here with x:Ref="Page"-->
<ContentPage.BindingContext>
<viewmodels:MainPageViewModel x:Name="Page"/>
</ContentPage.BindingContext>
<ScrollView>
.
.
.
<CollectionView>
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="viewmodels:ItemViewModel">
<Border>
.
.
.
<Border.GestureRecognizers>
<TapGestureRecognizer
NumberOfTapsRequired="2"
Command="{Binding SelectItemCommand, Source={x:Reference Page }}"
CommandParameter="{Binding .}"/>
</Border.GestureRecognizers>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
.
.
.
</ScrollView>
</ContentPage>
MainPage 后面的代码中唯一添加的功能是 SELF_TEST 块。
public partial class MainPage : ContentPage
{
public MainPage() => InitializeComponent();
new MainPageViewModel BindingContext =>(MainPageViewModel)base.BindingContext;
#if SELF_TEST
protected override async void OnNavigatedTo(NavigatedToEventArgs args)
{
base.OnNavigatedTo(args);
await Task.Delay(AppShell.TestInterval);
// After for optional 'long' setup interval for the first
// iteration, followed by test at smaller increments.
AppShell.TestInterval = TimeSpan.FromSeconds(1);
Debug.WriteLine($"Count = {_debugCount++}");
var randoBC = BindingContext.Items[_rando.Next(BindingContext.Items.Length)];
BindingContext.SelectItemCommand.Execute(randoBC);
}
int _debugCount = 1;
Random _rando = new Random(Seed: 1);
#endif
}
小改动