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

15 阅读5分钟

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

1. GetX 的核心架构

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

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

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

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

1.1 响应式编程模型

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

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

// 简化的内部实现原理
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)。它维护了一个全局的依赖实例映射表,通过类型或标签来查找和注册依赖项。

// 简化的内部实现原理
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 来管理页面导航。

// 简化的内部实现原理
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 状态管理原理

简单状态管理

简单状态管理使用 GetBuilder和 update()方法:

  1. 当调用 update()时,GetX 会通知所有使用GetBuilder构建的与该控制器关联的 UI 组件进行重建
  2. 内部使用唯一 ID 来标识每个控制器实例,确保只有相关的 UI 组件会重建
// 简化的内部实现原理
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: 最小范围局部刷新
// 简化的内部实现原理
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)来管理依赖的创建方式:

// 简化的内部实现原理
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:

// 简化的内部实现原理
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 参数可以在需要时重新创建已释放的实例

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

3.2 性能优化

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

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

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

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

// 局部更新机制简化实现
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

工程示例

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

github.com/lizy-coding…