目录
1.前言
2.GetX 依赖管理概述
1.GetX 依赖管理的基本概念
2.与其他依赖管理工具的比较
3. 基础依赖注入
1.Get.put
2.Get.lazyPut
3.Get.putAsync
4.高级依赖注入
1.使用Get.create
2.依赖生命周期管理
5. 参考资料
1.前言
今天这篇博客主要介绍Getx的三大功能之一的依赖管理。依赖管理是软件开发中的一个关键部分,尤其是在复杂应用中。它帮助开发者管理应用中的各种依赖,确保依赖的实例化和生命周期管理变得更加简单和高效。
2.GetX 依赖管理概述
1.GetX 依赖管理的基本概念
GetX 提供了一种简单且高效的依赖注入方式,通过少量代码即可实现依赖的注入、管理和访问。它主要通过Get.put、Get.lazyPut、Get.putAsync和Get.create等方法来实现依赖管理。
2.与其他依赖管理工具的比较
Provider:Provider 是 Flutter 官方推荐的依赖注入和状态管理工具。它需要较多的样板代码,使用起来相对复杂。
Riverpod:Riverpod 是 Provider 的增强版,提供了更多的功能和更好的性能,但学习曲线较陡。
GetX:GetX 简单易用,提供了更多的功能,如路由管理和状态管理,适合各种规模的项目。
3. 基础依赖注入
1.Get.put
Get.put 是 GetX 提供的一个方法,用于将一个实例注入到依赖管理系统中。它的主要作用是创建一个新的实例,并将其注册为依赖,供整个应用程序的其他部分使用。使用 Get.put 可以方便地管理和访问依赖实例,避免手动管理实例的生命周期。
它的使用场景如下:
1.全局单例:需要在整个应用程序中共享的依赖。
2.初始化即创建:需要在应用启动时立即创建的依赖。
我们使用Get.put的时候,还可以配置一些选项:
- dependency:要注入的依赖实例
- tag:依赖实例的可选标签,用于区分相同类型的不同实例。
- permanent:是否将依赖设置为永久存在,即使不再使用也不会被销毁。
我们可以通过一个例子说明Get.put的用法。
在这个例子中,我们在首页操作计时器,然后在收藏页面调用HomeController中的计时器的点击次数。
图1.Get.put用法
我们来看看如何实现这个功能。
1.首先我们创建一个控制器,它包含计数逻辑。
class HomeController extends GetxController {var count = 0.obs;void increment() {count++;}
}
2.在应用启动时注入依赖
在 main 方法中使用 Get.put 将 CounterController 注入到依赖管理系统中。
void main() {// 注入依赖Get.put(HomeController());runApp(const MyApp());
}
3.在页面中使用依赖
完整代码:
import 'package:flutter/material.dart';
import 'package:get/get.dart';class DependencyManagerMain extends StatefulWidget {const DependencyManagerMain({super.key});@overrideState<DependencyManagerMain> createState() => _DependencyManagerMainState();
}class _DependencyManagerMainState extends State<DependencyManagerMain>with SingleTickerProviderStateMixin {late TabController _tabController;@overridevoid initState() {super.initState();// 初始化 TabController_tabController = TabController(length: 3, vsync: this);}@overridevoid dispose() {// 销毁 TabController_tabController.dispose();super.dispose();}@overrideWidget build(BuildContext context) {return Scaffold(body: TabBarView(controller: _tabController,children: [HomePage(),FavoritesTab(),const SettingsTab(),],),bottomNavigationBar: TabBar(controller: _tabController,tabs: const [Tab(icon: Icon(Icons.home), text: "首页"),Tab(icon: Icon(Icons.star), text: "收藏"),Tab(icon: Icon(Icons.settings), text: "设置"),],),);}
}class HomeController extends GetxController {var count = 0.obs;void increment() {count++;}
}
class HomePage extends StatelessWidget {HomePage({super.key});final HomeController controller = Get.find<HomeController>();@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text('首页'),),body: Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,children: [// 使用 Obx 来监听 count 的变化Obx(() => Text('Count: ${controller.count}',style: const TextStyle(fontSize: 20),)),const SizedBox(height: 20),ElevatedButton(onPressed: controller.increment,child: const Text('Increment'),),],),),);}
}class FavoritesTab extends StatelessWidget {FavoritesTab({super.key});final HomeController controller = Get.find<HomeController>();@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text('收藏'),),body: Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,children: [// 使用 Obx 来监听 count 的变化Obx(() => Text('点击次数: ${controller.count}',style: const TextStyle(fontSize: 20),)),const SizedBox(height: 20),],),),);}
}class SettingsTab extends StatelessWidget {const SettingsTab({super.key});@overrideWidget build(BuildContext context) {return const Center(child: Text('Settings Tab Content'),);}
}
2.Get.lazyPut
可以懒加载一个依赖,这样它只有在使用时才会被实例化。这对于计算代价高的类来说非常有用,或者如果你想在一个地方实例化几个类(比如在Bindings类中),而且你知道你不会在那个时候使用这个类。
Get.lazyPut方法用于延迟创建依赖实例,只有在第一次使用时才会创建。
这里就不写demo了,感兴趣的话,可以参考上一个例子中的demo写Demo测试一下。
///只有当第一次使用Get.find<ApiMock>时,ApiMock才会被调用。
Get.lazyPut<ApiMock>(() => ApiMock());Get.lazyPut<FirebaseAuth>(
() {
// ... some logic if needed
return FirebaseAuth();
},
tag: Math.random().toString(),
fenix: true
)Get.lazyPut<Controller>( () => Controller() )
3.Get.putAsync
如果你想注册一个异步实例,你可以使用Get.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<S>(
// 必备:一个将被执行的异步方法,用于实例化你的类。
AsyncInstanceBuilderCallback<S> builder,// 可选:和Get.put()一样,当你想让同一个类有多个不同的实例时,就会用到它。
// 必须是唯一的
String tag,// 可选:与Get.put()相同,当你需要在整个应用程序中保持该实例的生命时使用。
// 默认值为false
bool permanent = false
)
4.GetView、GetxView 和GetxController
4.高级依赖注入
1.使用Get.create
Get.create方法用于每次请求时创建一个新的依赖实例,适用于需要多次创建的依赖。
void main() {Get.create<MyController>(() => MyController());runApp(MyApp());
}
2.依赖生命周期管理
GetX 提供了三种依赖生命周期管理模式:
Permanent:永久存在,直到应用关闭。
Singleton:默认模式,实例在第一次创建后存在于整个应用生命周期内。
Transient:每次请求时创建新的实例。
模块化依赖注入:
在大型项目中,可以将依赖注入模块化,便于管理和维护。
class AppBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut<MyController>(() => MyController());
}
}void main() {
runApp(GetMaterialApp(
initialBinding: AppBinding(),
home: MyApp(),
));
}
5.依赖管理中的常见问题及解决方案
1.依赖未能正确注入或找到
例如在下面的例子中,我在首页调用Get.find的方法,但是想相应的实例方法却没有被创建。
CounterController counterController = Get.find();
图2.依赖未能正确注入或找到
控制台日志输出如下:
图3.控制台输入日志
解决的方法也很简单,就是在调用之前确保相应的GetxController被实例化。
例如我们可以在app启动之前,实例化相应的控制器或者初始化的时候就绑定相应的GetxController。
图3.全局绑定GetxController
2.实例化过程中的性能问题
在使用 GetX 时,如果不小心,可能会引入性能问题。例如,不恰当地更新整个 UI 而不是局部更新,或者频繁地创建和销毁控制器。下面是一个示例,展示了如何使用 GetX 并说明可能的性能问题,以及如何优化这些问题。
在下面的代码中,PerformancelssueExample类展示了可能的性能问题。每次计数器值变化时,整个 Column 会被重建,导致性能下降。
import 'package:flutter/material.dart';
import 'package:get/get.dart';void main() {runApp(MyApp());
}class MyApp extends StatelessWidget {@overrideWidget build(BuildContext context) {return GetMaterialApp(home: HomePage(),);}
}class HomeController extends GetxController {var counter = 0.obs;void increment() => counter.value++;
}class HomePage extends StatelessWidget {@overrideWidget build(BuildContext context) {final HomeController controller = Get.put(HomeController());return Scaffold(appBar: AppBar(title: Text('GetX Performance Example'),),body: Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,children: <Widget>[Text('You have pushed the button this many times:'),GetX<HomeController>(builder: (controller) {return Text('${controller.counter.value}',style: Theme.of(context).textTheme.headline4,);},),PerformanceIssueExample(),],),),floatingActionButton: FloatingActionButton(onPressed: controller.increment,tooltip: 'Increment',child: Icon(Icons.add),),);}
}class PerformanceIssueExample extends StatelessWidget {@overrideWidget build(BuildContext context) {final HomeController controller = Get.find();return Column(children: [Obx(() {print("Rebuilding entire Column");return Column(children: [for (int i = 0; i < 100; i++)Text('Item $i'),Text('Counter: ${controller.counter.value}'),],);}),ElevatedButton(onPressed: controller.increment,child: Text('Increment Counter'),),],);}
}
优化建议:
- 避免在可观察值变化时重建整个组件树。
-
将变化范围限制在最小的部件中,以减少重建次数
-
使用 GetX 或 Obx 只在必要时更新 UI
3.依赖生命周期管理不当导致的内存泄漏
在使用 GetX 时,内存管理是一个重要的考虑因素。如果不正确地管理控制器和依赖项,可能会导致内存泄漏。下面是一个示例,说明 GetX 内存管理中的常见问题及其解决方案。
1.示例代码
假设我们有一个简单的计数器应用,每次进入一个新页面都会创建一个新的控制器实例,但未正确释放旧的控制器实例,这会导致内存泄漏。
以下面代码为例:
import 'package:flutter/material.dart';
import 'package:get/get.dart';void main() {runApp(MyApp());
}class CounterController extends GetxController {var count = 0.obs;void increment() => count++;
}class MyApp extends StatelessWidget {@overrideWidget build(BuildContext context) {return GetMaterialApp(home: HomePage(),);}
}class HomePage extends StatelessWidget {@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text('Home Page'),),body: Center(child: ElevatedButton(onPressed: () {Get.to(CounterPage());},child: Text('Go to Counter Page'),),),);}
}class CounterPage extends StatelessWidget {@overrideWidget build(BuildContext context) {// 不推荐的做法:每次进入页面时创建新的控制器实例final CounterController controller = Get.put(CounterController());return Scaffold(appBar: AppBar(title: Text('Counter Page'),),body: Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,children: <Widget>[GetX<CounterController>(builder: (_) {return Text('Count: ${controller.count.value}');},),ElevatedButton(onPressed: controller.increment,child: Text('Increment'),),],),),);}
}
在这个示例中,每次导航到 CounterPage 时,都会创建一个新的 CounterController 实例,但未正确释放旧的实例,这会导致内存泄漏。
2.解决方案
为了避免内存泄漏,可以使用 Get.put 或 Get.lazyPut 在合适的地方创建和释放控制器实例,并使用 Get.delete 方法在不需要时释放控制器实例。
修改后的代码如下:
import 'package:flutter/material.dart';
import 'package:get/get.dart';void main() {runApp(MyApp());
}class CounterController extends GetxController {var count = 0.obs;void increment() => count++;
}class MyApp extends StatelessWidget {@overrideWidget build(BuildContext context) {return GetMaterialApp(home: HomePage(),);}
}class HomePage extends StatelessWidget {@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text('Home Page'),),body: Center(child: ElevatedButton(onPressed: () {Get.to(CounterPage());},child: Text('Go to Counter Page'),),),);}
}class CounterPage extends StatelessWidget {@overrideWidget build(BuildContext context) {// 推荐的做法:使用 Get.put 只创建一个控制器实例,直到不再需要final CounterController controller = Get.put(CounterController());return Scaffold(appBar: AppBar(title: Text('Counter Page'),),body: Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,children: <Widget>[GetX<CounterController>(builder: (_) {return Text('Count: ${controller.count.value}');},),ElevatedButton(onPressed: controller.increment,child: Text('Increment'),),],),),);}@overridevoid dispose() {// 当页面销毁时,释放控制器实例Get.delete<CounterController>();super.dispose();}
}
修改之后的实例中:
1.正确使用Get.put:在 CounterPage 页面创建时,只创建一个 CounterController 实例。
2.使用Get.put和Get.lazyPut:根据需要创建和管理控制器实例。
3.释放控制器实例:在页面销毁时,通过 Get.delete<CounterController>() 方法释放控制器实例,防止内存泄漏。
6. 参考资料
1.Getx官方文档