循序渐进 —— Flutter GetX 状态管理

Flutter 应用开发中状态管理是重要的一环,目前成熟的状态管理插件有 provider,provider,getx 等,本文基于 getx 的使用及基本原理展开讨论,文末有具体工程示例链接;

1. GetX 的核心架构

GetX 是一个轻量级的状态管理、依赖注入和路由管理框架,它的核心目标------>

减少样板代码,最大化开发效率

从更新原理及工程整合上与 provider 及 bloc 的差异

维度 Provider BLoC GetX
是否依赖 context ❌ 不需要
状态更新模型 rebuild event → state 响应式 / 命令式
样板代码 极少
生命周期 Widget 驱动 BLoC 显式控制 Route + SmartManagement
工程整合度

1.1 响应式编程模型

GetX 的响应式编程基于观察者模式(Observer Pattern)实现。当一个被观察的变量(使用.obs标记)发生变化时,所有依赖该变量的 UI 组件都会自动重建。

内部实现上,GetX 使用了一个名为 GetStream的流(Stream)来管理状态变化。当可观察对象的值发生变化时,会触发流中的事件,从而通知所有监听者。

dart 复制代码
// 简化的内部实现原理
class RxImpl<T> {
 final _subject = GetStream<T>();
 
 T get value => _subject.value;
 set value(T val) {
   _subject.add(val);  // 触发流事件
 }
}

1.2 依赖注入系统

GetX 的依赖注入系统基于服务定位器模式(Service Locator Pattern)。它维护了一个全局的依赖实例映射表,通过类型或标签来查找和注册依赖项。

dart 复制代码
// 简化的内部实现原理
class GetInstance {
  static final _singl = <String, dynamic>{};
  
  void put<S>(S dependency, {String? tag}) {
    _singl[_getKey(S, tag)] = dependency;
  }
  
  S find<S>({String? tag}) {
    return _singl[_getKey(S, tag)] as S;
  }
  
  String _getKey(Type type, String? tag) => tag == null ? type.toString() : type.toString() + tag;
}

1.3 路由管理系统

GetX 的路由管理是对 Flutter 原生导航 API 的封装,它维护了一个全局的路由历史栈,并提供了更简洁的 API 来管理页面导航。

dart 复制代码
// 简化的内部实现原理
class GetNavigation {
  final _history = <GetPage>[];
  
  Future<T?> to<T>(GetPage page) {
    _history.add(page);
    return Navigator.push(...);  // 调用Flutter原生导航
  }
  
  void back() {
    _history.removeLast();
    Navigator.pop(...);  // 调用Flutter原生导航
  }
}

2. GetX 的工作原理

2.1 状态管理原理

简单状态管理

简单状态管理使用 GetBuilderupdate()方法:

  1. 当调用 update()时,GetX 会通知所有使用GetBuilder构建的与该控制器关联的 UI 组件进行重建
  2. 内部使用唯一 ID 来标识每个控制器实例,确保只有相关的 UI 组件会重建
dart 复制代码
// 简化的内部实现原理
void update([List<String>? ids]) {
  if (ids == null) {
    // 通知所有监听该控制器的UI组件
    _notifyAllListeners();
  } else {
    // 只通知特定ID的UI组件
    _notifyListenersById(ids);
  }
}

响应式状态管理

响应式状态管理使用 .obsObx()

  1. .obs变量的值发生变化时,会触发内部的 GetStream

  2. Obx()组件会监听这些流的变化,并在值变化时自动重建 UI

  3. GetX 使用了一个智能的差异算法,只重建依赖变化值的组件,而不是整个页面

sequenceDiagram participant A as Obx声明阶段 participant B as 数据访问阶段 participant C as 数据更新阶段 participant D as UI重建阶段 A->>B: 创建_ObxState和RxNotifier B->>C: 通过代理绑定观察者 C->>D: 触发setState重建 Note right of D: 最小范围局部刷新
dart 复制代码
// 简化的内部实现原理
class Obx extends StatefulWidget {
  @override
  _ObxState createState() => _ObxState();
}

