get 部分源码分析

588 阅读5分钟

以下内容针对get 5.0版本,可能和历史版本略有不同

getx 状态管理 架构图

GetInstance.png

GetInstance 是一个单利, 维护一个控制器字典map< key:controller >用来保存、创建、查找、删除控制器

GetxController.png

GetxController 主要是通过 ListNotifierSingleMixin来维护一个函数列表(_updaters),一般存的是widget的setState或者markNeedsBuild, 用来控制 widget重建.

GetInstance3.jpg

view 通过自定义widget 即重写createEmelent, 并给自定义视图添加getUpate(), getUpdate()最后会调用markNeedsBuild以此来重建widget

另外get提供了几种自定义StatelessWidget:

  • GetView 只是简单添加一个查找控制器的方法, 也最为常用

  • Bind, 在自定义Emelent的生命周期中完成控制器的创建、插入及销毁

  • Binds, 控制器与widget建立绑定关系,与Bind类似,使用可参考GetMaterialApp

     class GetMaterialApp extends StatelessWidget {
       @override
       Widget build(BuildContext context) => Binds(binds: [ Bind.lazyPut<GetMaterialController>(
       () => GetMaterialController],child: widget);
     }
    
  • obx, 通过Notifer向控制器中插入刷新的回调函数(markNeedsBuild), 主要用来局部刷新。

  • GetBuilderstatefullwidget生命周期做一些hooks,也极为常用

  • GetWidget, 解决多个重复页面对应一个控制器的问题, 比如导航中同时存在多个详情页,每个控制器都要有单独的生命周期

StateMixin

StateMixin 就是对ListNotifier的一个简单应用, 开发中会经常用到。

  1. value 实现它的get/set方法, 由Notifer添加监听者(通常为view), set方法执行刷新
  2. _statusvalue 类似
  3. futurize 处理异步任务, 一般为网络请求
mixin StateMixin<T> on ListNotifier {

  GetStatus<T>? _status;
  GetStatus<T> get status {
    reportRead();
    return _status ??= _status = GetStatus.loading();
  }
  set status(GetStatus<T> newStatus) {
    if (newStatus == status) return;
    _status = newStatus;
    if (newStatus is SuccessStatus<T>) {
      _value = newStatus.data!;
    }
    refresh();
  }
  
  T get state => value;
  T? _value;
  @protected
  T get value {
    reportRead();
    return _value as T;
  }
  @protected
  set value(T newValue) {
    if (_value == newValue) return;
    _value = newValue;
    refresh();
  }

  void futurize(Future<T> Function()  body,
      {T? initialData, String? errorMessage, bool useEmpty = true}) {
    final compute = body;
    _value ??= initialData;
    compute().then((newValue) {
      if ((newValue == null || newValue._isEmpty()) && useEmpty) {
        status = GetStatus<T>.loading();
      } else {
        status = GetStatus<T>.success(newValue);
      }

      refresh();
    }, onError: (err) {
      status = GetStatus.error(errorMessage ?? err.toString());
      refresh();
    });
  }
}

RouterOutlet是怎么刷新的?

GetDelegate 实现了 ListNotifierSingleMixin 接口, 因此在push / back 等出栈入栈 调用refresh() 可以触发build重建, 不过此时 GetDelegate显然还不满足refresh的条件, 即没有向链表中添加markNeedsBuild函数 image.png

向链表中添加markNeedsBuild的两种方式 WeChat72ca96f095d4aa01437b2f12f05f1bcb.png

RouterOutletStatedidChangeDependencies的时候为 routerDelegate 添加了一个setState(() {})函数, 从而使routerDelegate 在出入栈时触发重建

class RouterOutlet<TDelegate extends RouterDelegate<T>, T extends Object>
    extends StatefulWidget {
  final TDelegate routerDelegate;
  
  // 与例子无关, 忽略实现
  @override
  _RouterOutletState<TDelegate, T> createState() =>
      _RouterOutletState<TDelegate, T>();
}

class _RouterOutletState<TDelegate extends RouterDelegate<T>, T extends Object>
    extends State<RouterOutlet<TDelegate, T>> {
  RouterDelegate? delegate;
  // 与例子无关, 忽略实现
  void _listener() {
    setState(() {});
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
   // 与例子无关, 忽略实现
    delegate?.addListener(_listener);
  }

}

rx obs 原理解析

11111111111212123123.jpg

  1. MiniStream<T> 为 get 封装的一个简易版的 stream , 功能和 stream 类似. 性能上比flutter的stream 差许多, 因此 MiniStream 只是用来做参考.

    • MiniStream<T>主要作用是对T value 添加监听者, 并在合适时机触发MiniSubscription<T>对应的方法

    • MiniSubscription为监听者, on data 值发生变化 , on error 发生错误, on done 完成.

    • T value,实际是重写 get,set方法, set 方法触发MiniSubscription<T>.onData,value 值发生变化

    • listen, 实际上是给value 添加监听者, 监听者对应FastList<T> 链表的节点, 一个监听者对应一个Node节点

    • add触发on data

    • addError 触发 on error

    • close 触发 on done

  2. rx 的核心类为GetListenable,T value监听部分由StreamController完成.

    • ListNotifierSingle updater 更新者, 一般用来页面重建
    • T value,同样是重写 get,set方法, 不过此时由于监听部分已经由StreamController完成, 因此get,set方法调用了ListNotifierSingle 部分方法
    • Rx<T> 作用和 MiniStream<T> 基本类似
  3. value.obs 明显就是 value 转成 MiniStream/Rx<T>, 从而完成对value的监听操作

    extension RxT<T> on T {
      Rx<T> get obs => Rx<T>(this);
    }
    
  4. rx obs 和 obx 通信

    • obx 带有getUpdate方法的StatelessWidget,并通过Notifier.append把自身的getUpate()刷新方法封装到NotifierData中(待插入).

    • obsRx<T> 对象

    • Rx<T>get 方法中调用了 Notifier.read, 此时将NotifierData对象中getUpate()的添加到contorller 列表中

    • Rx<T>set 方法中调用了 controllerrefresh() 触发了widget重建,

    var name = 'Jonatas Borges'.obs;
    
    Obx(() => Text("${controller.name}"));
    

obx简单应用

  1. 创建GetxController
  2. obs 观察值
  3. view 中使用 GetxController, 通过GetInstance.put 添加 controller, GetInstance.find 查找controller
  4. obx提供getUpate()
  5. obsget 方法, 添加getUpate()controller.list<func>列表中
  6. obsset 方法触发 controllerrefresh()
class Controller extends GetxController{
  var count = 0.obs;
  increment() => count.value++;
}
class Home extends StatelessWidget {

  @override
  Widget build(context) {

    // 使用Get.put()实例化你的类,使其对当下的所有子路由可用。
    final Controller c = Get.put(Controller());

    return Scaffold(
      // 使用Obx(()=>每当改变计数时,就更新Text()。
      appBar: AppBar(title: Obx(() => Text("Clicks: ${c.count.value}"))),

      // 用一个简单的Get.to()即可代替Navigator.push那8行,无需上下文!
      body: Center(child: ElevatedButton(
              child: Text("Go to Other"), onPressed: () => Get.to(Other()))),
      floatingActionButton:
          FloatingActionButton(child: Icon(Icons.add), onPressed: c.increment));
  }
}

Workers

  1. ever 实际上为 GetListenable.listen 添加监听
ever(count1, (_) => print("$_ has been changed"));
  1. everAll 批量添加监听
  2. once, 先GetListenable.listen 添加监听, 收到onData监听后,移除观察者, 因此只执行一次
  3. interval, 先GetListenable.listen 添加监听, 收到onData监听后, delayed 来控制执行
  4. debounce, 先GetListenable.listen 添加监听, 收到onData监听后, Timer来控制执行

路由

路由1.jpg

Get.to(NextScreen());  相当于 GetDelegate.to(NextScreen())
Get.back();  相当于 GetDelegate.back()

  1. GetDelegate 相当于对 Navigator2.0 做了一些封装, 另外通过维护一个数组 List<RouteDecoder> 来实现页面的切换

  2. ParseRouteTree 路由表, 包含应用所有路由信息

  3. RouteDecoder 路由的配置信息, path + arguments + params

  4. GetPage 基于路由配置信息生成的页面

问题

问题1 WeChat8f9c302fe89872babf826fa21e063dbe.png

data.disposers.isEmpty 它为空的可能性只有一个, Notifier.read, 没有被调用 Notifier.read 有两个作用:

  1. 添加dispose
  2. list<func>添加函数

Notifier.readStateMixin<T> 即通过 T 的get调用

mixin StateMixin<T> on ListNotifier {

 @protected
  T get value {
    reportRead();
    return _value as T;
  }
}

因此 T.obs 之后, 必须要使用, 使用即会调用 T.get

问题2: getx路由中多个重复页面对应一个控制器的问题

解法一: 推荐 首先Bindings 使用create 即每次跳转都创建新的控制器 image.png

widget 要继承 GetWidget, 路由跳转时,preventDuplicates(去重)改为false image.png

解法二: 适用部分情况

使用tag做标记, tag 一般为详情页id image.png

此处和lazyput对应, 通过tag获取控制器 image.png