- 前端
【免费下载链接】getx
Open screens/snackbars/dialogs/bottomSheets without context, manage states and inject dependencies easily with Get.
本篇技术指南围绕 Get(GetX)的依赖管理系统展开,详细讲解Get.put、Get.lazyPut、Get.putAsync、Get.create四种实例化方法的参数与适用场景,以及Get.find、Get.delete、replace/lazyReplace的使用方式,并结合源码剖析 Bindings、BindingsBuilder 与 SmartManagement 的底层实现。读完本文,你将能够在不依赖 Provider context 与 InheritedWidget 的情况下完成依赖注入,理解permanent与fenix的本质区别,并为路由配置自动回收、按需重建的控制器生命周期。本文对应的原始文档位于 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); }它通过内部方法_insert以isSingleton: true、permanent: permanent的方式把实例登记进_singl,随后立即调用find<S>,也就是说put是"注册即初始化",实例在调用返回时已经可用。_insert中还有一个细节:如果该 key 已存在且不是isDirty状态,会直接返回,避免重复注册覆盖。仓库测试 get_instance_test.dart 验证了Get.put后Get.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 )源码层面,lazyPut与put一样调用_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: false、permanent: 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 == ct2为false。
三、使用已注册的实例: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.put或Get.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
已注册的实例可以被替换为同类或扩展类的实例,之后仍可通过原类型获取。这就是replace与lazyReplace的用途:
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)删除原实例,再以相同permanent值put新实例;lazyReplace:先删除,再以lazyPut注册新工厂,且fenix未显式传入时默认取原实例的permanent值。
这一能力与Get.put的overrideAbstract参数搭配,常用于测试场景:先用抽象类注册,再替换为具体实现类。仓库测试 get_instance_test.dart 展示了抽象类Service分别通过lazyPut与spawn(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.put | permanent: false, isSingleton: true | 是(调用后立即可用) | 单例(直接持有dependency) | 视图 Controller |
Get.putAsync | permanent: false, isSingleton: true | 是(异步完成后立即 find) | 单例 | 需要 await 初始化的实例 |
Get.create | permanent: true, isSingleton: false | 否(等界面用到时才调用) | 每次 find 新建(builderFunc每次执行) | 列表项各自的独立 Controller,需配合 GetWidget |
Get.lazyPut | 不调用 insert,注册到"工厂"区 | 否(首次 find 时创建) | 首次 find 后成为单例 | 计算开销大、集中注册但不立即使用的类 |
逐条展开(文档原文要点):
- Get.put 与 Get.putAsync遵循相同的创建顺序,区别仅是后者使用异步方法:两者都会创建并初始化实例,直接通过内部方法
insert以permanent: false、isSingleton: true插入内存(isSingleton参数唯一的作用是决定取用dependency还是FcBuilderFunc作为依赖来源)。随后调用Get.find()立即初始化内存中的实例。 - Get.create:顾名思义会"创建"依赖。与
put类似也调用内部insert,但permanent变为true、isSingleton变为false(因为是"创建",不可能是单例)。由于permanent: true,默认享有"页面切换不丢失"的好处;同时Get.find()不会被立即调用,而是等待界面真正使用它时才触发。值得强调的是,Get.create的设计目标就是创建不共享、但不会被释放的实例。 - Get.lazyPut:惰性过程。实例会被创建,但不会立即被调用,而是等待使用。与其他方法相反,这里不会调用
insert,而是把实例插入内存的另一区域——负责判断"实例是否可以重建"的区域,文档称之为"factory(工厂)"。这样"未来要用的东西"不会与"正在使用的东西"混在一起。fenix的魔法就在这里:若fenix: false且smartManagement不是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(), );仓库中initialBinding是GetMaterialApp与GetCupertinoApp的标准配置项(见 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(), ) ) }smartManagement是GetMaterialApp的内置配置参数,默认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)
文档在结尾给出了两条关键提醒,结合仓库实现可归纳为以下最佳实践:
不要在使用多个 Bindings 时使用
SmartManagement.keepFactory。它被设计用于"不使用 Bindings"或"仅在 GetMaterialApp 的 initialBinding 中关联单个 Binding"的场景。Bindings 完全是可选的。你也可以直接在类中使用
Get.put()与Get.find()而毫无问题。但如果你使用 Services 或任何其他抽象,建议使用 Bindings 以获得更好的组织性。GetxService 不可被普通
delete删除。源码 lifecycle.dart 说明:与 GetxController 不同,GetxService 一旦启动就常驻内存(例如 Auth 服务),唯一的移除方式是Get.reset()。delete方法对实现GetxServiceMixin的实例也会直接返回(除非force: true),测试 get_instance_test.dart 验证了这一行为:普通delete后isRegistered仍为true,只有delete(force: true)才会真正移除。permanent 实例的删除需要 force。
delete方法对permanent: true的实例会拒绝删除并输出错误日志,除非显式传入force: true(见 extension_instance.dart)。在测试中善用
Get.reset()。它会清空所有注册实例(包括永久实例)与路由绑定(clearRouteBindings),保证测试之间互不污染(见 extension_instance.dart)。生命周期钩子:如果你的 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.
相关推荐
GetX 依赖管理完全指南:Get.put、Get.lazyPut、Bindings 与 SmartManagement 的实战与源码解析
GetX 依赖管理完全指南:Get.put、Get.lazyPut、Bindings 与 SmartManagement 的实战与源码解析 GetX(当前仓库
前端GetX 依赖管理完全指南:从 Get.put 到 Bindings 的注入、生命周期与内存管理
GetX 依赖管理完全指南:从 Get.put 到 Bindings 的注入、生命周期与内存管理 GetX(当前仓库 gh_mirrors/ge/getx )内
前端GetX 依赖管理(Dependency Management)完整指南:Get.put、Bindings 与 SmartManagement 源码级解析
GetX 依赖管理(Dependency Management)完整指南:Get.put、Bindings 与 SmartManagement 源码级解析 Ge
前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考