class _ObxState extends State<Obx> {
  late StreamSubscription _subscription;
  
  @override
  void initState() {
    super.initState();
    // 订阅可观察变量的变化
    _subscription = _getStream().listen((_) {
      if (mounted) setState(() {});  // 触发UI重建
    });
  }
  
  @override
  Widget build(BuildContext context) {
    // 构建UI时会访问可观察变量,从而建立依赖关系
    return widget.builder();
  }
}
sequenceDiagram participant WidgetTree as Widget树 participant ObxWidget as ObxWidget participant _ObxState as _ObxState participant RxNotifier as RxNotifier participant RxObject as Rx participant GetStream as GetStream WidgetTree->>ObxWidget: 插入Obx组件 ObxWidget->>_ObxState: createState() _ObxState->>RxNotifier: 创建_observer实例 _ObxState->>GetStream: 订阅监听(subs = _observer.listen) loop 构建阶段 _ObxState->>RxInterface: proxy = _observer RxObject-->>GetStream: 访问.value触发addListener RxObject->>RxNotifier: 注册监听关系 RxInterface-->>_ObxState: 恢复proxy原始值 end Note over RxObject,RxNotifier: 建立双向绑定关系 par 数据更新时 RxObject->>GetStream: 广播变化事件 GetStream->>RxNotifier: 通知_observer RxNotifier->>_ObxState: 调用_updateTree _ObxState->>WidgetTree: setState局部重建 end

2.2 依赖注入原理

GetX 的依赖注入系统支持多种注入方式:

  1. 常规注入Get.put):立即创建实例并注册

  2. 懒加载注入 Get.lazyPut):只在首次使用时创建实例

  3. 异步注入Get.putAsync):异步创建实例

  4. 一次性注入Get.create):每次获取都创建新实例

内部实现上,GetX 使用工厂模式(Factory Pattern)来管理依赖的创建方式:

dart 复制代码
// 简化的内部实现原理
void lazyPut<S>(InstanceBuilderCallback<S> builder) {
  _factories[_getKey(S)] = builder;  // 存储工厂函数
}

S find<S>() {
  final key = _getKey(S);
  if (_singl.containsKey(key)) {
    return _singl[key];  // 返回已存在的实例
  }
  
  if (_factories.containsKey(key)) {
    final instance = _factories[key]();  // 调用工厂函数创建实例
    _singl[key] = instance;  // 缓存实例
    return instance;
  }
  
  throw Exception('未找到依赖项');
}

2.3 路由管理原理

GetX 的路由管理系统在内部维护了一个页面堆栈,并提供了丰富的导航方法:

  1. 基本导航Get.to() Get.back()

  2. 命名路由Get.toNamed() Get.offNamed()

  3. 替换导航Get.off() Get.offAll()

每个路由操作都会更新内部的页面堆栈,并调用 Flutter 原生的导航 API:

dart 复制代码
// 简化的内部实现原理
Future<T?> toNamed<T>(String routeName, {dynamic arguments}) {
  final route = _findRouteByName(routeName);
  if (route != null) {
    _currentRoute = route;
    return Navigator.of(context).pushNamed(routeName, arguments: arguments);
  }
  throw Exception('未找到路由');
}

3. GetX 的优化机制

3.1 内存管理

GetX 提供了智能的内存管理机制:

  1. 自动释放:当控制器不再被使用时,默认会被自动释放

  2. 永久实例 :使用 permanent: true 参数可以创建永久实例

  3. 智能实例 :使用 fenix: true 参数可以在需要时重新创建已释放的实例

dart 复制代码
// 自动释放机制简化实现
void _autoRelease() {
  if (!_isPermanent && _getBuilderCount() == 0) {
    _singl.remove(_getKey(this.runtimeType));
  }
}

