GetX之状态管理源码分析

699 阅读5分钟

Getx是一个第三方库,它为我们封装好了一些方法与功能,可以简化我们的代码编写功能。

状态管理

在使用Getx时,离不开使用他的状态管理,通常会从三中方式中选中使用

  • put/create
    • 通常在StatelessWidget中使用
  • binds
    • 与路由结合一起使用,页面继承GetView
  • Obx
    • 这个通常用于单个变量(也包括对象)值的改变,自动更新视图

put/create

先来说说使用

controller.jpg

1, create 使用方式

Get.create(() => GetStateLogic());

将一个继承了GetxController的对象传给Get.create方法,内部转换后最终执行 _insert 方法,_insert 后面说

/// 此为Get.create方法
void create<S>(InstanceBuilderCallback<S> builder,
        {String? tag, bool permanent = true}) =>
    GetInstance().create<S>(builder, tag: tag, permanent: permanent);
    
    
/// 此为GetInstance().create方法
void create<S>(
  InstanceBuilderCallback<S> builder, {
  String? tag,
  bool permanent = true,
}) {
  _insert(
    isSingleton: false,
    name: tag,
    builder: builder,
    permanent: permanent,
  );
}  

2, put 使用方式

  • Get.put
class GetStatePage extends StatelessWidget {
  //初始化controller
  final logic = Get.put(GetStateLogic());
  
  ///获取state
  final state = Get.find<GetStateLogic>().state;
  
}

将一个继承GetxController的类传入Get.put方法,Get.put会返回当前传入的对象因此可以用于初始化对象,经过内部转换后,最终执行 _insert 方法,_insert 后面说

/// 此为Get.put方法
S put<S>(S dependency,
        {String? tag,
        bool permanent = false,
        InstanceBuilderCallback<S>? builder}) =>
    GetInstance().put<S>(dependency, tag: tag, permanent: permanent);
    
/// 此为GetInstance().put方法
S put<S>(
  S dependency, {
  String? tag,
  bool permanent = false,
  @deprecated InstanceBuilderCallback<S>? builder,
}) {
  _insert(
      isSingleton: true,
      name: tag,
      permanent: permanent,
      builder: builder ?? (() => dependency));    
  return find<S>(tag: tag);
}
  • Get.lazyPut
Get.putAsync<GetStateLogic>(
  () => Future.value(GetStateLogic())
);

Get.put的懒加载模式,初始化时,并不是直接传入一个实体对象,而是传入一个有回调的方法,最终会走到 _insert方法,Get.putAsync不会返回任何值,只能用于插入到Get使用,而不能显示Get.put那样用于初始化对象使用

/// 此为Get.lazyPut方法
void lazyPut<S>(InstanceBuilderCallback<S> builder,
    {String? tag, bool fenix = false}) {
  GetInstance().lazyPut<S>(builder, tag: tag, fenix: fenix);
}

/// 此为GetInstance().lazyPut方法
void lazyPut<S>(
  InstanceBuilderCallback<S> builder, {
  String? tag,
  bool? fenix,
  bool permanent = false,
}) {
  _insert(
    isSingleton: true,
    name: tag,
    permanent: permanent,
    builder: builder,
    fenix: fenix ?? Get.smartManagement == SmartManagement.keepFactory,
  );
}
  • Get.putAsync
Get.putAsync<GetStateLogic>(
  () => Future.value(GetStateLogic()),
);

某些时候,需要经过耗时/异步处理,拿到数据后再初始化controller,Get.putAsync需要传入一个Future,同时该方法也会返回一个Future,经过内部转换,最终调用Get.put进行插入,核心点就在于,调用Get.put时,传入的是一个 await 修饰的方法

/// 此为 Get.putAsyn方法
Future<S> putAsync<S>(AsyncInstanceBuilderCallback<S> builder,
        {String? tag, bool permanent = false}) async =>
    GetInstance().putAsync<S>(builder, tag: tag, permanent: permanent);
    
/// 此为 GetInstance().putAsync 方法
Future<S> putAsync<S>(
  AsyncInstanceBuilderCallback<S> builder, {
  String? tag,
  bool permanent = false,
}) async {
  return put<S>(await builder(), tag: tag, permanent: permanent);
}

上面说的都是如何使用,并最终执行到 _insert方法

/// 全局存储
static final Map<String, _InstanceBuilderFactory> _singl = {};

/// 获取key
String _getKey(Type type, String? name) {
  return name == null ? type.toString() : type.toString() + name;
}

/// 插入到_singl
void _insert<S>({
  bool? isSingleton,
  String? name,
  bool permanent = false,
  required InstanceBuilderCallback<S> builder,
  bool fenix = false,
}) {
  final key = _getKey(S, name);

  if (_singl.containsKey(key)) {
    final dep = _singl[key];
    if (dep != null && dep.isDirty) {
      _singl[key] = _InstanceBuilderFactory<S>(
        isSingleton,
        builder,
        permanent,
        false,
        fenix,
        name,
        lateRemove: dep as _InstanceBuilderFactory<S>,
      );
    }
  } else {
    _singl[key] = _InstanceBuilderFactory<S>(
      isSingleton,
      builder,
      permanent,
      false,
      fenix,
      name,
    );
  }
}

