Flutter GetX 状态管理在BottomSheet中Controller未销毁问题定位

本文分析了在使用Flutter GetX进行状态管理时,BottomSheet中的Controller未被销毁的问题。通过研究代码,发现GetModalBottomSheetRoute在dispose时没有正确清理Controller。而GetDialogRoute和GetPageRoute在dispose时进行了清理。为了解决这个问题,文章建议使用GetX代替Obx,并确保设置global为false。

在BottomSheet中使用Obx方式做状态管理,发现Controller未销毁掉。

return Obx(() {
      return PageStateManager(
        () {
          return getContent(context);
        },
        state.loadState.value,
        errorCode: state.errorCode.value,
        errorMsg: state.errorMsg.value,
        onRetryTap: state.onRetryTap,
      );
    });

分析代码发现Get.put的时候,主要是把Controller存在GetInstance的_singl Map中,如果controller没被移除,应该是delete方法没被调用。

查看get_navigation中src/bottomsheet/bottomsheet.dart代码,发现GetModalBottomSheetRoute在dispose中并没有做任何操作。

而src/dialog/dialog_route.dart代码中,GetDialogRoute重写了dispose,调用了GetInstance().removeDependencyByRoute进而调用了delete。

src/routes/default_route.dart中,GetPageRoute同样重写了dispose,并调用了GetIn