3.2 性能优化

GetX 在性能方面做了多项优化:

  1. 局部更新:只重建依赖变化数据的 UI 部分

  2. 懒加载:只在需要时创建依赖实例

  3. 按需构建 :使用 GetBuilder id 参数可以更精确地控制 UI 更新范围

dart 复制代码
// 局部更新机制简化实现
void update([List<String>? ids]) {
  if (ids == null) {
    _notifyAllListeners();
  } else {
    // 只通知特定ID的监听器
    for (final id in ids) {
      _notifyListenersById(id);
    }
  }
}
flowchart TD A[main.dart 启动] --> B[AppStoreInternal 单例初始化] B --> C[AppStore 构造函数执行] C --> D[_connectivity.onConnectivityChanged.listen] D --> E[等待网络状态变化] F[LottieJsonWidget initData] --> G[延迟1秒] G --> H[初始化环境配置] H --> I[LoginUtils.setLoginRegion] I --> J[LoginUtils.getIpCountryCode] J --> K[LoginPluginApi.requestIpRegionIfNecessary] E --> L[_updateConnectionStatus 被调用] L --> M[networkChange 执行] M --> N{网络状态判断} N -->|有网络| O[await initUserInfo] N -->|无网络| P[设置离线时间] O --> Q[RESTApis.getUserInfo] Q --> R[await getUserPrivilege] R --> S[notifyListeners] K --> T[异步获取IP地区] T --> U[AppStoreInternal.store.setIpCountryCode] U --> V[notifyListeners] S --> W[UI更新] V --> W X[App.onMounted] --> Y[AppStoreInternal.store.setMainContext] Y --> Z[检查网络状态] Z --> AA[AppUpgradeUtils.checkUpgrade] BB[AppPages useMounted] --> CC[init 函数] CC --> DD{网络和登录状态检查} DD -->|有网络且已登录| EE[ApplicationController.queryUserConfig] DD -->|无网络或未登录| FF[跳过配置加载] EE --> GG[PrintReportController.uploadIsarToService] style A fill:#e1f5fe style C fill:#fff3e0 style L fill:#f3e5f5 style O fill:#e8f5e8 style T fill:#fff8e1 style W fill:#fce4ec

工程示例

text 复制代码
lib/
├── app/
│   ├── controllers/       # 控制器(状态管理)
│   ├── modules/           # 各功能模块的UI页面
│   ├── routes/            # 路由管理
│   ├── translations/      # 国际化翻译
│   └── bindings/          # 依赖注入绑定
└── main.dart              # 应用入口

github.com/lizy-coding...

相关推荐
Rain的Java大神之路1 小时前
线上接口负载满了如何解决
java·数据库·redis·后端·缓存·面试·架构
Dr.kangder1 小时前
嵌入式面试总结(十八)——JTAG调试
嵌入式硬件·面试·职场和发展·架构·嵌入式
Dr.kangder4 小时前
嵌入式面试总结(十六)——AMBA总线
面试·职场和发展·架构·嵌入式·虚拟化·总线
Dr.kangder4 小时前
嵌入式面试总结(十九)——内存泄露
单片机·算法·面试·职场和发展·架构·硬件架构
Dr.kangder4 小时前
嵌入式面试总结(十七)——DMA总线
面试·职场和发展·架构·嵌入式·总线
stringwu5 小时前
Flutter Generative UI 深度架构剖析与官方 SDK 实战
flutter
码匠许师傅5 小时前
【C++ 面试真题】聊聊 C++ 的虚函数和多态
java·c++·面试
2601_953988076 小时前
Ricon组态系统vs传统组态软件:为什么选择新一代Web组态平台
前端·后端·物联网·tcp/ip·数学建模·前端框架
艾莉丝努力练剑7 小时前
【QT:解决问题】Qt5Core.dll:无法定位程序输入点
java·开发语言·qt·学习·面试