news 2026/9/23 22:58:55

GetX 依赖管理实战指南:Get.put / Get.lazyPut / Get.create 与 Bindings 智能生命周期的完整解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
GetX 依赖管理实战指南:Get.put / Get.lazyPut / Get.create 与 Bindings 智能生命周期的完整解析
  • 前端

【免费下载链接】getx

Open screens/snackbars/dialogs/bottomSheets without context, manage states and inject dependencies easily with Get.

项目地址:https://gitcode.com/gh_mirrors/ge/getx
点击查看免费下载

本篇技术指南围绕 Get(GetX)的依赖管理系统展开,详细讲解Get.putGet.lazyPutGet.putAsyncGet.create四种实例化方法的参数与适用场景,以及Get.findGet.deletereplace/lazyReplace的使用方式,并结合源码剖析 Bindings、BindingsBuilder 与 SmartManagement 的底层实现。读完本文,你将能够在不依赖 Provider context 与 InheritedWidget 的情况下完成依赖注入,理解permanentfenix的本质区别,并为路由配置自动回收、按需重建的控制器生命周期。本文对应的原始文档位于 documentation/ar_EG/dependency_management.md(英文原版见 documentation/en_US/dependency_management.md),全部源码佐证均来自当前仓库 lib/get_instance 与 lib/get_state_manager 等目录。

一、为什么需要 Get 的依赖管理器

Get 提供了一套"简单而强大"的依赖管理器:你不需要 Provider 的context,不需要InheritedWidget,只需要一行代码,就能像获取普通类一样拿到你的 Controller 或 Bloc 实例:

Controller controller = Get.put(Controller()); // 代替 Controller controller = Controller();

其核心思想是:不要在你使用类的内部去实例化它,而是把它实例化到 Get 这个"全局容器"里,让它在整个 App 范围内可用。之后你可以像使用普通类一样使用这个 controller(或 Bloc 类)。

使用前有两个重要提示(文档原文 Note):

  • 如果你正在使用 Get 的状态管理器,请重点关注 Bindings API,它能更轻松地将视图与控制器连接起来;
  • Get 的依赖管理与其他部分(状态管理、路由)是解耦的。即便你的应用已经在使用其他任何状态管理器,也无需更换,可以直接无冲突地使用这套依赖注入管理器。

从源码看,这套机制的核心是一个以类型(和可选的 tag)为 key 的 HashMap。在 extension_instance.dart 中,_singl保存了所有通过Get.put注册的实例工厂;_getKey生成 key 的逻辑是name == null ? type.toString() : type.toString() + name,即"类型名 + tag"共同决定唯一性,这正是同一类型可通过 tag 注册多个实例的原理。

二、四大实例化方法(Instancing Methods)

文档将注册依赖的方式归纳为四种方法,各有各的参数与适用场景。

Get.put():最常用的同步注册

Get.put是最常见的插入依赖的方式,例如视图对应的 Controller:

Get.put<SomeClass>(SomeClass()); Get.put<LoginController>(LoginController(), permanent: true); Get.put<ListItemController>(ListItemController, tag: "some unique string");

Get.put支持的全部参数如下(来自文档):

