Flutter Getx核心功能浅析

4,013 阅读3分钟

原文转自:www.jianshu.com/p/567dbad52…

Getx简介

官网有全套的中文文档。

详情请看:github.com/jonataslaw/…

源码

版本:get: 3.24.0 Getx 提供了非常丰富的功能, 功能模块:

├── lib
│   ├── get_connect  [网络模块]
│   │   ├── http 
│   │   └── sockets
│   ├── get_core   [核心模块]
│   ├── get_instance   [依赖管理模块]
│   ├── get_navigation   [路由管理模块]
│   ├── get_rx   [响应式模块(类似RxDart)]
│   ├── get_state_manager   [状态管理模块]
│   └── get_utils   [通用工具]
│           ├── extensions  [一些基础功能扩展]
│           ├── get_utils   [提供常用功能集(各种判断isNull、isBlank、isNum等等等)]
│           ├── platform   [提供平台判断(isAndroid、isIOS)]
│           └── queue  [队列]

Setup

  1. 在pubspec.yaml文件中添加依赖 get: ^3.24.0
  2. MaterialApp改为GetMaterialApp 请看以下完整代码,实现了HelloWorld的功能:
import 'package:flutter/material.dart';
import 'package:get/get.dart';

void main() => runApp(GetMaterialApp(home: Home()));

/// 计数器控制器
class CounterController extends GetxController {
  var count = 0.obs;
}

class Home extends StatelessWidget {
  // 初始化计数器控制器,放入依赖管理中,并获取这个实例
  final _counter = Get.put(CounterController());

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Count Getx')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('You have pushed the button this many times:'),
            Obx(() => Text(// 响应数据变化
                  '${_counter.count.value}',
                  style: Theme.of(context).textTheme.headline4,
                )),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        // 触发数据变化
        onPressed: () => _counter.count + 1,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}
  1. 代码分析
  • CounterController类
    1. 继承自GetxController
    2. 声明了 一个响应式的变量count,类型为RxInt,get_rx模块中定义。
  • Home类
    1. 变量_counter 初始化计数器控制器,放入依赖管理中,并获取这个实例
    2. 对应的响应数据变化使用了 Rx类型对应的Widget:Obx。 当CounterController.count 值方发生变化时,触发Obx的更新。当在Obx中调用_counter.count.value时,RxInterface.proxy.addListener()为这个值添加了监听事件

Getx 路由管理、状态管理、依赖管理是怎么一起工作的

1. 状态管理数据是怎么存储的?

final _counter = Get.put(CounterController());

调用流程详见下图:

stack1.png

static final Map<String, _InstanceBuilderFactory> _singl = {};
......
  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);
  }

调用栈中,可以看到CounterController最终数据存到了GetInstance全局单例中。 与provider状态管理相比,provider数据是存放到了InheritedWidget中,InheritedWidget特性是只有子节点才能获取到存放的数据, 在和界面无关时,无法获取到provider中的数据。相比之下,Getx把数据存到了单例对象中,不依赖于UI, 这种方式更加灵活,试用范围更广。

2. 状态管理怎么做到自动管理的?

详见下图:

  1. 添加GetxController和路由的绑定关系 stack2.png
  // 保存路由和GetxController的对应关系
  static final Map<String, String> _routesKey = {};
  ......
  // 将CounterController的类名与当前路由对应起来, 存入_routesKey中。
  void _registerRouteInstance<S>({String tag}) {
    _routesKey.putIfAbsent(_getKey(S, tag), () => Get.reference);
  }
  1. 当页面退出时,GetPageRoute.dispose 移除_routesKey中对应的Key与Value,并删除在GetInstance._singl中保存的GetxController类 截屏2021-01-08 下午2.22.00.png

注意:Get.put调用的时机,像开头例子中Home就不能正确绑定路径。 因为还没有进入Home页面时,Get.put就已经触发了,这个时候Get.reference获取的路径是Home的上一个路由路径。

3. Obx() 是怎么响应Obs变量变化的?

Obx(() => Text(// 响应数据变化
                  '${_counter.count.value}',
                  style: Theme.of(context).textTheme.headline4,
                )),

只能看到_counter.count.value获取了值,应该也是这个地方绑定了数据响应关系。看源码具体实现:

  T get value {
    if (RxInterface.proxy != null) {
      RxInterface.proxy.addListener(subject); // 这里建立了数据关系,添加了数据变化监听
    }
    return _value;
  }

subject为GetStream类型。数据变化监听弄明白了,再看看Obx是怎么监听数据变化的,看Obx源码:

class Obx extends ObxWidget {
  final WidgetCallback builder;

  const Obx(this.builder);

  @override
  Widget build() => builder();
}

Obx继承自ObxWidget。

abstract class ObxWidget extends StatefulWidget {
  const ObxWidget({Key key}) : super(key: key);

  @override
  _ObxState createState() => _ObxState();

  @protected
  Widget build();
}

class _ObxState extends State<ObxWidget> {
  RxInterface _observer;
  StreamSubscription subs;

  _ObxState() {
    _observer = RxNotifier();
  }

  @override
  void initState() {
    subs = _observer.listen(_updateTree);
    super.initState();
  }

  Widget get notifyChilds {
    final observer = RxInterface.proxy;
    RxInterface.proxy = _observer;
    final result = widget.build();
    if (!_observer.canUpdate) {
      throw """
      [Get] the improper use of a GetX has been detected. 
      You should only use GetX or Obx for the specific widget that will be updated.
      If you are seeing this error, you probably did not insert any observable variables into GetX/Obx 
      or insert them outside the scope that GetX considers suitable for an update 
      (example: GetX => HeavyWidget => variableObservable).
      If you need to update a parent widget and a child widget, wrap each one in an Obx/GetX.
      """;
    }
    RxInterface.proxy = observer;
    return result;
  }

  @override
  Widget build(BuildContext context) => notifyChilds;
}
  1. initState() 方法中, subs = _observer.listen(_updateTree),监听了数据更新,当数据更新后,调用_updateTree更新视图。
  2. notifyChilds() 方法中RxInterface.proxy = _observer; 返回到Obx()开头的代码:
  Obx(() => Text(// 响应数据变化
                  '${_counter.count.value}',
                  style: Theme.of(context).textTheme.headline4,
                )),

  T get value {
    if (RxInterface.proxy != null) {
      RxInterface.proxy.addListener(subject); // 这里建立了数据关系,添加了数据变化监听
    }
    return _value;
  }

可以看到RxInterface.proxy。 当Obx 执行build时调用notifyChilds(),会把当前_observer赋值给RxInterface.proxy,再执行widget.build(),运行到_counter.count.value的get方法,RxInterface.proxy.addListener(subject); 这样变量和Widget的关联就完整建立。

  1. 恢复RxInterface.proxy,详见下面代码:
    final result = widget.build();
    if (!_observer.canUpdate) {
      throw """
      [Get] the improper use of a GetX has been detected. 
      You should only use GetX or Obx for the specific widget that will be updated.
      If you are seeing this error, you probably did not insert any observable variables into GetX/Obx 
      or insert them outside the scope that GetX considers suitable for an update 
      (example: GetX => HeavyWidget => variableObservable).
      If you need to update a parent widget and a child widget, wrap each one in an Obx/GetX.
      """;
    }
    RxInterface.proxy = observer;

RxInterface.proxy = observer; 恢复RxInterface.proxy为之前保存的值。

4. GetBuilder

这个绑定关系比较好理解,就不写了

以上代码为自己的理解,有错误的欢迎各位大佬指正。