Flutter中使用GoRouter切换页面时如何禁用mobile_scanner组件
看起来你遇到的问题是用GoRouter的StatefulShellRoute做底部导航时,切换页面后QR扫描器还在后台偷偷运行对吧?我之前也碰到过类似的情况,其实核心问题是StatefulShellRoute的各个分支页面切换时,原来的页面并没有被销毁,只是被隐藏了,所以你尝试的onExit方法根本不会触发(因为路由没被弹出)。下面给你几个靠谱的解决思路:
方法一:利用RouteAware监听页面可见性(最推荐)
这是Flutter官方推荐的监听路由可见性变化的方式,刚好适配你这种页面隐藏/显示的场景:
给QR页面的State混入RouteAware
首先你的QR页面得是StatefulWidget,然后在State类中混入RouteAware,通过路由的生命周期回调来控制扫描器的启停:class QRPageState extends State<QRPage> with RouteAware { late MobileScannerController _scannerController; @override void initState() { super.initState(); _scannerController = MobileScannerController(); // 初始化时先不启动扫描,等页面可见后再启动 } @override void didChangeDependencies() { super.didChangeDependencies(); // 注册RouteObserver,需要在根App中提前配置全局实例 final routeObserver = Router.of(context).routeObserver as RouteObserver<PageRoute>; routeObserver.subscribe(this, ModalRoute.of(context)! as PageRoute); } @override void dispose() { // 取消注册+销毁扫描控制器,避免内存泄漏 final routeObserver = Router.of(context).routeObserver as RouteObserver<PageRoute>; routeObserver.unsubscribe(this); _scannerController.dispose(); super.dispose(); } // 页面首次被推到前台时启动扫描 @override void didPush() { super.didPush(); _scannerController.start(); } // 从其他页面返回当前页面时重启扫描 @override void didPopNext() { super.didPopNext(); _scannerController.start(); } // 当前页面被隐藏(切换到其他底部导航页面)时停止扫描 @override void didPushNext() { super.didPushNext(); _scannerController.stop(); } // 页面被弹出时停止扫描 @override void didPop() { super.didPop(); _scannerController.stop(); } @override Widget build(BuildContext context) { return MobileScanner( controller: _scannerController, // 其他扫描器配置... ); } }在根App中配置全局RouteObserver
你需要在项目的根MaterialApp(或CupertinoApp)中提供一个RouteObserver实例,这样页面才能订阅路由变化:final RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>(); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp.router( routerConfig: _router, routeObserver: routeObserver, // 其他App配置... ); } }
方法二:利用WidgetsBindingObserver监听页面可见性(适合简单场景)
如果你觉得RouteAware配置有点麻烦,也可以用WidgetsBindingObserver来监听APP的可见性状态。不过要注意,这个是监听整个APP的前后台变化,虽然在APP前台切换页面时也能触发,但精准度不如RouteAware:
class QRPageState extends State<QRPage> with WidgetsBindingObserver { late MobileScannerController _scannerController; @override void initState() { super.initState(); _scannerController = MobileScannerController(); WidgetsBinding.instance.addObserver(this); } @override void dispose() { WidgetsBinding.instance.removeObserver(this); _scannerController.dispose(); super.dispose(); } @override void didChangeAppLifecycleState(AppLifecycleState state) { super.didChangeAppLifecycleState(state); switch (state) { case AppLifecycleState.resumed: // 页面回到前台时启动扫描 _scannerController.start(); break; case AppLifecycleState.paused: case AppLifecycleState.inactive: case AppLifecycleState.detached: // 页面被隐藏时停止扫描 _scannerController.stop(); break; } } @override Widget build(BuildContext context) { return MobileScanner( controller: _scannerController, // 其他扫描器配置... ); } }
为什么onExit方法没用?
这里给你补个知识点:你之前尝试的onExit回调只有在路由被弹出(比如用户按返回键、调用context.pop())的时候才会触发,但用StatefulShellRoute切换底部导航栏时,QR页面所在的路由并没有被弹出,只是被当前活跃的分支导航器隐藏了,所以onExit根本不会执行,自然也就没法调用到页面的方法啦。
不管用哪种方法,一定要在页面的dispose方法中停止并销毁MobileScannerController,不然会导致内存泄漏,甚至相机权限的遗留问题。
内容来源于stack exchange