Get.put<S>( // 必填:想要保存的类实例,比如 controller 或任何对象 // 注意:"S" 表示它可以是任意类型的类 S dependency // 可选:当你需要多个同类型实例时使用 // 因为通常用 Get.find<Controller>() 获取类,需要用 tag 告诉 Get 你要哪个实例 // 必须是唯一的字符串 String tag, // 可选:默认情况下,Get 会在实例不再被使用时将其释放 // (例如被关闭的视图的 controller)。但如果这个实例需要在整个 App // 中一直存活(比如 SharedPreferences 之类的实例),就使用这个参数 // 默认为 false bool permanent = false, // 可选:允许你在测试中先使用抽象类,再将其替换为另一个类并继续测试 // 默认为 false bool overrideAbstract = false, // 可选:允许你用一个函数来创建依赖,而不是直接传依赖本身 // 这个参数不常用 InstanceBuilderCallback<S> builder, )

结合源码可以更精确地理解put的行为。在 extension_instance.dart 中:

S put<S>( S dependency, { String? tag, bool permanent = false, }) { _insert( isSingleton: true, name: tag, permanent: permanent, builder: (() => dependency)); return find<S>(tag: tag); }

它通过内部方法_insertisSingleton: truepermanent: permanent的方式把实例登记进_singl,随后立即调用find<S>,也就是说put是"注册即初始化",实例在调用返回时已经可用。_insert中还有一个细节:如果该 key 已存在且不是isDirty状态,会直接返回,避免重复注册覆盖。仓库测试 get_instance_test.dart 验证了Get.putGet.find返回同一实例。

Get.lazyPut():懒加载

lazyPut允许你懒加载依赖:只有第一次真正使用(Get.find)时才会实例化。这非常适合计算开销大的类,或者像 Bindings 类里集中注册多个类、但当时并不需要全部使用的情况。

/// ApiMock 只有在有人第一次使用 Get.find<ApiMock> 时才会被调用 Get.lazyPut<ApiMock>(() => ApiMock()); Get.lazyPut<FirebaseAuth>( () { // ... 如果需要,可以在这里写一些逻辑 return FirebaseAuth(); }, tag: Math.random().toString(), fenix: true ) Get.lazyPut<Controller>( () => Controller() )

lazyPut的参数说明(来自文档):

Get.lazyPut<S>( // 必填:一个方法,在类第一次被调用时执行 InstanceBuilderCallback builder, // 可选:与 Get.put() 相同,用于需要同一类的多个不同实例 // 必须是唯一的 String tag, // 可选:与 "permanent" 类似,区别在于:实例在不再使用时会被丢弃, // 但当再次需要它时,Get 会重新创建实例 // 与 bindings API 中的 "SmartManagement.keepFactory" 行为一致 // 默认为 false bool fenix = false )

源码层面,lazyPutput一样调用_insert,但不会立即执行find,实例只是以工厂回调的形式注册;同时有个值得注意的默认逻辑:

fenix: fenix ?? Get.smartManagement == SmartManagement.keepFactory,

也就是说,如果你没有显式传fenix,那么fenix的值会跟随全局smartManagement配置:当SmartManagement.keepFactory生效时,lazyPut默认开启 fenix 行为(详见后文 SmartManagement 章节)。

仓库测试 get_instance_test.dart 验证了 fenix 语义:lazyPut(fenix: true)注册的实例在Get.delete后再find,会重新得到 count 归零的新实例;而fenix: false时,delete之后find会直接抛出异常(因为实例已从内存移除)。

Get.putAsync():异步注册

当需要注册一个异步创建的实例时(比如需要await初始化),使用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<S>( // 必填:一个异步方法,用于实例化你的类 AsyncInstanceBuilderCallback<S> builder, // 可选:与 Get.put() 相同,用于同一类的多个不同实例 // 必须是唯一的 String tag, // 可选:与 Get.put() 相同,用于需要在整个 App 中保持存活的实例 // 默认为 false bool permanent = false )

Get.put一样,putAsync也是"注册即初始化",区别仅在于初始化过程是异步完成的;注册完成后同样立即对实例调用find完成初始化。

Get.create():每次 find 都"重新制造"

Get.create是最特别的一个方法(文档原话:"This one is tricky",建议先阅读方法之间的差异一节再回来理解)。

Get.Create<SomeClass>(() => SomeClass()); Get.Create<LoginController>(() => LoginController());

参数说明(来自文档):

Get.create<S>( // 必填:一个返回类的函数,每次调用 Get.find() 时都会用这个函数"重新制造"一个实例 // 示例:Get.create<YourClass>(() => YourClass()) FcBuilderFunc<S> builder, // 可选:与 Get.put() 中的 tag 类似,用于同一类的多个实例 // 当你有一个列表、每个列表项都需要自己的 controller 时非常有用 // 必须是唯一的字符串(注意参数名是 name 而不是 tag) String name, // 可选:与 Get.put() 中的 permanent 类似,用于需要在整个 App 中 // 保持存活的实例。区别在于:Get.create 中 permanent 默认为 true bool permanent = true )

从当前仓库源码结构看,Get.create的底层语义与内部方法spawn一致:以isSingleton: falsepermanent: true注册工厂,于是每次Get.find都会调用 builder 生成全新实例(非单例),同时因为permanent: true,实例不会在页面切换时被回收。文档特别强调:Get.create的目标是"创建不共享、但不会被释放的实例",例如 listView 中每个列表项想要独立且唯一的 controller,因此Get.create必须与 GetWidget 配合使用。仓库中 get_view.dart 的注释也印证了这一点:Get 会帮你缓存 controller,因此可以安全地使用Get.create。测试 get_instance_test.dart 验证了该语义:连续两次find得到的实例ct1 == ct2false

三、使用已注册的实例:Get.find 与 Get.delete

想象你已经导航了无数个路由,现在需要之前遗留在某个 controller 里的数据。传统方案需要状态管理器配合 Provider 或 get_it,而 Get 只需要一行Get.find,不需要任何额外依赖:

final controller = Get.find<Controller>(); // 或者 Controller controller = Get.find();

是的,看起来像魔法:Get 会找到你的 controller 并把它交给你。哪怕你实例化了 100 万个 controller,Get 也总能给你正确的那个。

拿到实例后就能正常恢复数据:

Text(controller.textFromApi);

由于返回值就是普通类,你可以对它做任何事:

int count = Get.find<SharedPreferences>().getInt('counter'); print(count); // out: 12345

移除一个实例:

Get.delete<Controller>(); // 通常你不需要这样做,因为 GetX 会自动删除不再使用的 controller

从源码看,find首先检查该 key 是否已注册,未注册则抛出提示(会提示你调用Get.putGet.lazyPut);已注册则进入_initDependencies初始化流程——如果实例实现了GetLifeCycleMixin_startController会触发其生命周期方法onStart(),并依据smartManagement配置决定是否把该依赖与当前路由绑定(RouterReportManager.reportDependencyLinkedToRoute),这正是"路由移除时自动回收依赖"的机制来源。

此外还有几个实用的配套 API:

  • Get.findOrNull<T>():已注册则返回实例,否则返回null,不会抛异常(见 extension_instance.dart);
  • Get.isRegistered<T>({tag}):判断实例是否已注册(extension_instance.dart);
  • Get.isPrepared<T>({tag}):判断是否还有未被初始化的懒加载工厂(extension_instance.dart);
  • Get.reset()/Get.resetInstance():清空所有注册实例(包括永久实例),适合在单元测试的tearDown中使用(见 extension_instance.dart)。

四、指定替代实例:replace 与 lazyReplace

已注册的实例可以被替换为同类或扩展类的实例,之后仍可通过原类型获取。这就是replacelazyReplace的用途:

abstract class BaseClass {} class ParentClass extends BaseClass {} class ChildClass extends ParentClass { bool isChild = true; } Get.put<BaseClass>(ParentClass()); Get.replace<BaseClass>(ChildClass()); final instance = Get.find<BaseClass>(); print(instance is ChildClass); // true class OtherClass extends BaseClass {} Get.lazyReplace<BaseClass>(() => OtherClass()); final instance = Get.find<BaseClass>(); print(instance is ChildClass); // false print(instance is OtherClass); // true

源码实现印证了"替换 = 删除 + 重新注册":

  • replace:先读取原实例的permanent状态,用delete(force: permanent)删除原实例,再以相同permanentput新实例;
  • lazyReplace:先删除,再以lazyPut注册新工厂,且fenix未显式传入时默认取原实例的permanent值。

这一能力与Get.putoverrideAbstract参数搭配,常用于测试场景:先用抽象类注册,再替换为具体实现类。仓库测试 get_instance_test.dart 展示了抽象类Service分别通过lazyPutspawn(create 语义)注入Api实现的用法。

五、permanent 与 fenix:方法间的本质差异

先厘清 permanent 与 fenix

二者的根本区别在于你希望如何存储实例

先重申默认行为:GetX 默认会在实例不再被使用时将其删除。例如:屏幕 1 有 controller 1,屏幕 2 有 controller 2,当你用Get.off()Get.offNamed()把第一条路由移出栈时,controller 1 失去用途,随即被清除。

  • 使用permanent: true:controller 不会在这次切换中丢失——非常适合希望在整个应用生命周期中存活的 Service。
  • fenix: true:用于"不担心它在页面切换时丢失,但当你需要它时它必须是活的"的服务。它会释放不再使用的 controller/service/类,但当你再次需要时,会"从灰烬中重生"(重新创建)一个新实例。

各方法的底层差异

文档给出了非常清晰的底层机制对照,结合 extension_instance.dart 中的_insert实现可以完全印证:

方法底层 insert 参数是否立即 find 初始化实例形态典型场景
Get.putpermanent: false, isSingleton: true是(调用后立即可用)单例(直接持有dependency视图 Controller
Get.putAsyncpermanent: false, isSingleton: true是(异步完成后立即 find)单例需要 await 初始化的实例
Get.createpermanent: true, isSingleton: false否(等界面用到时才调用)每次 find 新建(builderFunc每次执行)列表项各自的独立 Controller,需配合 GetWidget
Get.lazyPut不调用 insert,注册到"工厂"区否(首次 find 时创建)首次 find 后成为单例计算开销大、集中注册但不立即使用的类

逐条展开(文档原文要点):

  • Get.put 与 Get.putAsync遵循相同的创建顺序,区别仅是后者使用异步方法:两者都会创建并初始化实例,直接通过内部方法insertpermanent: falseisSingleton: true插入内存(isSingleton参数唯一的作用是决定取用dependency还是FcBuilderFunc作为依赖来源)。随后调用Get.find()立即初始化内存中的实例。
  • Get.create:顾名思义会"创建"依赖。与put类似也调用内部insert,但permanent变为trueisSingleton变为false(因为是"创建",不可能是单例)。由于permanent: true,默认享有"页面切换不丢失"的好处;同时Get.find()不会被立即调用,而是等待界面真正使用它时才触发。值得强调的是,Get.create的设计目标就是创建不共享、但不会被释放的实例。
  • Get.lazyPut:惰性过程。实例会被创建,但不会立即被调用,而是等待使用。与其他方法相反,这里不会调用insert,而是把实例插入内存的另一区域——负责判断"实例是否可以重建"的区域,文档称之为"factory(工厂)"。这样"未来要用的东西"不会与"正在使用的东西"混在一起。fenix的魔法就在这里:若fenix: falsesmartManagement不是keepFactory,那么首次Get.find时实例会从"工厂"区移到公共实例内存区,随即默认从"工厂"区移除;若fenix: true,实例即使在移入公共区后仍保留在工厂区,将来可再次被调用重建。

六、Bindings:路由、状态与依赖的完整集成

Bindings 也许是 Get 包最突出的差异化能力之一:路由管理器、状态管理器与依赖管理器的完整集成。当路由从栈中移除时,与之相关的所有 controller、变量和对象实例都会从内存中移除;如果你使用了 streams 或 timers,它们会被自动关闭,你无需操心这些。

自 Get v2.10 起完整实现了 Bindings API。你不再需要 init 方法,甚至可以不手动声明 controller 的类型——可以在合适的地方启动你的 controller 与 services。Binding 类的作用是把依赖注入"解耦",同时把路由与状态管理器、依赖管理器"绑定"在一起:这让 Get 能知道"某个 controller 被使用时正在显示哪个界面",也知道"在哪里、以何种方式释放它"。此外 Binding 类还允许你获得 SmartManager 配置控制权,决定依赖是在路由移出栈时、还是使用它的 widget 布局时被清理,或两者都不清理。

Bindings 类

创建一个实现Binding的类(注意当前源码中Bindings已标记为废弃,推荐使用Binding,见 bindings_interface.dart):

class HomeBinding implements Binding {}

IDE 会自动提示你重写dependencies方法,在该方法里注册这个路由要用到的所有类:

class HomeBinding implements Binding { @override void dependencies() { Get.lazyPut<HomeController>(() => HomeController()); Get.put<Service>(()=> Api()); } } class DetailsBinding implements Binding { @override void dependencies() { Get.lazyPut<DetailsController>(() => DetailsController()); } }

然后在路由声明中告知 Get 使用该 Binding,从而打通"路由管理器 ↔ 依赖 ↔ 状态":

使用命名路由:

getPages: [ GetPage( name: '/', page: () => HomeView(), binding: HomeBinding(), ), GetPage( name: '/details', page: () => DetailsView(), binding: DetailsBinding(), ), ];

使用普通路由:

Get.to(Home(), binding: HomeBinding()); Get.to(DetailsView(), binding: DetailsBinding())

这样你就不必再担心应用的内存管理,Get 会替你完成。

Binding 类会在路由被调用时执行;同时你也可以在GetMaterialApp中设置initialBinding,集中注册那些"一开始就要创建"的依赖:

GetMaterialApp( initialBinding: SampleBind(), home: Home(), );

仓库中initialBindingGetMaterialAppGetCupertinoApp的标准配置项(见 get_cupertino_app.dart),示例工程也采用了 Binding + 路由的完整分层(可参考 example/lib/routes/app_pages.dart 与 example_nav2/lib/app/routes/app_pages.dart)。

BindingsBuilder

默认方式是为每个路由创建实现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 类,更简洁。文档说明两种方式都完全有效,按个人偏好选择即可。

七、SmartManagement:智能内存管理

GetX 默认会释放不再使用的 controller,即使发生异常、使用它的 widget 未被正确销毁也不例外——这就是依赖管理的full模式。如果你希望改变 GetX 控制类释放的方式,可以通过SmartManagement类设置不同行为。源码中它是一个三值枚举,定义于 smart_management.dart,默认值为full(见 get_interface.dart)。

如何修改

通常你不需要修改此配置;如果确实需要,在GetMaterialApp上指定即可:

void main () { runApp( GetMaterialApp( smartManagement: SmartManagement.onlyBuilder // 在这里配置 home: Home(), ) ) }

smartManagementGetMaterialApp的内置配置参数,默认SmartManagement.full(见 get_material_app.dart)。

SmartManagement.full

默认模式。释放"未被使用且未被标记为 permanent"的类。绝大多数情况下你都应保持该配置不变,如果你是 GetX 新手,请不要改动它。

SmartManagement.onlyBuilder

该模式下,只有通过init:启动、或通过 Binding 里的Get.lazyPut()加载的 controller 才会被释放。如果你使用Get.put()Get.putAsync()或其他任何方式注册,SmartManagement 将没有权限删除该依赖。而默认行为(full)下,即使是通过Get.put实例化的 widget 也会被移除,这与onlyBuilder不同。

注意命名:当前仓库源码中的枚举名为onlyBuilder(单数),部分文档版本写作onlyBuilders(复数),使用时以源码为准。此外,路由侧也有对应逻辑:RouterReportManager只有在smartManagement != SmartManagement.onlyBuilder时才把依赖与路由绑定(见 router_report.dart),这解释了为何onlyBuilder模式下Get.put注册的实例不受路由生命周期管理。

SmartManagement.keepFactory

SmartManagement.full一样,在依赖不再使用时移除它;但会保留其工厂(factory),意味着当你再次需要该实例时会重新创建。因此它和lazyPut(fenix: true)效果等价——事实上源码中lazyPut的默认fenix正是跟随smartManagement == keepFactory(见 extension_instance.dart)。

八、Bindings 底层工作原理

文档给出了非常直观的底层说明:Bindings 创建的是临时工厂(transitory factories)——在你点击跳转另一界面的瞬间创建,并在界面切换动画结束的瞬间销毁,整个过程快到 analyzer 都无法察觉。当你再次导航到该界面时,会重新调用一个新的临时工厂。

因此 Bindings 方式优于直接使用SmartManagement.keepFactory;但如果你不想创建 Bindings,或想把所有依赖放在同一个 Binding 里,keepFactory会是有效的替代。工厂本身占用内存极小——它不持有实例,只保存一个"具有该类形状"的函数,内存成本非常低。不过由于本库的设计目标是"用最少的资源获得最大性能",Get 默认连工厂也会移除。二者按需选用即可。

九、注意事项与最佳实践(Notes)

文档在结尾给出了两条关键提醒,结合仓库实现可归纳为以下最佳实践:

  1. 不要在使用多个 Bindings 时使用SmartManagement.keepFactory。它被设计用于"不使用 Bindings"或"仅在 GetMaterialApp 的 initialBinding 中关联单个 Binding"的场景。

  2. Bindings 完全是可选的。你也可以直接在类中使用Get.put()Get.find()而毫无问题。但如果你使用 Services 或任何其他抽象,建议使用 Bindings 以获得更好的组织性。

  3. GetxService 不可被普通delete删除。源码 lifecycle.dart 说明:与 GetxController 不同,GetxService 一旦启动就常驻内存(例如 Auth 服务),唯一的移除方式是Get.reset()delete方法对实现GetxServiceMixin的实例也会直接返回(除非force: true),测试 get_instance_test.dart 验证了这一行为:普通deleteisRegistered仍为true,只有delete(force: true)才会真正移除。

  4. permanent 实例的删除需要 forcedelete方法对permanent: true的实例会拒绝删除并输出错误日志,除非显式传入force: true(见 extension_instance.dart)。

  5. 在测试中善用Get.reset()。它会清空所有注册实例(包括永久实例)与路由绑定(clearRouteBindings),保证测试之间互不污染(见 extension_instance.dart)。

  6. 生命周期钩子:如果你的 Controller 混入GetLifeCycleMixin(如 GetxController),实例创建时会依次触发onStart → onInit → onReady,销毁时触发onDelete → onClose(见 lifecycle.dart)。这让你可以在onClose中关闭 streams、timers、TextEditingController 等资源,配合路由回收机制实现无泄漏的内存管理。

以上内容完整继承自 documentation/ar_EG/dependency_management.md,并通过对 lib/get_instance/src/extension_instance.dart、lib/get_instance/src/bindings_interface.dart、lib/get_core/src/smart_management.dart、lib/get_instance/src/lifecycle.dart 与 test/instance/get_instance_test.dart 等源码和测试的对照,对每种方法的底层参数、内存行为与适用场景做了进一步印证与扩展。

  • 前端

【免费下载链接】getx

Open screens/snackbars/dialogs/bottomSheets without context, manage states and inject dependencies easily with Get.

项目地址:https://gitcode.com/gh_mirrors/ge/getx
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/23 22:56:02

数据恢复全攻略:从回收站到深度扫描,小白也能找回误删文件

1. 数据恢复这件事&#xff0c;到底难在哪电脑用久了&#xff0c;谁还没手滑删过几个文件。我见过最离谱的一位朋友&#xff0c;把攒了半年的项目素材拖进回收站&#xff0c;顺手又清空了&#xff0c;等反应过来的时候&#xff0c;整个人是懵的。后来他花了三天时间&#xff0c…

作者头像 李华
网站建设 2026/9/23 22:55:24

Ubuntu ext4误删文件恢复:extundelete实战指南

简介&#xff1a;本资源是一份面向Linux系统管理员与Ubuntu初学者的实用故障恢复指南&#xff0c;聚焦rm命令误删文件后的紧急抢救方案。文档详细解析ext3grep&#xff08;适配ext3&#xff09;与extundelete&#xff08;适配ext4&#xff09;两大核心恢复工具的安装、分区定位…

作者头像 李华
网站建设 2026/9/23 22:55:21

ENSP实战:校园局域网课程设计的VLAN划分与网络排错指南

简介&#xff1a;一份基于eNSP平台完成校园局域网组网设计的课程设计报告&#xff0c;面向网络工程、计算机等专业学生&#xff0c;适合用于课程设计、毕业设计或综合实训。报告内容覆盖组网概述、需求分析、网络设计三大主体模块。需求分析部分针对学校教学、科研、管理场景&a…

作者头像 李华