在 Flutter 中,页面拦截器(路由拦截)可以通过多种方式实现,以下是主要的几种方法:
1. 使用 NavigatorObserver(导航观察者)
这是 Flutter 内置的标准方法,通过监听导航事件实现拦截。
dart
class AuthNavigatorObserver extends NavigatorObserver {
@override
void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {
// 页面被推入时调用
super.didPush(route, previousRoute);
_checkAuth(route);
}
@override
void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {
// 页面被弹出时调用
super.didPop(route, previousRoute);
}
void _checkAuth(Route<dynamic> route) {
if (route.settings.name == '/profile' && !UserService.isLoggedIn) {
// 重定向到登录页
Navigator.pushReplacementNamed(route.navigator!.context, '/login');
}
}
}
// 在 MaterialApp 中使用
MaterialApp(
navigatorObservers: [AuthNavigatorObserver()],
// 其他配置...
)
2. 使用 GetX 中间件(推荐用于 GetX 项目)
如果您使用 GetX 状态管理库,可以使用其内置的中间件系统。
dart
class AuthMiddleware extends GetMiddleware {
@override
int? priority = 1; // 优先级,数字越小优先级越高
@override
RouteSettings? redirect(String? route) {
// 检查用户是否登录
if (!AuthService.isLoggedIn && route != '/login') {
return RouteSettings(name: '/login');
}
return null;
}
}
// 在路由配置中使用
GetPage(
name: '/profile',
page: () => ProfilePage(),
middlewares: [AuthMiddleware()],
),
个人理解
dart
middlewares: [AuthMiddleware()] 中间件可以设置多个,他们的执行顺序按照他们各自的priority优先级来;
返回的RouteSettings(name: '/login')必须是在
GetPage(
name: '/login',
page: () => OtherPageView(),
),中配置的
一旦某个中间件返回了重定向(RouteSettings),后续所有中间件都将不会执行。
前两种方式的性能差异:
dart
GetX 的中间件(拦截器):只有你在路由配置里设置了 middlewares,对应页面跳转时才会执行这些中间件。如果没设置中间件,页面跳转就不会有拦截和额外逻辑,性能不会有影响。
Flutter 的 NavigatorObserver:这是 Flutter 原生的导航观察者,只要你在 MaterialApp 或 GetMaterialApp 里注册了,它会监听所有页面的 push、pop、replace 等导航事件。
它的回调(如 didPush、didPop)会在每次页面跳转时执行。
性能影响说明:
NavigatorObserver 本身只是事件监听,回调方法很轻量,通常不会影响性能。
只有你在这些回调里写了复杂或耗时的逻辑,才可能拖慢页面跳转。
一般用于埋点、统计、日志、权限等场景,合理使用不会有明显性能问题。
总结:
GetX 中间件只在你设置时才执行,不设置就没有影响。
NavigatorObserver 只要注册就会监听所有导航事件,但本身很轻量,性能影响极小,主要看你回调里写了什么。
3. 使用 onGenerateRoute 拦截
在 MaterialApp 的 onGenerateRoute
回调中实现路由拦截。
dart
MaterialApp(
onGenerateRoute: (RouteSettings settings) {
// 检查权限
if (settings.name == '/admin' && !UserService.isAdmin) {
return MaterialPageRoute(builder: (_) => UnauthorizedPage());
}
// 正常路由
switch (settings.name) {
case '/':
return MaterialPageRoute(builder: (_) => HomePage());
case '/profile':
return MaterialPageRoute(builder: (_) => ProfilePage());
default:
return MaterialPageRoute(builder: (_) => NotFoundPage());
}
},
)
1与3的差异:
dart
NavigatorObserver 和 onGenerateRoute 在拦截导航行为上有根本性的区别:它们处于导航过程的不同阶段,因此拥有不同的能力和职责。
onGenerateRoute 是 路由生成器:它在路由被创建之前拦截,决定要创建什么。
NavigatorObserver 是 路由观察者:它在路由被推送或弹出之后拦截,用于知道发生了什么。
核心区别对比表
特性 | onGenerateRoute |
NavigatorObserver |
---|---|---|
拦截时机 | 路由创建之前 | 路由已经执行之后 |
角色 | 生成者/决策者 | 观察者/记录者 |
主要能力 | 创建、替换、重定向路由。可以决定不创建目标路由,而是创建另一个(如登录页)。 | 监听、记录、分析导航事件。无法改变当前正在发生的导航行为。 |
访问 Context | 可以 ,因为它是一个构建页面的函数,自然能拿到 context 。 |
不可以 ,观察者方法里没有 context 参数。 |
典型用例 | 身份验证守卫:用户未登录时,访问个人中心页面的请求被重定向到登录页。 | 行为分析:记录用户浏览了哪些页面(Firebase Analytics等)。 |
修改导航 | 可以强干预 ,直接返回一个不同的 Route 对象。 |
不能直接干预 当前的 push /pop 操作。但可以通过监听结果触发新的导航(这可能导致体验不佳)。 |
依赖获取 | 可通过 ModalRoute.of(context) 获取路由参数,可通过 Provider 等访问全局状态。 |
无法直接通过 context 获取依赖,通常需要直接访问全局状态容器(如 Get.put() 的实例、全局的 bloc 等)。 |
4. 使用 RouteAware 混入
通过混入 RouteAware 类并配合 NavigatorObserver 实现页面级别的拦截。
dart
class AuthPage extends StatefulWidget {
@override
_AuthPageState createState() => _AuthPageState();
}
class _AuthPageState extends State<AuthPage> with RouteAware {
@override
void didChangeDependencies() {
super.didChangeDependencies();
RouteObserver.of(context).subscribe(this, ModalRoute.of(context)!);
}
@override
void didPush() {
// 页面被推入时
_checkAuth();
}
@override
void dispose() {
RouteObserver.of(context).unsubscribe(this);
super.dispose();
}
void _checkAuth() {
if (!UserService.isLoggedIn) {
Navigator.pushReplacementNamed(context, '/login');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
// 页面内容...
);
}
}
// 在 MaterialApp 中注册观察者
MaterialApp(
navigatorObservers: [RouteObserver<PageRoute>()],
)
5. 使用第三方路由库
许多第三方路由库提供了更强大的拦截功能:
AutoRoute
dart
@RoutePage()
class ProfilePage extends StatelessWidget {
// 页面内容...
}
// 使用守卫
class AuthGuard extends AutoRouteGuard {
@override
void onNavigation(NavigationResolver resolver, StackRouter router) {
if (UserService.isLoggedIn) {
resolver.next(true);
} else {
router.push(LoginRoute());
resolver.next(false);
}
}
}
Fluro
dart
// 定义处理程序
var profileHandler = Handler(
handlerFunc: (context, params) {
if (!UserService.isLoggedIn) {
return LoginPage();
}
return ProfilePage();
},
);
// 配置路由
void defineRoutes(FluroRouter router) {
router.define('/profile', handler: profileHandler);
}
6. 使用 BLoC/Cubit 模式结合导航
结合状态管理实现更复杂的拦截逻辑。
dart
// 在 BLoC 中控制导航
class NavigationCubit extends Cubit<NavigationState> {
final AuthCubit authCubit;
StreamSubscription? _authSubscription;
NavigationCubit({required this.authCubit}) : super(NavigationInitial()) {
// 监听认证状态变化
_authSubscription = authCubit.stream.listen((authState) {
if (authState is Unauthenticated) {
// 重定向到登录页
emit(NavigationRedirect(route: '/login'));
}
});
}
@override
Future<void> close() {
_authSubscription?.cancel();
return super.close();
}
}
// 在 UI 中监听导航状态
BlocListener<NavigationCubit, NavigationState>(
listener: (context, state) {
if (state is NavigationRedirect) {
Navigator.pushReplacementNamed(context, state.route);
}
},
child: // 页面内容...
)
选择建议
- 简单项目 :使用
onGenerateRoute
或NavigatorObserver
GetX
项目 :使用GetX
中间件- 复杂路由需求 :考虑
AutoRoute
或Fluro
- 状态驱动导航 :使用
BLoC/Cubit
模式
根据您的项目规模和需求选择最适合的方法。对于大多数应用,GetX
中间件或 NavigatorObserver
提供了良好的平衡 between
简单性和功能性。