_insert 就是把当前Controller,以当前类型与Tag组合成key,存储到一个全局的Map对象中去。

再结合GetX的路由,自动调用Get.delete方法移除Controller

  • 所有页面都需要注册到GetMaterialApp的getPages列表里去
  • getPages是一个List< GetPage >
  • GetPage继承Page,并且重写了createRoute方法
@override
Route<T> createRoute(BuildContext context) {
  // return GetPageRoute<T>(settings: this, page: page);
  final _page = PageRedirect(
    route: this,
    settings: this,
    unknownRoute: unknownRoute,
  ).getPageToRoute<T>(this, unknownRoute);

  return _page;
}
  • 通过createRoute会把Route转化成GetPageRoute
  • GetPageRoute混入了PageRouteReportMixin,实现了dispose方法
mixin PageRouteReportMixin<T> on Route<T> {
  @override
  void install() {
    super.install();
    RouterReportManager.reportCurrentRoute(this);
  }

  @override
  void dispose() {
    super.dispose();
    RouterReportManager.reportRouteDispose(this);
  }
}
  • 每当视图被移除时会执行dispose
RouterReportManager.reportRouteDispose 调用 _removeDependencyByRoute 调用 GetInstance().delete

binds/binds

单个/多个绑定

/// 单个绑定
GetPage(
  name: Paths.getBind,
  page: () => BindPage(),
  binding: BindBinding(),
)

/// 多个绑定
GetPage(
  name: Paths.getBind,
  page: () => BindPage(),
  bindings: [
    BindBinding(),
    OtherBinding(),
  ],
)

在GetPage如果需要使用绑定,需要传入一个或者多个继承Bindings的类对象,继承Bindings需要重写 dependencies方法

class BindBinding extends Bindings {
  @override
  void dependencies() {
    Get.lazyPut(() => BindLogic());
  }
}

通常会在dependencies中把对应页面的Controller插入到Get的Controller管理器中去,在需要使用的地方,通过Get.find()得到当前页的Controller使用

上面是如何使用,那么bind/binds是在什么时候被调用的呢

  • 通过上面的_insert方法分析我们了解到,所有页面都会转成GetPageRoute
  • GetPageRoute会继承PageRoute
  • 当打开页面时,会执行GetPageRoute重写PageRoute中的buidPage方法
  • buidPage->buildContent->_getChild
Widget _getChild() {
  if (_child != null) return _child!;
  final middlewareRunner = MiddlewareRunner(middlewares);

  final localbindings = [
    if (bindings != null) ...bindings!,
    if (binding != null) ...[binding!]
  ];
  final bindingsToBind = middlewareRunner.runOnBindingsStart(localbindings);
  if (bindingsToBind != null) {
    for (final binding in bindingsToBind) {
      binding.dependencies();
    }
  }

  final pageToBuild = middlewareRunner.runOnPageBuildStart(page)!;
  _child = middlewareRunner.runOnPageBuilt(pageToBuild());
  return _child!;
}
  • 不管是单个绑定还是多个绑定,最终会合成为一个列表,并遍历调用binding.dependencies()
  • binding.dependencies()就是BindBinding需要重写的方法

Obx

yuque_diagram.jpg

这个要分两个部分来说

  • 变量定义(拿RxInt来说):变量的值改变时通知视图更新
    • RxInt 继承Rx< T > 继承 _RxImpl 继承 RxNotifier 混入 RxObjectMixin
    • RxNotifier = RxInterface with NotifyManager,RxObjectMixin混入了NotifyManage
  • obx包裹视图:监听RxInt的数值变化
    • obx 继承 ObxWidget = StatefulWidget
    • 实例化RxNotifier对象,并把setstate方法传入进去,用于RxInt数值变化时回调

上面说了RxInt与obx是什么,干什么用的,那么他们是如何关联起来的呢 核心就在与RxInterface类中,现在我们按照执行顺序来分析

  • 绑定步骤
    • 第一步:在页面中,计算绘制视图时,执行obx
    • 第二步:在obx对应的StatefulWidget中实例化_observer(_observer是一个Stream),传入setState()与_observer进行绑定,每当_observer有事件流来时,就会执行setState()方法
    • 第三步:执行obx对应的StatefulWidget对应的Build方法,build方法执行RxInterface.notifyChangeChild,并传入_observer与widget.build
    • 第四步:通过RxInterface类中的静态变量(中转使用),存储当前_observer,并执行widget.build
    • 第五步:绘制widget.build时,会调用获取RxInt.value
    • 第六步:调用RxInt.value实际执行的是RxInt最终继承的RxObjectMixin类中的T get value 方法(在被调用RxObjectMixin的value方法前,RxInt初始化时,获自动创建subject(Stream),用于在RxInt发生改变时,发送流事件)
    • 第七步:把当前RxInt的subject传入进去,用于把RxInt值变化的Stream与页面刷新事件进行绑定
    • 第八步:绑定完成后,恢复RxInterface的proxy
  • 更新步骤
    • 每当RxInt值发送改变时,最终都会执行RxObjectMixin类中的 set value方法
    • 在set value 中,给subject 新增事件流
    • 通过之前的绑定关系,刷新使用到这个RxInt的obx视图