在使用 GetxController(以及它的子类,如 GetxService)时,我们通常会接触到几个常见的生命周期方法,比如 onInit、onReady、onClose 等。实际上,GetX 在 Controller 的生命周期管理上提供了更细致的钩子函数,帮助我们更好地安排业务逻辑。
一、GetxController 主要生命周期方法
- onInit()
- onReady()
- onClose()
这三大方法在 GetxController 中最常用,也是官方文档重点介绍的。除此之外,在 GetX 源码中还可以找到一些其他(比较少见)的生命周期方法,例如 onStart, onDelete, 以及对于 GetxService 的一些附加逻辑。
不过在常规的业务开发场景下,主要关注 onInit(), onReady() 和 onClose() 就足以应对绝大部分需求。
让我们依次了解它们在何时被调用、适合处理哪些逻辑,以及一些最佳实践。
1. onInit()
何时被调用?
- 当 Controller 实例被 Get.put 或 Get.find(第一次获取)时,会立即调用该 Controller 的 onInit() 方法。
- 这意味着 onInit() 通常是这个 Controller 生命周期的开端,一般在 Widget 构建之前就会执行(或者说,在页面渲染之前)。
适合处理的逻辑
- 变量初始化:初始化业务变量、Rx 变量等。
- 监听绑定:通过 ever, debounce, interval 等 Worker,在此处集中设置对 Rx 变量的监听。
- 依赖注入:如果这个控制器需要依赖某些服务(也可以用 Bindings),也可以在这里尝试使用 Get.find 获取并赋值。
- 轻量级网络请求:如果数据的优先级比较高,需要在页面显示之前就拿到(如基础配置、用户信息),可以在 onInit()里发请求。但如果请求会阻塞页面渲染过久,可以考虑放到 onReady()。
class HomeController extends GetxController {
var counter = 0.obs;
@override
void onInit() {
super.onInit();
print('onInit -> Controller开始初始化');
// 设置 Worker,比如监听 counter 的变化
ever(counter, (value) {
print('counter changed: $value');
});
// 如果需要请求一些不太大的数据,比如配置文件
fetchInitialData();
}
void fetchInitialData() {
// ...
}
}
⚠️ 注意:如果请求会比较耗时,且必须在页面完全渲染后才做,不建议放在 onInit(),否则用户可能在空白页面等待,体验不好。这时应该考虑放到 onReady()。
2. onReady()
何时被调用?
- 当 Widget(绑定了该 Controller 的页面)渲染完成后,就会调用 onReady()。
- 它比 onInit() 更晚,通常意味着页面已经可见,此时做一些需要依赖“页面可见”的操作会更合适。
适合处理的逻辑
- 网络请求 / 动画启动:页面渲染完成后,再开始长耗时操作或加载数据,可以先让用户看到页面框架,避免一直等待空白屏。
- 弹窗 / SnackBar:有时需要在页面刚显示时弹出引导或提示,也可以在这里调用。
- 启动轮询 / 计时器:页面已经加载完,可能需要周期性地更新数据。
class ProfileController extends GetxController {
var profile = Rxn<UserProfile>(); // 可能是null
@override
void onInit() {
super.onInit();
// 这里可以做一些很轻量的初始化
// 例如:profile.value = localCache?.readProfile();
}
@override
void onReady() {
super.onReady();
print('onReady -> 页面已经渲染完成');
// 进行相对耗时或需要页面已加载才执行的网络请求
fetchUserProfile();
}
void fetchUserProfile() async {
// ... 异步请求
// profile.value = ...
}
}
PS:有些人也喜欢把所有网络请求都放 onInit(),这样页面一进来就开始请求。并不是不可以,但若数据体量较大,则可能延迟页面的渲染,用户体验没有 onReady() 友好。
3. onClose()
何时被调用?
- 当 Controller 所在的页面被销毁、或者我们主动调用了 Get.delete()(若此 Controller 未被其它依赖持有)时,会触发 onClose().
- 在默认的 SmartManagement.full 模式下,离开页面并且路由不再保留时,就会自动销毁该 Controller 并调用 onClose()。
适合处理的逻辑
- 释放资源:例如取消订阅、关闭流、dispose Timer / Worker、断开 WebSocket 等。
- 保存数据:在页面关闭时,将一些状态持久化到本地或共享存储。
- 取消请求:如果有网络请求或异步操作在进行,可以在这里清理或取消。
class ChatController extends GetxController {
late final StreamSubscription messageSub;
@override
void onInit() {
super.onInit();
messageSub = ChatService.onNewMessage.listen((msg) {
// handle new message
});
}
@override
void onClose() {
// 页面销毁时,取消监听,避免内存泄漏
messageSub.cancel();
super.onClose();
}
}
⚠️ 注意:若你在 onInit() 或其他地方启动了定时器 (Timer)、stream 监听、socket 连接等,一定要在 onClose() 中主动释放,否则会出现内存泄漏或意外的后台执行。
二、其他较少见的生命周期/方法
onStart()
在部分早期版本或某些扩展中,可能会看到 onStart(),但在官方的最新控制器中并没有默认实现。onStart() 更类似于一个“刚开始启动 Controller”的时刻,一般你可以把它当作是比 onInit() 更早的一层(但通常我们不使用它)。
目前常见的场景会使用 onInit() 即可满足需求。若你要在生成 Controller 之后,还没正式初始化前执行某些事情,可以考虑直接在构造函数或 onInit() 里处理。
onDelete()
当使用 Get.delete(force: true) 时,可能会触发到 onDelete。它在 GetX 的内部封装里用来清理一些辅助资源。大多数时候你只需要在 onClose() 中进行资源释放即可。
三、在不同生命周期做什么业务最合适
下面做一个小结,可以作为一个通用的“对照表”:
生命周期方法 | 何时调用 & 适用场景 | 建议做的事 | 不建议做的事 |
---|---|---|---|
onInit() | 1. 当 Controller 被注入(或 Get.find 首次被调用)时触发2. 页面构建前执行,属于 Controller 的初始化阶段 | 1. 绑定 Worker(ever , debounce , interval 等)2. 依赖注入或 Get.find() 3. 轻量级数据准备(如同步读取、少量网络请求) 4. 做一些初步逻辑(设置默认值、读取缓存等) | 1. 不要进行非常耗时的操作(会阻塞页面首帧渲染) 2. 不要在此处弹出对话框或执行大量 UI 操作(此时页面尚未渲染) |
onReady() | 1. 当页面(Widget)已完成渲染后触发 2. 比 onInit() 更晚,可确保用户已能看到页面主体 | 1. 加载大数据或较长耗时异步请求(确保页面不白屏) 2. 启动动画、弹出对话框或引导 3. 获取用户真正所需的核心数据(如详情、列表) 4. 启动定时器、轮询等(页面已可见,用户体验更好) | 1. 不适合过早执行的操作(应放在 onInit() 中)2. 如果某操作必须在页面显示前完成,放在 onInit() 但要注意影响首屏渲染,需平衡用户体验 |
onClose() | 1. 当 Controller 被销毁或页面结束路由栈时触发 2. 在默认 SmartManagement.full 模式下,离开页面会销毁对应 Controller | 1. 释放资源(如 Stream 、Timer 、WebSocket、Controller)2. 保存页面状态或数据(写入本地或全局服务) 3. 取消未完成的网络请求(避免后台无用操作) | 1. 避免过多耗时操作(用户已离开页面) 2. 不建议执行过于复杂的逻辑,可考虑改为后台异步执行或交给全局服务处理 |
四、完整实例
以下示例演示一个带有 Worker、网络请求、资源释放的 Controller,并在不同生命周期执行不同逻辑。
class ProductController extends GetxController {
// 产品列表
final products = <Product>[].obs;
// 搜索关键字
final searchQuery = ''.obs;
// 定时轮询的 timer
Timer? pollingTimer;
@override
void onInit() {
super.onInit();
print('ProductController -> onInit');
// 1. Worker - 监听搜索关键字变化,触发搜索
debounce(searchQuery, (_) {
fetchProducts(searchQuery.value);
}, time: Duration(milliseconds: 500));
// 2. 可以进行一些轻量级初始化操作,或者尝试用局部缓存
final cachedData = LocalCache.get('product_list');
if (cachedData != null) {
products.assignAll(cachedData);
}
}
@override
void onReady() {
super.onReady();
print('ProductController -> onReady');
// 页面已渲染,可以进行真正的网络请求
fetchProducts();
// 每 30 秒轮询一次数据
pollingTimer = Timer.periodic(Duration(seconds: 30), (timer) {
fetchProducts();
});
}
@override
void onClose() {
print('ProductController -> onClose');
// 1. 释放 Timer
pollingTimer?.cancel();
// 2. 可能需要保存最后的产品列表
LocalCache.save('product_list', products.toList());
// 3. 执行父类 onClose
super.onClose();
}
void fetchProducts([String? query]) async {
// ... 调接口获取产品列表
// products.value = ...
print('Fetching products with query: $query');
}
}
- 在 onInit() 中,设置了 searchQuery 的去抖监听,并尝试加载本地缓存数据。
- 在 onReady() 中,页面渲染完成后进行网络请求、启动定时轮询。
- 在 onClose() 中,销毁 Timer 并保存数据到本地。
五、总结
onInit():
- 适合做初始绑定、轻量初始化、Worker 监听
- 时间点:Controller 被创建、注入后立即执行
onReady():
- 适合在页面渲染后再做更多数据加载、动画启动、提示弹窗
- 时间点:Widget build 完成后
onClose():
- 适合资源释放、销毁定时器或保存数据
- 时间点:页面或 Controller 被销毁前
合理利用 GetX Controller 的这些生命周期方法,可以让你的业务逻辑分层更加清晰、页面渲染和数据请求的时机更可控,也更便于管理资源和避免内存泄漏。