路由拦截
1. 全局路由拦截
class GlobalRouteObserver extends NavigatorObserver {
@override
void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {
// 路由被压入栈时的处理
print('新路由: ${route.settings.name}')
}
@override
void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {
// 路由被弹出栈时的处理
print('返回到: ${previousRoute?.settings.name}')
}
}
2. 自定义路由拦截器
class RouteInterceptor {
static bool handleRoute(String routeName) {
// 登录拦截
if (_needLogin(routeName) && !isLoggedIn) {
Navigator.pushNamed(context, '/login');
return false;
}
return true;
}
static bool _needLogin(String routeName) {
// 需要登录的路由列表
return ['/profile', '/settings'].contains(routeName);
}
}
路由守卫
1. 路由前置守卫
class RouteGuard {
static Future<bool> canActivate(String routeName) async {
// 权限检查
if (!await checkPermission(routeName)) {
showPermissionDeniedDialog();
return false;
}
// 其他条件检查
if (!meetOtherConditions(routeName)) {
return false;
}
return true;
}
}
2. 路由解析守卫
class RouteResolver extends RouteInformationParser<RouteState> {
@override
Future<RouteState> parseRouteInformation(
RouteInformation routeInformation
) async {
final uri = Uri.parse(routeInformation.location!);
// 路由参数验证
if (!validateParams(uri.queryParameters)) {
return RouteState.error();
}
return RouteState.fromUri(uri);
}
}
路由状态管理
1. 路由状态持久化
class RouteStateManager {
// 保存路由历史
static Future<void> saveRouteHistory(String routeName) async {
final prefs = await SharedPreferences.getInstance();
final history = prefs.getStringList('route_history') ?? [];
history.add(routeName);
await prefs.setStringList('route_history', history);
}
// 恢复路由状态
static Future<void> restoreRouteState() async {
final prefs = await SharedPreferences.getInstance();
final lastRoute = prefs.getString('last_route');
if (lastRoute != null) {
Navigator.pushNamed(context, lastRoute);
}
}
}
2. 路由栈管理
class RouteStackManager {
static final List<String> _routeStack = [];
// 压入路由
static void push(String routeName) {
_routeStack.add(routeName);
_updateRouteState();
}
// 弹出路由
static String? pop() {
if (_routeStack.isNotEmpty) {
final route = _routeStack.removeLast();
_updateRouteState();
return route;
}
return null;
}
// 更新路由状态
static void _updateRouteState() {
// 处理路由状态变化
}
}
高级特性
1. 深层链接处理
class DeepLinkHandler {
static Future<void> handleDeepLink(String link) async {
final uri = Uri.parse(link);
// 解析深层链接
final route = parseDeepLink(uri);
if (route != null) {
// 处理路由跳转
await Navigator.pushNamed(context, route);
}
}
static String? parseDeepLink(Uri uri) {
// 实现深层链接解析逻辑
return null;
}
}
2. 路由动画定制
class CustomPageRoute extends PageRouteBuilder {
final Widget page;
CustomPageRoute({required this.page})
: super(
pageBuilder: (context, animation, secondaryAnimation) => page,
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return FadeTransition(
opacity: animation,
child: child,
);
},
);
}
最佳实践
1. 路由表集中管理
RouteNames(字符串常量)+RoutePages.list(GetPage 列表)两层:业务代码只认 RouteNames,注册只改 RoutePages——实战案例里 freight 项目的真实结构- 新增页面先加常量、再加 GetPage,顺序别反:先写跳转代码再补注册,中间跑起来就是”按钮点了没反应”
- 路由名小写+下划线,全局唯一
2. 登录拦截用中间件
- GetPage 的
middlewares: [AuthMiddleware()]统一拦,别在十几个按钮的 onPressed 里各自判登录——实战:之前在点击处判,漏了两个入口被测试抓包 - 中间件 redirect 到登录页时,把原目标路由存下来,登录成功后跳回去——别让用户登录完停在首页,满世界找刚才的页面
3. 404 与参数兜底
- 配 unknownRoute:拼错的路由名进 404 页,给”返回首页”按钮,别白屏
- 参数缺失/类型不对也进错误页:详情页 id 为 null 时展示”参数错误”,别
!强拆
4. 深链接按需再做
- App Links/Universal Links 要配 assetlinks.json、apple-app-site-association,还要过平台校验,成本不低;作者的几个项目都没做——没这个需求就别为了”进阶”而做
常见问题
1. 路由参数丢失
- 使用路由参数持久化
- 正确处理页面重建
- 实现参数恢复机制
2. 返回键行为
- 自定义返回逻辑
- 处理多级返回
- 防止意外退出
3. 内存管理
- 及时释放资源
- 控制路由栈深度
- 避免循环引用
实战案例
路由”进阶”我理解就是一件事:路由表别散。某物流冷链项目 里我有 RouteNames(纯字符串常量)+ RoutePages.list(GetPage 列表)两层:前者给业务代码跳转用(Get.toNamed(RouteNames.profile)),后者给 GetMaterialApp(getPages:) 注册用。中间件(比如登录拦截)我挂在 GetPage 的 middlewares 上,而不是每个按钮里判登录——之前试过在点击处判,十几个入口漏了两个,被测试抓包。集中式路由表 + 中间件,是我从”能跑”到”可维护”跨过去的关键一步。至于深链接(App Links),这几个项目都没做,有需求再说。
总结
路由进阶归根结底是”集中”:路由表集中、拦截集中、埋点集中。RouteNames + getPages 两层表,登录拦截挂 middleware,NavigatorObserver 做埋点,深链接按需再加。散落在各处的跳转看着灵活,项目一大就是灾难——路由表是应用的索引,值得像数据库表结构一样认真设计。