<think>我们使用GetX状态管理库时,监听页面不可见状态可以通过GetxController的生命周期来实现。在GetX中,当页面被创建时,会绑定一个Controller,当页面被关闭时,Controller会被销毁。但是,如果我们想要在页面不可见(例如被其他页面覆盖,但还销毁)时执行一些操作,我们可以使用GetX提供的Worker来监听路由变化,或者利用SchedulerBinding来监听页面的可见性。 然而,GetX本身并没有直接提供监听页面不可见状态的回调。但是,我们可以通过以下两种方式来实现: 1. 使用`ever`或`everAll`等Workers来监听路由变化,结合GetX的路由状态判断当前页面是否处于活动状态。但这种方法可能不够直接。 2. 使用Flutter原生的WidgetsBindingObserver来监听页面的生命周期(如AppLifecycleState),然后在GetXController中混入WidgetsBindingObserver,并重写didChangeAppLifecycleState方法。这样可以在整个应用生命周期变化时得到回调。 但是,需要注意的是,WidgetsBindingObserver监听的是整个应用的生命周期,而不是单个页面的。因此,我们需要在页面级别进行管理,通常是在StatefulWidget的State中混入WidgetsBindingObserver,然后通过GetX将状态传递到Controller。 不过,在GetX中,我们通常使用GetView和GetController来构建页面,这样页面本身是无状态的。因此,我们可以考虑在Controller中监听应用生命周期,并结合当前路由信息来判断当前页面是否可见。 具体步骤: - 在Controller中混入`GetLifeCycleMixin`(这是GetXController自带的,用于管理Controller的生命周期)和`WidgetsBindingObserver`(用于监听应用生命周期)。 - 在Controller初始化时,添加观察者;在Controller销毁时,移除观察者。 - 在`didChangeAppLifecycleState`方法中,根据应用的状态(resumed, inactive, paused, detached)来判断当前页面的可见性。 注意:当应用处于inactive或paused状态时,可能表示页面不可见(例如被其他应用覆盖或回到桌面),但这种方法无法区分是当前页面还是其他页面。 如果我们只想监听当前页面的可见性(即当有新的页面覆盖在当前页面上时,当前页面不可见),我们可以结合GetX的路由管理。例如,在页面进入时(onReady)开始监听,在页面退出时(onClose)移除监听,同时利用路由栈信息。 但是,GetX目前没有提供页面被覆盖时的回调,因此我们可以通过路由观察者(RouteObserver)来实现。这种方式需要我们在MaterialApp中设置navigatorObservers,然后使用GetMaterialApp已经内置了GetObserver,我们可以通过Get.routing来获取当前路由信息,但同样无法直接知道当前页面是否被覆盖。 因此,一个折中的方案是:在Controller中,我们监听应用的生命周期,并且假设当应用处于非resumed状态时,当前页面不可见。同时,我们也可以监听路由的变化,当有新的页面压栈时,当前页面就会不可见(但此时Controller仍然存在,直到页面被销毁)。 但是,由于GetXController是懒加载的,并且当页面被销毁Controller默认会被销毁(除非设置为永久存在),所以当页面被覆盖时,Controller并不会销毁,因此我们可以利用这个特性来监听应用生命周期。 下面是一个在GetXController中监听应用生命周期的示例: ```dart import 'package:flutter/widgets.dart'; import 'package:get/get.dart'; class MyController extends GetxController with WidgetsBindingObserver { @override void onInit() { super.onInit(); WidgetsBinding.instance.addObserver(this); } @override void onClose() { WidgetsBinding.instance.removeObserver(this); super.onClose(); } @override void didChangeAppLifecycleState(AppLifecycleState state) { super.didChangeAppLifecycleState(state); switch (state) { case AppLifecycleState.resumed: // 应用可见,页面处于活动状态 print('页面可见'); break; case AppLifecycleState.inactive: // 应用处于非活动状态,通常是进入后台或弹窗覆盖 print('页面不可见(非活动)'); break; case AppLifecycleState.paused: // 应用进入后台 print('页面不可见(进入后台)'); break; case AppLifecycleState.detached: // 应用即将销毁 break; } } } ``` 但是,这种方法只能监听整个应用的生命周期,不能精确到单个页面。因此,如果多个页面同时存在(例如A页面打开B页面),那么A页面的Controller也会收到应用生命周期的回调,但此时A页面已经被覆盖,而应用可能仍然在前台(resumed状态)。 因此,为了精确监听单个页面的可见性,我们可以使用RouteAware。GetX没有内置RouteAware,但我们可以结合GetX的路由中间件或使用Flutter原生的RouteObserver。 由于在GetX中,我们通常使用命名路由,因此可以创建一个RouteAware的Controller。但是,这需要我们在每个页面中注册和注销RouteObserver。 具体步骤: 1. 在MaterialApp中设置navigatorObservers: ```dart GetMaterialApp( navigatorObservers: [GetObserver()], // 或者使用自定义的RouteObserver ); ``` 2. 创建一个自定义的RouteObserver(可以使用Flutter的RouteObserver)。 3. 在页面State中混入RouteAware,并注册到RouteObserver,然后重写didPush、didPopNext等方法。 但是,GetX推荐使用GetPageRoute,并且GetXController通常与GetPageRoute绑定,因此我们可以尝试在Controller中实现RouteAware,但需要将Controller与Route绑定,这需要额外的处理。 鉴于复杂度,这里提供一个在StatefulWidget的State中使用RouteAware的示例,然后通过Get.put将Controller与页面绑定,并在State中处理路由观察,再通知Controller: ```dart import 'package:flutter/material.dart'; import 'package:get/get.dart'; class MyPage extends StatefulWidget { @override _MyPageState createState() => _MyPageState(); } class _MyPageState extends State<MyPage> with RouteAware { final MyController controller = Get.put(MyController()); @override void didChangeDependencies() { super.didChangeDependencies(); // 注册路由观察者 final routeObserver = Get.find<RouteObserver>(); // 需要先注入RouteObserver routeObserver.subscribe(this, ModalRoute.of(context)); } @override void dispose() { Get.find<RouteObserver>().unsubscribe(this); super.dispose(); } // 当路由弹出,当前页面成为活动页面(即页面可见) @override void didPopNext() { controller.onPageVisible(); } // 当路由压入新页面,当前页面不再活动(即页面不可见) @override void didPushNext() { controller.onPageInvisible(); } @override Widget build(BuildContext context) { return Container(); } } class MyController extends GetxController { void onPageVisible() { print('页面可见'); } void onPageInvisible() { print('页面不可见'); } } ``` 但是,这种方法要求我们使用StatefulWidget,与GetX的无状态页面(GetView)风格不符。 因此,在GetX中,如果我们想要在Controller中监听页面可见性,可能需要等待GetX库的更新或者使用第三方插件。 目前,一个更简单的方法是在GetPage中利用中间件(middleware)来监听页面的路由事件。GetX提供了路由中间件功能,我们可以在其中监听路由的变化,并通知到Controller。 例如: ```dart class MyMiddleware extends GetMiddleware { @override GetPage? onPageCalled(GetPage? page) { // 在页面被调用时 return super.onPageCalled(page); } @override void onPageDispose() { // 在页面销毁时 super.onPageDispose(); } } ``` 但是,中间件中我们无法直接获取到目标页面的Controller,因为此时Controller可能还创建。 综上,目前GetX没有提供直接监听页面可见性的方法,但我们可以结合Flutter的原生方法(如RouteAware)来实现,但需要牺牲一部分GetX的便利性。 因此,对于大多数场景,如果只需要监听整个应用的生命周期,可以使用WidgetsBindingObserver;如果需要精确到页面级别的可见性,则建议在StatefulWidget中使用RouteAware,并通过Get.put将Controller与页面绑定,然后在State中处理路由回调并调用Controller的方法。 如果坚持使用GetView(无状态)方式,我们可以创建一个基类Controller,结合GetX的路由堆栈信息,在每次路由变化时检查当前页面是否在栈顶,但这可能比较消耗性能。 综上所述,根据需求选择合适的方法。如果应用要求不高,使用应用生命周期监听即可;如果要求精确,则使用RouteAware。 由于问题要求监听页面不可见状态,这里我们给出一个在GetXController中监听应用生命周期的示例,因为实现起来比较简单,但需要注意其局限性。 希望这个回答能帮到你。</think>在 Flutter 中使用 GetX 监听页面不可见状态,可以通过以下两种方式实现: ### 方法一:使用 `ever` 监听路由变化(推荐) ```dart import 'package:get/get.dart'; class PageVisibilityController extends GetxController { // 定义页面可见状态 var isPageVisible = true.obs; @override void onInit() { super.onInit(); // 监听路由变化 ever(Get.routing, (route) { // 当页面被覆盖时(新页面压栈) if (route is! GetPageRoute || route.settings.name != currentRoute) { isPageVisible.value = false; print("页面不可见"); } // 当页面回到栈顶时 else { isPageVisible.value = true; print("页面可见"); } }); } // 获取当前路由名 String get currentRoute => Get.currentRoute; } // 在页面中使用: class MyPage extends StatelessWidget { final controller = Get.put(PageVisibilityController()); @override Widget build(BuildContext context) { return Obx(() { if (!controller.isPageVisible.value) { // 执行不可见时的操作 } return Scaffold(...); }); } } ``` ### 方法二:结合 `WidgetsBindingObserver`(精确监听) ```dart class LifecycleController extends GetxController with WidgetsBindingObserver { var isAppInactive = false.obs; @override void onInit() { super.onInit(); WidgetsBinding.instance.addObserver(this); } @override void onClose() { WidgetsBinding.instance.removeObserver(this); super.onClose(); } @override void didChangeAppLifecycleState(AppLifecycleState state) { switch(state) { case AppLifecycleState.inactive: // 页面被覆盖 isAppInactive.value = true; print("页面不可见"); break; case AppLifecycleState.resumed: // 页面恢复 isAppInactive.value = false; print("页面可见"); break; default: break; } } } ``` ### 注意事项: 1. 方法一适合监听页面跳转导致的不可见状态 2. 方法二可监听应用级状态变化(包括回到桌面/通知栏覆盖) 3. 在 `onClose()` 中必须移除监听防止内存泄漏 4. 不可见状态常见场景: - 打开新页面覆盖当前页 - 应用进入后台 - 系统弹窗(权限申请/来电)
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值