- 前端
【免费下载链接】getx
Open screens/snackbars/dialogs/bottomSheets without context, manage states and inject dependencies easily with Get.
本文基于仓库文档 documentation/ru_RU/dependency_management.md 编写,并结合 lib/get_instance、lib/get_core 等源码进行纵深验证。
GetX 内置了一套简单而强大的依赖管理器(Dependency Manager / Instance Manager),它允许你用一行代码拿到与 Bloc 或 Controller 相同的类实例,全程不需要 Provider、InheritedWidget,甚至不需要 BuildContext。无论你的应用是否使用 GetX 的状态管理,这套依赖注入系统都可以独立工作。阅读本文后,你将掌握Get.put/Get.lazyPut/Get.putAsync/Get.create四种注册方式及其内存语义差异,理解Bindings如何把路由、状态管理与依赖管理绑定在一起,并能按需配置SmartManagement的三种内存清理策略,彻底告别手动管理控制器生命周期的烦恼。
一、核心思路:把实例交给 Get,而不是交给类
传统写法中,你在类内部手动创建实例:
Controller controller = Controller();GetX 的写法则是在 Get 的实例中创建它,从而让这个实例在整个应用中随处可用:
Controller controller = Get.put(Controller());这样你便可以在任意位置使用你的控制器(或 Bloc):
final controller = Get.find<Controller>();两点重要说明:
- 与状态管理解耦:GetX 的依赖管理是独立于包内其他部分的。如果你的应用已经在使用其他状态管理器,完全不必迁移,可以单独引入这套依赖注入管理器,不会产生任何冲突(见 instance_manager.dart 中对
get_instance的独立导出)。 - 配合状态管理使用:如果你要使用 GetX 的状态管理,请务必关注本文后面的 Bindings API——它能把视图与控制器之间的连接工作简化到极致。
二、四种实例创建方法详解
2.1Get.put():最常用的即时注册
这是最普遍的依赖注入方式,非常适合注册视图的控制器。实例会立即创建并存入内存:
Get.put<SomeClass>(SomeClass()); Get.put<LoginController>(LoginController(), permanent: true); Get.put<ListItemController>(ListItemController, tag: "some unique string");Get.put支持的全部参数:
Get.put<S>( // 必填:想要保存的类实例,如控制器或其他任何对象 // 注意:"S" 表示可以是任意类型的类 S dependency, // 可选:当需要注册多个同类型实例时使用 // 因为通常通过 Get.find<Controller>() 按类型查找, // 需要用 tag 来区分你要的是哪一个实例 // 必须是唯一的字符串 String tag, // 可选:默认情况下 Get 会在实例不再被使用后将其销毁 // (例如某个被关闭视图的控制器), // 但你可能希望实例在整个应用中常驻, // 例如 SharedPreferences 之类的对象,此时用 permanent // 默认值为 false bool permanent = false, // 可选:允许在测试中先注册一个抽象类,再替换为另一个实现并继续测试 // 默认值为 false bool overrideAbstract = false, // 可选:允许用函数而非依赖本身来创建依赖 // 这个参数不常用 InstanceBuilderCallback<S> builder, )源码视角:查看 extension_instance.dart 可以看到,Get.put的实现是调用内部_insert方法,传入isSingleton: true、permanent(默认 false)以及一个返回该依赖的 builder,随后立刻执行find<S>(tag: tag)完成初始化:
S put<S>(S dependency, {String? tag, bool permanent = false}) { _insert( isSingleton: true, name: tag, permanent: permanent, builder: (() => dependency), ); return find<S>(tag: tag); }也就是说,Get.put是"注册 + 立即初始化"两步合一。
2.2Get.lazyPut():延迟加载
可以推迟依赖的加载,让它只在被真正使用的那一刻才创建。非常适合计算资源密集的类,或者当你在一个地方(例如 Bindings 类中)集中注册多个类、但此刻并不确定都会用到它们时。
/// ApiMock 只在有人第一次使用 Get.find<ApiMock>() 时才会被调用 Get.lazyPut<ApiMock>(() => ApiMock()); Get.lazyPut<FirebaseAuth>( () { // ... 需要的话可以在这里写一些逻辑 return FirebaseAuth(); }, tag: Math.random().toString(), fenix: true, ); Get.lazyPut<Controller>(() => Controller());Get.lazyPut支持的全部参数:
Get.lazyPut<S>( // 必填:当你的类第一次被调用时才会执行的方法 InstanceBuilderCallback builder, // 可选:与 Get.put() 相同,用于注册同类的多个不同实例 // 必须唯一 String tag, // 可选:与 "permanent" 类似,区别在于——实例在未被使用时会 // 被销毁,但当再次需要它时,Get 会重新创建实例 // 与 Bindings API 中的 "SmartManagement.keepFactory" 行为一致 // 默认值为 false bool fenix = false, )源码视角:lazyPut同样走_insert,但注意它有一个关键细节——当没有显式传入fenix时,其默认值取决于全局Get.smartManagement:
void lazyPut<S>( InstanceBuilderCallback<S> builder, { String? tag, bool? fenix, bool permanent = false, }) { _insert( isSingleton: true, name: tag, permanent: permanent, builder: builder, fenix: fenix ?? Get.smartManagement == SmartManagement.keepFactory, ); }见 extension_instance.dart。这与文档中"fenix等价于SmartManagement.keepFactory"的描述完全吻合:在keepFactory模式下,即使不写fenix: true,lazyPut注册的工厂也会被保留以便重建实例。
2.3Get.putAsync():注册异步实例
当你需要注册一个异步创建的实例时,使用Get.putAsync:
Get.putAsync<SharedPreferences>(() async { final prefs = await SharedPreferences.getInstance(); await prefs.setInt('counter', 12345); return prefs; }); Get.putAsync<YourAsyncClass>(() async => await YourAsyncClass());Get.putAsync支持的全部参数:
Get.putAsync<S>( // 必填:用于实例化你的类的异步方法 AsyncInstanceBuilderCallback<S> builder, // 可选:与 Get.put() 相同,用于注册同类的多个不同实例 // 必须唯一 String tag, // 可选:与 Get.put() 中的同名参数一致,用于需要在整个应用中 // 保持该实例存活的情况 // 默认值为 false bool permanent = false, )源码视角:异步 builder 的函数签名在源码中以 typedef 形式保留,见 extension_instance.dart:
typedef AsyncInstanceBuilderCallback<S> = Future<S> Function();实战示例:在应用启动阶段初始化服务非常常见,README 中给出的典型用法是配合await等待初始化完成:
await Get.putAsync(() => DbService().init()); await Get.putAsync(SettingsService()).init();2.4Get.create():每次 find 都"新建"的工厂
这是一个比较"取巧"的方法,它与Get.find配合时,每次调用都会重新构建一个全新实例。更详细的差异说明见本文"方法间的差异"一节:
Get.Create<SomeClass>(() => SomeClass()); Get.Create<LoginController>(() => LoginController());Get.create支持的全部参数:
Get.create<S>( // 必填:一个函数,每次调用 Get.find() 时都会通过它 // "重新制造"一个新的类实例 // 示例:Get.create<YourClass>(() => YourClass()) FcBuilderFunc<S> builder, // 可选:与 Get.put() 类似,但在需要同类的多个实例时使用 // 特别适用于列表中的每一项都需要自己独立控制器的场景 // 必须是唯一的字符串。注意这里参数名从 tag 变成了 name String name, // 可选:与 Get.put() 一样,用于需要在整个应用中保持实例存活的情况。 // 区别在于 Get.create 中 permanent 默认为 true bool permanent = true, )源码视角:从源码结构看,Get.create的语义与spawn方法一致(见 extension_instance.dart),其核心是isSingleton: false,意味着每次find都会调用 builder 生成新实例:
void spawn<S>( InstanceBuilderCallback<S> builder, { String? tag, bool permanent = true, }) { _insert( isSingleton: false, name: tag, builder: builder, permanent: permanent, ); }对应内部工厂 _InstanceBuilderFactory 的getDependency()逻辑:isSingleton == true时缓存并复用dependency,isSingleton == false时每次都执行builderFunc()返回新实例。由于permanent默认为 true,这类实例默认不会跨页面被销毁——这正是文档强调"Get.create需要与GetWidget配合使用"的原因(例如 ListView 中每个条目需要独一无二且不被回收的实例)。
三、取回与清理:Get.find与Get.delete
设想你穿越了众多路由,需要拿到控制器里残留的数据。在其他方案里你需要状态管理 + Provider 或 Get_it 组合,但 Get 不需要——你只需让 Get "找到"你的控制器:
final controller = Get.find<Controller>(); // 或者 Controller controller = Get.find(); // 没错,这看起来像魔法:Get 会找到你的控制器并把它交给你。 // 哪怕你有 100 万个已实例化的控制器,Get 也总是能给出正确的那一个。拿到控制器后,就可以恢复其中此前获取的数据:
Text(controller.textFromApi);返回值是普通类,所以你可以对它做任何操作:
int count = Get.find<SharedPreferences>().getInt('counter'); print(count); // out: 12345删除某个 Get 实例:
Get.delete<Controller>(); // 通常你并不需要手动做这件事,因为 GetX 已经会自动删除不再使用的控制器源码视角:Get.find的完整链路在 extension_instance.dart:先检查是否已注册,未注册会抛出"$S" not found的明确错误提示;已注册则进入_initDependencies完成生命周期初始化(对GetLifeCycleMixin类型调用onStart),再返回实例。Get.delete(同文件 L357-L414)则会依据permanent、fenix、GetxServiceMixin等条件决定是真正移除、重置工厂还是拒绝删除。
另外值得了解的几个相关能力(均定义于 extension_instance.dart):
Get.findOrNull<S>():未注册时返回 null 而不是抛异常;Get.putOrFind<S>(builder):已注册则直接取回,否则执行 builder 注册;Get.replace<S>(child)/Get.lazyReplace<S>(builder):替换父级实例,适合测试或热替换;Get.deleteAll()/Get.resetInstance():清空所有注册(resetInstance常用于单元测试的 tearDown);Get.isRegistered<S>()/Get.isPrepared<S>():查询注册与懒加载准备状态。
四、方法间的差异:permanent、fenix 与底层内存模型
首先来看Get.lazyPut的fenix参数与其他方法的permanent参数。两者最根本的区别在于你希望如何保存这些实例。
先记住默认行为:默认情况下,GetX 会在实例不再被使用时将其删除。也就是说:如果屏幕 1 有控制器 1,屏幕 2 有控制器 2,当你把第一个路由从栈中移除时(例如使用Get.off()或Get.offNamed()),控制器 1 现在不再被使用,因此会被清除。
- 使用
permanent: true时,控制器不会在这次页面切换中丢失——这对你想在整个应用生命周期内保持的服务非常有用; fenix则是为"页面切换期间丢失也无所谓,但一旦需要就必须可用"的服务设计的。本质上它会清理掉未被使用的控制器/服务/类,但当再次需要时,它会"浴火重生"——重新创建一个新实例。
结合源码,四类方法的差异可以精确归纳如下:
| 方法 | 底层调用 | isSingleton | permanent 默认值 | 初始化时机 | 内存位置 |
|---|---|---|---|---|---|
Get.put | _insert | true | false | 注册后立即find初始化 | 通用实例区 |
Get.putAsync | _insert(异步 builder) | true | false | 注册后立即初始化 | 通用实例区 |
Get.create(对应spawn) | _insert | false | true | 首次find时才创建 | 每次新建 |
Get.lazyPut | _insert | true | false | 首次find时才创建 | 工厂区 → 实例区 |
Get.put 与 Get.putAsync 遵循相同的创建顺序,区别只是后者使用异步方法:两者都会创建并立即初始化实例,通过内部
insert方法以permanent: false、isSingleton: true直接插入内存(isSingleton参数仅用于告诉内部机制:是从dependency取依赖,还是从FcBuilderFunc(builder)取依赖),随后调用Get.find()立即初始化内存中的实例。Get.create:顾名思义,它"创造"你的依赖!与
Get.put()类似,它也调用内部insert创建实例,但permanent变成 true、isSingleton变成 false(因为我们是在"创造"依赖,它不可能是单例)。由于permanent: true,默认不会在页面切换间丢失它!而且Get.find()不会被立即调用,它等待在屏幕上实际使用时才触发。Get.create的设计目标就是创建"不共享但又不被删除"的实例——例如 ListView 中的按钮,每个列表项都需要唯一的实例,因此Get.create需要与GetWidget搭配使用。Get.lazyPut:如名称所示,这是一个惰性过程。实例"被创建"但不会立即调用使用,而是处于待命状态。与其他方法不同,它不调用
insert直接注册实例,而是把实例插入另一块内存区域——负责判断实例能否重建的区域,我们称之为"工厂"(factory)。如果我们要创建稍后才会用到的东西,它不会与当前正在使用的东西混在一起。fenix的魔力就在这里体现:如果你选择fenix: false,且你的smartManagement不是keepFactory,那么当Get.find被调用时,实例会从"工厂"移动到通用实例内存区,随后默认从"工厂"中删除;如果你选择fenix: true,实例即使转移到了通用区,也会继续保留在"工厂"这个专用区域,以便未来再次调用。
五、Bindings:把路由、状态与依赖绑在一起
这可能是本包最重要的特性之一:它让路由、状态管理与依赖管理三者实现了完全集成。当一个路由从栈中移除时,所有与该路由关联对象的控制器、变量和实例都会从内存中清除。如果你使用了流(Streams)或定时器(Timers),它们也会被自动关闭,你完全无需操心。从 GetX 2.10 版本起,Bindings API 已经完整实现——你不再需要初始化方法,甚至如果不想的话连控制器都不用手动注入,可以在合适的地方启动你的控制器和服务。
Bindings 类的作用:它是一个将依赖注入"绑定"到路由、状态管理和依赖管理器上的类。这个类让 Get 知道:在使用某个特定控制器时当前显示的是哪个屏幕,以及在哪里、如何删除它。此外,Bindings 类还允许你控制 SmartManager 的配置——你可以定制依赖何时被整理:是当路由从栈中移除时,还是当使用它的 widget 被释放时,或者两者都不是。智能依赖管理会为你工作,但你仍然可以按需微调。
5.1 创建 Binding 类
创建一个类并实现Bindings接口:
class HomeBinding implements Bindings {}你的 IDE 会自动提示你重写dependencies方法,只需照做并在其中放入该路由要使用的所有类:
class HomeBinding implements Bindings { @override void dependencies() { Get.lazyPut<HomeController>(() => HomeController()); Get.put<Service>(()=> Api()); } } class DetailsBinding implements Bindings { @override void dependencies() { Get.lazyPut<DetailsController>(() => DetailsController()); } }然后在路由配置中告诉 Get:该路由将使用这个 Binding 来建立路由管理器、依赖与状态之间的连接。
使用命名路由(GetPage):
getPages: [ GetPage( name: '/', page: () => HomeView(), binding: HomeBinding(), ), GetPage( name: '/details', page: () => DetailsView(), binding: DetailsBinding(), ), ];使用普通路由:
Get.to(Home(), binding: HomeBinding()); Get.to(DetailsView(), binding: DetailsBinding());源码视角:GetPage的binding字段类型为BindingsInterface?(见 get_route.dart),而Bindings接口定义在 bindings_interface.dart。仓库中的 example/lib/routes/app_pages.dart 正是这种写法的真实样例——每个GetPage都通过binding:挂载对应的 Binding。
除此之外,你还可以在GetMaterialApp中创建initialBinding,一次性注入所有需要预先创建的依赖。Binding 会在路由被调用时执行:
GetMaterialApp( initialBinding: SampleBind(), home: Home(), );从此你不再需要操心应用的内存管理,Get 会替你完成。
5.2 BindingsBuilder:免去每个路由一个类
默认情况下,Binding 通过创建实现 Bindings 接口的类来构建。作为替代方案,你可以使用BindingsBuilder回调,直接用函数创建你想要的一切:
getPages: [ GetPage( name: '/', page: () => HomeView(), binding: BindingsBuilder(() { Get.lazyPut<ControllerX>(() => ControllerX()); Get.put<Service>(()=> Api()); }), ), GetPage( name: '/details', page: () => DetailsView(), binding: BindingsBuilder(() { Get.lazyPut<DetailsController>(() => DetailsController()); }), ), ];这样你就能避免为每个路由单独创建一个 Binding 类,让配置更简洁。两种方式都完美可用,按你的口味选择即可。
5.3 当前仓库的 Binding 实践
从源码结构看,当前仓库的Binding已经演进为返回List<Bind>的形态(见 get_state.dart 处的abstract class Binding extends BindingsInterface<List<Bind>>),配合Bind.lazyPut/Bind.put等声明式写法。仓库示例 example/lib/pages/home/bindings/home_binding.dart 展示了接口与实现分离的推荐实践:
class HomeBinding extends Binding { @override List<Bind> dependencies() { return [ Bind.lazyPut<IHomeProvider>(() => HomeProvider()), Bind.lazyPut<IHomeRepository>(() => HomeRepository(provider: Get.find())), Bind.lazyPut(() => HomeController(homeRepository: Get.find())), ]; } }这种写法让抽象接口(IHomeProvider、IHomeRepository)与具体实现解耦,配合Get.find()完成依赖链的自动装配,是大型项目中组织依赖的成熟模式。
六、SmartManagement:三种内存清理策略
即使某个 widget 在使用控制器时因异常未能正常释放,GetX 默认也会将不再使用的控制器从内存中删除。这就是所谓的"full"(完全)依赖管理模式。如果你希望改变 GetX 管理类删除的方式,可以使用SmartManagement类来指定不同的行为。
6.1 如何切换
通常你不需要修改这个配置,但如果确实要改,方式如下:
void main () { runApp( GetMaterialApp( smartManagement: SmartManagement.onlyBuilders, // 在这里配置 home: Home(), ), ); }源码视角:SmartManagement枚举定义于 smart_management.dart,共三个值full、onlyBuilder、keepFactory;默认值在 get_interface.dart 中初始化为SmartManagement.full。
6.2SmartManagement.full(默认)
删除所有不再使用且未标记为 permanent 的类。大多数情况下你应该保持这个配置不动——如果你是 GetX 新手,请勿更改它。
6.3SmartManagement.onlyBuilders
使用该选项时,只有通过init:启动的控制器,或通过 Binding 中的Get.lazyPut()加载的控制器才会被删除。
如果你使用Get.put()、Get.putAsync()或任何其他方式注册依赖,SmartManagement 将没有权限移除该依赖。而在默认行为下,即使是Get.put创建的 widget 也会被移除——这是与onlyBuilders的关键区别。
6.4SmartManagement.keepFactory
与SmartManagement.full一样,它会在依赖不再被使用时将其移除;但它会保留工厂(factory),这意味着当你再次需要这个实例时,它可以重新创建依赖。
源码佐证:上文提到lazyPut在未显式指定fenix时,会以Get.smartManagement == SmartManagement.keepFactory作为默认值(extension_instance.dart),这正是"keepFactory 等价于全局 fenix"这一设计的实现。而_initDependencies中(同文件 L202-L207),只有smartManagement != onlyBuilder时,实例才会被登记关联到当前路由,从而在路由销毁时触发自动清理——这也解释了为什么onlyBuilders模式下Get.put创建的实例不在智能管理范围内。
七、Bindings 的底层工作方式
Bindings 创建的是临时工厂:它们在你点击跳转到另一个屏幕的瞬间被创建,并随着转场动画的完成而被销毁。这个过程如此之快,以至于分析器(Analyzer)甚至都来不及记录。
当你再次跳转到这个屏幕时,会调用一个新的临时工厂,因此这种方式比使用SmartManagement.keepFactory更受推荐。但如果你不想创建 Bindings,或者想把所有依赖放在同一个 Binding 中,那么keepFactory会帮到你。
工厂本身占用的内存极小——它们不保存实例,只保存一个包含你所需类"形状"的函数。内存成本非常低。但由于这个库的目标是以最少资源获得尽可能高的性能,Get 默认连工厂都会删除。请使用你感觉更合适的方式。
八、注意事项与最佳实践
不要在多 Binding 场景下使用
SmartManagement.keepFactory。它被设计用于"完全不使用 Bindings",或"只使用一个绑定在GetMaterialApp的initialBinding中"的场景。使用 Bindings 完全可选。如果愿意,你可以毫无问题地对使用某控制器的类直接用
Get.put()和Get.find()。但如果你在与服务(Services)或其他抽象打交道,建议使用 Bindings 以获得更好的组织性。服务的特殊语义:从源码看,继承自
GetxService的实例(见 lifecycle.dart)默认不会被自动删除,也不能被Get.delete()移除(除非force: true),唯一彻底清理方式是Get.reset()。它适合认证(Auth)这类一旦启动就必须常驻内存的服务。控制器生命周期:实现了
GetLifeCycleMixin的控制器在注册/查找时自动走onStart() → onInit() → onReady(),销毁时走onDelete() → onClose()(见 lifecycle.dart),流、定时器、TextEditingController、AnimationController等资源请放在onClose()中释放。测试辅助:
Get.resetInstance()可以在单元测试的 tearDown 中一键清空所有注册,避免测试间相互污染;overrideAbstract参数则允许在测试中用具体实现替换抽象类。
九、小结
GetX 的依赖管理器以"注册 → 查找 → 智能回收"为核心闭环:Get.put与Get.putAsync即时创建并初始化实例,Get.lazyPut延迟到首次使用时创建并支持fenix重生,Get.create(对应spawn)每次查找都新建实例;Bindings与GetPage.binding将依赖生命周期绑定到路由生命周期,而SmartManagement的full、onlyBuilders、keepFactory三种策略让你在"彻底清理""仅管理 Builder 注册"与"保留工厂可重建"之间自由取舍。配合源码中 extension_instance.dart、smart_management.dart 与示例项目 example/lib/routes/app_pages.dart 的印证,你现在可以从容地在自己的应用中设计一套无 Context、自动回收、与路由深度集成的依赖注入体系了。
- 前端
【免费下载链接】getx
Open screens/snackbars/dialogs/bottomSheets without context, manage states and inject dependencies easily with Get.
相关推荐
GetX 依赖管理完全指南:无 Context 的实例注入、Bindings 与 SmartManagement 实战
GetX 依赖管理完全指南:无 Context 的实例注入、Bindings 与 SmartManagement 实战 GetX(本仓库 gh_mirrors/
前端GetX 依赖注入完全指南:从 Get.put、Bindings 到 SmartManagement 的源码级解析
GetX 依赖注入完全指南:从 Get.put、Bindings 到 SmartManagement 的源码级解析 Get 内置的依赖管理器让你可以用一行代码注
前端GetX 依赖管理完全指南:从 Get.put 到 Bindings 与 SmartManagement 的源码级解析
GetX 依赖管理完全指南:从 Get.put 到 Bindings 与 SmartManagement 的源码级解析 导读 GetX(本仓库 getx )内置
前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考