`PageNavigationService` 类的详细代码注释

发布时间:2026/6/12 7:47:09

`PageNavigationService` 类的详细代码注释 下面是对PageNavigationService类的详细代码注释。该类是 Prism 框架中用于 MAUI 应用的页面导航服务实现了INavigationService和IRegistryAware接口。它负责处理所有导航逻辑包括前进、后退、返回到根、绝对/相对 URI 导航、Tab 选择等并集成了视图注册、事件聚合、窗口管理等。1. 命名空间与引用usingSystem.Text.RegularExpressions;usingSystem.Web;usingPrism.Common;usingPrism.Dialogs;usingPrism.Events;usingPrism.Mvvm;usingApplicationMicrosoft.Maui.Controls.Application;usingXamlTabPrism.Navigation.Xaml.TabbedPage;引入必要的系统库和 Prism 内部工具。System.Web用于HttpUtility.UrlDecode解码 URL 参数。XamlTab别名用于处理 TabbedPage 的 XAML 附加属性。2. 类定义与静态字段publicclassPageNavigationService:INavigationService,IRegistryAware{privatestaticreadonlySemaphoreSlim_semaphorenew(1,1);privatestaticreadonlyTimeSpan_minTimeBetweenNavigationsTimeSpan.FromMilliseconds(150);privatestaticDateTime_lastNavigate;internalconststringRemovePageRelativePath../;internalconststringRemovePageInstruction__RemovePage/;internalconststringRemovePageSegment__RemovePage;_semaphore信号量用于确保一次只有一个导航请求在执行避免并发操作导致的页面栈冲突。_minTimeBetweenNavigations和_lastNavigate强制两次导航之间至少间隔 150 毫秒保证 UI 刷新完成防止快速连续导航出错。三个常量用于支持“移除页面”的特殊导航指令模拟相对路径../的作用。internalstaticPageNavigationSourceNavigationSource{get;set;}PageNavigationSource.Device;静态属性表示当前导航的来源设备用户手势还是导航服务代码用于某些内部判断。3. 依赖注入的字段与 Window 属性privatereadonlyIContainerProvider_container;protectedreadonlyIWindowManager_windowManager;protectedreadonlyIPageAccessor_pageAccessor;protectedreadonlyIEventAggregator_eventAggregator;_containerIoC 容器用于解析视图和注册表。_windowManager窗口管理器用于获取、打开或关闭应用窗口。_pageAccessor页面访问器提供当前作用域下的Page对象通常是当前显示的页面。_eventAggregator事件聚合器用于发布导航请求事件NavigationRequestEvent。privateWindow_window;protectedWindowWindow{get{if(_windowisnull_pageAccessor.Pageisnotnull){_window_pageAccessor.Page.GetParentWindow();}else{_window_windowManager.Windows.OfTypePrismWindow().FirstOrDefault();}return_window;}}Window属性延迟获取当前导航服务关联的窗口。优先从当前页面向上查找父窗口否则从窗口管理器中获取第一个PrismWindow。4. 视图注册表属性publicIViewRegistryRegistry_container.ResolveINavigationRegistry();每次访问时从容器解析INavigationRegistry实际是IViewRegistry因为模块可能在服务创建后才注册视图。5. 构造函数publicPageNavigationService(IContainerProvidercontainer,IWindowManagerwindowManager,IEventAggregatoreventAggregator,IPageAccessorpageAccessor)初始化依赖项无其他逻辑。6. 公开导航方法6.1GoBackAsyncpublicvirtualasyncTaskINavigationResultGoBackAsync(INavigationParametersparameters)功能返回导航栈中的上一页。流程调用WaitForPendingNavigationRequests()等待信号量并确保时间间隔。进入GoBackInternalAsync实际执行返回逻辑。最后无论成功或失败都会更新_lastNavigate、重置NavigationSource并释放信号量。GoBackInternalAsync详解获取当前页面page。向参数中添加NavigationMode.Back标记。调用MvvmHelpers.CanNavigateAsync检查当前页面的IConfirmNavigation是否可以返回。如果当前有打开的对话框IDialogContainer.DialogStack则关闭最上层对话框并返回成功。判断是否当前页面是根页面IsRoot如果是则调用SendAppToBackground将应用置于后台仅 Android 有效。确定是否使用模态返回UseModalGoBack然后调用DoPop执行页面弹出。若弹出成功触发OnNavigatedFrom、OnNavigatedTo并销毁弹出的页面。最后发布导航请求事件并返回结果。6.2GoBackToAsyncpublicvirtualasyncTaskINavigationResultGoBackToAsync(stringviewName,INavigationParametersparameters)功能返回到导航栈中指定名称的页面该页面必须在栈中。流程等待信号量。检查当前页面是否可以导航离开。获取当前导航栈的完整列表反转顺序找到第一个匹配viewName的页面作为目标页面。从栈中移除目标页面之后的所有页面不包括目标页面和当前页面之间并调用Navigation.RemovePage移除它们。执行PopAsync返回到目标页面。遍历所有被移除的页面调用OnNavigatedFrom并销毁。调用OnNavigatedTo通知目标页面已导航到。注意此方法要求当前页面位于NavigationPage内部即具有导航栈。6.3GoBackToRootAsyncpublicvirtualasyncTaskINavigationResultGoBackToRootAsync(INavigationParametersparameters)功能返回到NavigationPage的根页面移除栈中所有其他页面。流程检查当前页面是否可以离开。获取当前完整导航栈NavigationStack将根页面以外的所有页面作为待销毁列表。调用PopToRootAsync弹出到根。依次销毁被弹出的页面最后通知根页面已导航到。6.4NavigateAsync(Uri uri, INavigationParameters parameters)publicvirtualasyncTaskINavigationResultNavigateAsync(Uriuri,INavigationParametersparameters)功能根据 URI 执行导航相对或绝对。流程等待信号量。将 URI 解析为导航段队列UriParsingHelper.GetUriSegments。如果 URI 是绝对路径调用ProcessNavigationForAbsoluteUri从根窗口开始导航。否则调用ProcessNavigation(GetCurrentPage(), segments, ...)从当前页面开始处理。发布导航事件并返回结果。6.5SelectTabAsyncpublicvirtualasyncTaskINavigationResultSelectTabAsync(stringtabName,Uriuri,INavigationParametersparameters)功能在父级TabbedPage中选择一个 Tab并可选择性地在该 Tab 内继续导航。参数tabName格式TabName直接在 TabbedPage 的子元素中查找匹配的页面。NavigationPageName|PageName先找到作为 Tab 的 NavigationPage再在其中找到根页面或当前页面。流程从当前页面向上查找TabbedPage。根据tabName找到目标子页面。检查当前页面是否可以离开。如果提供了uri则在目标 Tab 内部继续导航但不能是绝对 URI。设置tabbedPage.CurrentPage为选中的 Tab。触发导航离开/到达的生命周期方法。7. 等待导航请求完成privatestaticasyncTaskWaitForPendingNavigationRequests()获取信号量_semaphore保证同一时间只有一个导航操作。如果距离上次导航结束时间小于_minTimeBetweenNavigations则延迟剩余时间避免 UI 刷新问题。8. 核心导航处理方法ProcessNavigation8.1 主流程protectedvirtualasyncTaskProcessNavigation(PagecurrentPage,Queuestringsegments,INavigationParametersparameters,bool?useModalNavigation,bool?animated)递归处理每一段导航。每次从队列取出一个段nextSegment解析该段携带的参数。根据nextSegment是否为RemovePageSegment进入移除页面逻辑。根据当前页面类型ContentPage,NavigationPage,TabbedPage,FlyoutPage或 null调用对应的处理方法。8.2 处理移除页面段 (__RemovePage)protectedvirtualTaskProcessNavigationForRemovePageSegments(...)当 URI 中包含__RemovePage段时表示需要移除当前页或栈中之前的页面模拟../。调用CanRemoveAndPush判断后续是否还有非移除段如果有则执行“移除并推入新页面”否则执行“移除并返回”。移除并返回根据移除段的数量从当前页面的导航栈中移除相应数量的页面然后调用GoBackInternalAsync返回到前一页。移除并推入先移除指定数量的页面然后继续处理剩余的导航段推入新页面最后移除已标记的页面。8.3 处理根页面 (currentPage null)ProcessNavigationForRootPage创建第一个页面CreatePageFromSegment。如果是TabbedPage进行配置创建动态 Tab 等。继续处理剩余段。将新页面设置为窗口的主页Window.Page如果窗口不存在则创建新窗口。如果有旧的根页面销毁其模态栈。8.4 处理 ContentPageProcessNavigationForContentPage检查是否应该使用“反向导航”UseReverseNavigation即当前 ContentPage 位于 NavigationPage 内部且下一个页面也是 ContentPage。如果不需要反向导航则创建下一个页面继续处理剩余段然后执行DoPush。如果需要反向导航调用UseReverseNavigation模拟“推入多个页面”的效果实际上通过InsertPageBefore实现。8.5 处理 NavigationPageProcessNavigationForNavigationPage检查是否需要清空导航栈通过INavigationPageOptions.ClearNavigationStackOnNavigation接口。如果需要清空先PopToRootAsync(false)并收集待销毁页面。判断当前栈顶页面类型是否与下一个段的目标类型相同相同如果还有剩余段则在该页面内部继续反向导航同时如果段中包含SelectedTab参数则选中对应的 Tab。不同调用UseReverseNavigation处理新页面。最后销毁被清空的页面。8.6 处理 TabbedPageProcessNavigationForTabbedPage直接创建下一个页面可能也是 TabbedPage 或其他继续处理剩余段然后执行DoPush。注意从 TabbedPage 推入新页面时通常使用模态导航因为 TabbedPage 不是导航容器除非强制非模态。8.7 处理 FlyoutPageProcessNavigationForFlyoutPage逻辑较为复杂需要考虑详情页Detail的复用、导航页NavigationPage的重新创建条件等。核心思想如果当前没有 Detail则创建新的 Detail 并设置为页面的 Detail。如果强制使用模态导航则模态推入新页面。如果 Detail 是 NavigationPage判断是否复用该 NavigationPage根据ClearNavigationStackOnNavigation和要导航的页面类型。如果 Detail 类型与下一个段类型相同则在现有 Detail 内部继续导航。否则创建新的 Detail 并替换旧的同时销毁旧 Detail。保留 FlyoutPage 的IsPresented状态通过IFlyoutPageOptions接口控制导航后是否保持展开/折叠。8.8 辅助方法DoNavigateActionprotectedstaticasyncTaskDoNavigateAction(PagefromPage,stringtoSegment,PagetoPage,INavigationParametersparameters,FuncTasknavigationActionnull,ActionINavigationParametersonNavigationActionCompletednull)统一处理导航前后的生命周期合并段参数到全局参数。检查fromPage的IConfirmNavigation.CanNavigate如果存在。调用OnInitializedAsync初始化目标页面及其子页面TabbedPage 的子页面递归初始化。执行实际的导航动作navigationAction例如DoPush。调用OnNavigatedFrom通知源页面。执行完成回调。调用OnNavigatedTo通知目标页面。9. 页面创建CreatePageprotectedvirtualPageCreatePage(stringsegmentName)通过容器创建子作用域_container.CreateScope()然后使用注册表创建视图实例。处理各种异常并包装为NavigationException例如未注册的页面 →NoPageIsRegisteredViewModel 创建错误 →ErrorCreatingViewModelMAUI 在非 UI 线程创建页面导致的异常 →UnsupportedMauiCreationCreatePageFromSegment先提取段名称再调用CreatePage如果结果为 null 则抛出异常。10. TabbedPage 配置asyncTaskConfigureTabbedPage(TabbedPagetabbedPage,stringsegment,INavigationParametersparameters)从段参数中读取Title并设置。读取CreateTab参数支持多次出现该参数值为NavigationPage|ViewToCreate或单纯的View格式用于动态向 TabbedPage 中添加 Tab。对于NavigationPage类型的 Tab还会绑定 Title 和 Icon从 RootPage 或自定义源。privatevoidSelectPageTab(Pagepage,INavigationParametersparameters)privatevoidTabbedPageSelectTab(TabbedPagetabbedPage,INavigationParametersparameters)privatevoidTabbedPageSelectRootTab/TabbedPageSelectNavigationChildTab处理SelectedTab参数选中指定的 Tab。11. 反向导航 (UseReverseNavigation)protectedvirtualasyncTaskUseReverseNavigation(PagecurrentPage,stringnextSegment,Queuestringsegments,INavigationParametersparameters,bool?useModalNavigation,bool?animated)当在NavigationPage内部连续推入多个页面时为了避免多次PushAsync导致 UI 闪烁该方法会一次性插入多个页面到导航栈的适当位置。逻辑将当前段和后面的段收集到一个栈中反向顺序同时检测是否存在非法段如强制模态或 FlyoutPage若有则后续转为模态处理。从当前页面的导航栈确定插入位置偏移量。循环创建每个页面调用DoNavigateAction其中导航动作是DoPush并设置insertBeforeLast true即通过InsertPageBefore将页面插入到当前页之后、原下一页之前。如果遇到非法段则重新以模态方式处理后续段。12. 底层页面操作DoPushprotectedvirtualasyncTaskDoPush(PagecurrentPage,Pagepage,bool?useModalNavigation,bool?animated,boolinsertBeforeLastfalse,intnavigationOffset0)如果没有当前页即导航到根则设置窗口的Page或创建新窗口。否则根据是否使用模态导航调用PushModalAsync或PushAsync。若insertBeforeLast true则调用InsertPageBefore在指定偏移位置插入页面用于反向导航。InsertPageBefore调用Navigation.InsertPageBefore(page, firstPage)实现。DoPop根据是否模态调用PopModalAsync或PopAsync。13. 辅助判断方法UseModalNavigationinternalstaticboolUseModalNavigation(PagecurrentPage,bool?useModalNavigationDefault)确定是否需要模态导航若参数明确指定则使用参数值。否则如果当前页是NavigationPage则非模态。否则如果当前页有NavigationPage父级则非模态否则需要模态。UseModalGoBackinternalboolUseModalGoBack(PagecurrentPage,INavigationParametersparameters)判断返回操作是否应该使用模态方式即PopModalAsync优先根据参数中的UseModalNavigation。否则如果当前页是NavigationPage根据其是否在根上、是否在 Tab 中、是否是应用主页面等决定。有NavigationPage父级时类似判断。UseReverseNavigationinternalstaticboolUseReverseNavigation(PagecurrentPage,TypenextPageType)条件当前页有NavigationPage父级且下一个页面类型是ContentPage或其子类。IsRootprotectedstaticboolIsRoot(PagemainPage,PagecurrentPage)递归判断currentPage是否为应用主页面mainPage或其内部的某个容器页面的顶层。SendAppToBackground仅在 Android 上调用MoveTaskToBack(true)将应用置于后台而不是关闭页面。14. 事件发布privateINavigationResultNotify(NavigationRequestTypetype,INavigationParametersparameters,Exceptionexceptionnull)privateINavigationResultNotify(Uriuri,INavigationParametersparameters,Exceptionexceptionnull)发布NavigationRequestEvent通知订阅者导航请求的结果。对于 URI 导航还会移除内部使用的__RemovePage/指令将其转换为../相对路径后再发布。15. 其他内部工具GetCurrentPage()优先返回_pageAccessor.Page否则从窗口获取。GetPageFromWindow()安全获取窗口的当前页面。RemovePagesFromNavigationPage从NavigationPage的导航栈中移除指定页面列表并销毁。GetClearNavigationPageNavigationStack通过接口INavigationPageOptions获取是否需要清空栈。GetFlyoutPageIsPresented通过接口IFlyoutPageOptions获取导航后是否保持IsPresented状态。总结PageNavigationService是 Prism 导航体系的核心实现它支持相对/绝对 URI、参数传递、模态/非模态导航。正确处理 MAUI 中各种容器页NavigationPage, TabbedPage, FlyoutPage的嵌套与切换。提供了丰富的生命周期钩子OnInitialized, OnNavigatedTo/From, IConfirmNavigation。通过信号量和延时避免并发导航冲突。允许开发者通过接口如INavigationPageOptions,IFlyoutPageOptions自定义导航行为。支持动态创建 Tab 和选定 Tab。处理 Android 平台的软键盘模式和后台切换。该类充分体现了 Prism 的设计模式依赖注入、事件聚合、关注点分离并针对 MAUI 的特性做了大量适配。

相关新闻