尧图精选

GoRouter 状态恢复(State Restoration)完整指南:restorationScopeId 与 restorationId 配置详解

🕒 发布时间:2026/9/18 10:49:00 📁 来源:尧图网络
GoRouter 状态恢复State Restoration完整指南restorationScopeId 与 restorationId 配置详解【免费下载链接】packagesA collection of useful packages maintained by the Flutter team项目地址: https://gitcode.com/GitHub_Trending/pac/packages本指南基于 Flutter 官方维护的 go_router 包仓库packages/go_router系统讲解如何为基于 go_router 的应用启用 Android / iOS 平台的系统级状态恢复从顶层GoRouter与MaterialApp.router的restorationScopeId配置到GoRoute、ShellRoute、StatefulShellRoute三种路由类型各自的恢复 ID 要求并结合仓库源码与测试用例剖析底层实现原理。读完本文你将能够为普通页面路由、嵌套壳路由与多分支状态化壳路由分别配置状态恢复并理解每个 ID 在恢复链路中的真实作用。什么是状态恢复状态恢复State Restoration指的是当应用在后台被操作系统杀死后将序列化后的状态持久化并在下次启动时恢复的过程。它专门针对 Android / iOS 系统层面的进程回收场景——例如用户切到其他应用、系统内存紧张杀死后台进程之后用户再次回到应用时应用应尽量还原到被杀之前的界面与页面状态。需要特别区分的是状态恢复不是通用意义上的状态持久化。它只负责恢复被系统杀死这一场景下的序列化状态。如果只是想同时在内存中保存多条导航分支例如底部 Tab 各自的导航栈应使用StatefulShellRoute的能力而不是依赖状态恢复机制关于这一点StatefulShellRoute 的说明 与文档均有明确界定。支持范围go_router 对状态恢复提供完整支持。启用状态恢复需要两类配置顶层配置在GoRouter与MaterialApp.router上设置restorationScopeId路由级配置根据使用的路由类型GoRoute/ShellRoute/StatefulShellRoute追加额外的恢复 ID 配置。下面分别展开。顶层配置两个 restorationScopeId 缺一不可启用状态恢复的第一步是同时在GoRouter和MaterialApp.router上指定restorationScopeIdfinal _router GoRouter( restorationScopeId: router, routes: [ // ... ], );class MyApp extends StatelessWidget { override Widget build(BuildContext context) { return MaterialApp.router( restorationScopeId: app, routerConfig: _router, ); } }从源码结构看这两个 ID 各自承担不同的职责GoRouter的restorationScopeId最终会传递给GoRouterDelegate进而被 RouteBuilder 作为navigatorRestorationId传给顶层Navigator见 builder.dart。在 builder.dart 的文档注释中明确写道Restoration ID to save and restore the state of the navigator, including its history即保存并恢复 Navigator 的状态及其历史栈。GoRouter构造签名中restorationScopeId参数位于 router.dart。MaterialApp.router的restorationScopeId则属于 Flutter 框架层面的恢复作用域标识为整个应用提供统一的根恢复作用域。两者需要保持唯一且稳定。恢复 ID 的作用域是全局的重复的 ID 会互相冲突导致状态错乱。路由级配置三种路由类型的恢复 ID 要求GoRoute取决于是否使用 pageBuilder对于使用pageBuilder的GoRoute必须给返回的Page提供restorationIdGoRoute( pageBuilder: (context, state) { return MaterialPage( restorationId: detailsPage, path: /details, child: DetailsPage(), ); }, )对于不使用pageBuilder即使用builder的GoRoute无需任何额外配置——go_router 会为路由自动设置恢复 ID。仓库中有一个可直接运行并带测试的完整示例 go_route_state_restoration.dart其关键结构如下final GoRouter _router GoRouter( restorationScopeId: router, routes: GoRoute[ GoRoute( path: /, // 使用 builder 时restorationId 由框架自动设置 builder: (BuildContext context, GoRouterState state) { return const HomePage(); }, routes: GoRoute[ GoRoute( path: login, // 使用 pageBuilder 时必须手动为 MaterialPage 提供 restorationId pageBuilder: (BuildContext context, GoRouterState state) { return const MaterialPagevoid( restorationId: loginPage, fullscreenDialog: true, child: LoginPage(), ); }, ), ], ), ], );示例中HomePage与LoginPage内的TextField也都设置了restorationId如homeTextField、loginTextField这样用户输入的文本也能一并恢复。注意页面Page级的restorationId与页面内可恢复 Widget 的restorationId是两个层次前者恢复导航栈本身后者恢复具体 Widget 的局部状态二者需要分别配置。ShellRouterestorationScopeId 带 restorationId 的 pageBuilderShellRoute需要两步配置给ShellRoute设置唯一的restorationScopeId提供pageBuilder并为返回的页面设置restorationId。重要要启用ShellRoute的状态恢复必须提供返回带restorationId页面的pageBuilder否则状态恢复无法生效。ShellRoute( restorationScopeId: onboardingShell, pageBuilder: (context, state, child) { return MaterialPage( restorationId: onboardingPage, child: OnboardingScaffold(child: child), ); }, routes: [ // ... ], )完整可运行示例见 shell_route_state_restoration.dart其中ShellRoute包裹了welcome与setup两个子路由OnboardingScaffold作为壳页面渲染child而壳的恢复 ID 为onboardingShell/onboardingPage。恢复后用户停留在setup步骤时被系统杀死重启后依然停留在setup页面。StatefulShellRoute壳 每个分支都要配置StatefulShellRoute的配置分三层给StatefulShellRoute设置restorationScopeId提供返回带restorationId页面的pageBuilder给每一个StatefulShellBranch设置各自的restorationScopeId。重要要启用StatefulShellRoute的状态恢复必须提供返回带restorationId页面的pageBuilder。StatefulShellRoute.indexedStack( restorationScopeId: appShell, pageBuilder: (context, state, navigationShell) { return MaterialPage( restorationId: appShellPage, child: AppShell(navigationShell: navigationShell), ); }, branches: [ StatefulShellBranch( restorationScopeId: homeBranch, routes: [ // ... ], ), StatefulShellBranch( restorationScopeId: profileBranch, routes: [ // ... ], ), ], )每个StatefulShellBranch对应一个独立的Navigator见 route.dart 中对StatefulShellBranch的说明A separate Navigator will be built for each StatefulShellBranch因此每个分支都需要自己的恢复作用域 ID才能分别保存和恢复各自的导航历史。StatefulShellBranch构造签名中restorationScopeId的官方注释为Restoration ID to save and restore the state of the navigator, including its historyroute.dart。完整示例见 stateful_shell_route_state_restoration.dart它构建了Home/Profile两个分支通过StatefulNavigationShell.goBranch切换并分别为两个分支内的TextField设置了homeTextField、profileTextField恢复 ID。源码级原理恢复链路与 ID 派生规则1. restorationScopeId 如何抵达 NavigatorGoRouter的restorationScopeId通过构造链GoRouter → GoRouterDelegate → RouteBuilder传递最终在RouteBuilder.build中作为navigatorRestorationId赋给顶层自定义 Navigatorbuilder.dart。也就是说GoRouter上的这个 ID 决定的是根导航器的状态恢复作用域。2. StatefulShellRoute 的恢复 ID 派生规则StatefulNavigationShellState混入了 Flutter 的RestorationMixin其恢复 ID 直接取自路由的restorationScopeIdoverride String? get restorationId route.restorationScopeId;见 route.dart对于每个分支go_router 会派生一个分支位置恢复 ID若分支设置了restorationScopeId则派生为${branch.restorationScopeId}-location若未设置则回退到分支对象的identityHashCode字符串以保证始终有 ID 可用String _branchLocationRestorationScopeId(StatefulShellBranch branch) { return branch.restorationScopeId ! null ? ${branch.restorationScopeId}-location : identityHashCode(branch).toString(); }见 route.dart恢复时每个分支的RouteMatchList分支当前位置通过_RestorableRouteMatchList以该派生 ID 注册进恢复机制route.dart从而在进程重启后还原每个分支的导航位置。3. 分支 ID 的调试校验go_router 在 debug 模式下会校验如果任意一个StatefulShellBranch设置了restorationScopeId则StatefulShellRoute本身也必须设置否则直接触发断言失败static bool _debugValidateRestorationScopeIds( String? restorationScopeId, ListStatefulShellBranch branches, ) { if (branches.map((StatefulShellBranch e) e.restorationScopeId).nonNulls.isNotEmpty) { assert( restorationScopeId ! null, A restorationScopeId must be set for the StatefulShellRoute when using restorationScopeIds on one or more of the branches, ); } return true; }见 route.dart该断言在 route.dart 处被调用。这意味着要么所有分支都不设置恢复 ID要么壳与每个分支都完整设置不能出现只给分支设置、不给壳设置的半吊子配置。测试验证状态恢复如何在测试中被证实仓库测试为上述配置提供了直接验证。在 go_router_test.dart 中Restores GoRoute state correctly 用例展示了标准流程以initialLocation: /a/detail和restorationScopeId: test创建路由通过DummyRestorableStatefulWidget见 test_helpers.dart一个混入RestorationMixin、按restorationId注册计数器状态的测试 Widget把计数器递增到 1调用tester.restartAndRestore()模拟应用被系统杀死后重启并恢复断言恢复后仍停留在Screen A Detail页面且计数器保持为 1。该用例证明了两个关键结论导航位置被恢复仍停留在详情页且页面内的可恢复状态被恢复计数器未被重置。restartAndRestore正是 Flutter 测试框架中模拟杀进程并恢复的核心 API。此外Restores state of branches in StatefulShellRoute correctly 用例go_router_test.dart构造了三个分支的StatefulShellRoute其中branchA、branchB设置了restorationScopeId而branchC未设置同时壳设置了restorationScopeId: shell与restorationId: shellWidget验证了分支级导航栈的独立恢复行为。测试辅助函数createRouter也演示了另一种有效实践在测试中把顶层 ID 直接派生为$restorationScopeId-roottest_helpers.darttest_helpers.dart保证嵌套 Navigator 之间 ID 不冲突。配置速查表与最佳实践路由类型必填配置说明全局所有场景GoRouter(restorationScopeId: ...)恢复根 Navigator 及其历史全局所有场景MaterialApp.router(restorationScopeId: ...)提供应用级根恢复作用域GoRoute使用builder无恢复 ID 自动设置GoRoute使用pageBuilderPage(restorationId: ...)必须手动为页面提供恢复 IDShellRouterestorationScopeId 带restorationId的pageBuilder两者缺一不可StatefulShellRouterestorationScopeId 带restorationId的pageBuilder两者缺一不可StatefulShellBranch每个各自的restorationScopeId每个分支独立 Navigator需独立 ID实践建议ID 全局唯一且稳定恢复作用域是全局的重复 ID 会造成状态互相覆盖不要用随机值避免每次重建都变化导致无法恢复。壳路由务必配 pageBuilderShellRoute与StatefulShellRoute的状态恢复强依赖带restorationId的pageBuilder只设置builder无法生效。分支级 ID 要么全配、要么全不配只要有一个分支设置restorationScopeId壳就必须设置debug 断言会直接报错。区分两个 ID 层次路由/页面级的恢复 ID 负责恢复导航栈页面内TextField等 Widget 的restorationId负责恢复局部输入状态二者要分别配置。在真机上验证状态恢复只在系统真正回收进程的场景发生建议在 Android / iOS 真机上通过后台驻留后强制停止再启动的流程实测widget 测试中的restartAndRestore只是模拟。【免费下载链接】packagesA collection of useful packages maintained by the Flutter team项目地址: https://gitcode.com/GitHub_Trending/pac/packages创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联 返回资讯列表 →