likes
comments
collection
share

Flutter中Globalkey和Get是如何获取BuildContext的

作者站长头像
站长
· 阅读数 17

前言

最近闲下来在回顾知识点的时候,发现用了很久的Get.context却并不知道它的具体实现逻辑,和GlobalKey获取的BuildContext的方式又有什么共同点和差异点。今天就来分析一下它们。

GlobalKey的实现

@optionalTypeArgs
abstract class GlobalKey<T extends State<StatefulWidget>> extends Key {
  factory GlobalKey({ String? debugLabel }) => LabeledGlobalKey<T>(debugLabel);
  
  const GlobalKey.constructor() : super.empty();

  Element? get _currentElement => WidgetsBinding.instance.buildOwner!._globalKeyRegistry[this];

  BuildContext? get currentContext => _currentElement;

  Widget? get currentWidget => _currentElement?.widget;

  T? get currentState {
    final Element? element = _currentElement;
    if (element is StatefulElement) {
      final StatefulElement statefulElement = element;
      final State state = statefulElement.state;
      if (state is T) {
        return state;
      }
    }
    return null;
  }
}

GlobalKey是一个抽象类,我们创建的GlobalKey实际返回的是LabeledGlobalKey,这里我们不去探究。

GlobalKey内部实现非常简单,提供了三个getter,这里我们要探究的是currentContext

currentContext从何而来

Element? get _currentElement => WidgetsBinding.instance.buildOwner!._globalKeyRegistry[this];

BuildContext? get currentContext => _currentElement;

可以看到currentContext获取的是_currentElement_currentElement又是从_globalKeyRegistry中获取的传入当前this对象。

class BuildOwner {

  final Map<GlobalKey, Element> _globalKeyRegistry = <GlobalKey, Element>{};

  void _registerGlobalKey(GlobalKey key, Element element) {
    _globalKeyRegistry[key] = element;
  }

  void _unregisterGlobalKey(GlobalKey key, Element element) {
    if (_globalKeyRegistry[key] == element) {
      _globalKeyRegistry.remove(key);
    }
  }
}

可以看到在BuildOwner关于_globakKeyRegistry的实现也很简单,一个GlobalKeykeyElementvaluemap,提供了添加移除的方法。这两个方法又是何时调用的呢。

GlobalKey添加、取消

我们都知道Globalkey是作为key传入Widget的,我们随便找一个Widget一探究竟。

abstract class StatefulWidget extends Widget {
  const StatefulWidget({ super.key });
}

abstract class Widget extends DiagnosticableTree {
  const Widget({ this.key });
  final Key? key;
}

我们在Widget传入的key最终会赋值给Widget,而Widget只是作为配置项最终创建的是Element,所以我们进入Element查看。

abstract class Element extends DiagnosticableTree implements BuildContext {

  void mount(Element? parent, Object? newSlot) {
    if (parent != null) {
      _owner = parent.owner;
    }
    final Key? key = widget.key;
    if (key is GlobalKey) {
      owner!._registerGlobalKey(key, this);
    }
  }
  void unmount() {
    final Key? key = _widget?.key;
    if (key is GlobalKey) {
      owner!._unregisterGlobalKey(key, this);
  }
}

去掉无关代码,ELement添加移除GlobalKey的地方有两处,mount挂载和unmount取消挂载,只有传入的keyGlobalKey时才会执行。


abstract class GlobalKey<T extends State<StatefulWidget>> extends Key {
  Element? get _currentElement => WidgetsBinding.instance.buildOwner!._globalKeyRegistry[this];

  BuildContext? get currentContext => _currentElement;
}

再回过头来看GlobalKey内部实现,就能明白GlobakKey是如何拿到当前当前BuildContext的了。

Get.context的实现


extension GetNavigation on GetInterface {

  GlobalKey<NavigatorState> get key => _getxController.key;

  BuildContext? get context => key.currentContext;

  static GetMaterialController _getxController = GetMaterialController();
}

点击查看Get.context源码,我们可以找到上面三行代码,可以看到Get获取context的方式也是用的GlobalKey,它只是对这个方式做了一层封装供我们使用。

这里的key是从GetMaterialController中获取的,接下来我们来看看在GetMaterialControllerkey是怎么定义的。

Get中GlobalKey是如何创建的

class GetMaterialController extends SuperController {

  var _key = GlobalKey<NavigatorState>(debugLabel: 'Key Created by default');
  GlobalKey<NavigatorState> get key => _key;   
   
  GlobalKey<NavigatorState>? addKey(GlobalKey<NavigatorState> newKey) {
    _key = newKey;
    return key;
  }
    
}

GetMaterialController中有一个默认的key,同时也能调用addKey替换默认的key,这个默认的key又是何时生效的呢。

GlobakKey如何起作用

我们把目光放回到main.dart中,再使用了Get我们都会把MaterialApp替换成GetMaterialApp,而GetMaterialApp对其做一层封装就是为了执行自己的一系列操作逻辑。

class GetMaterialApp extends StatelessWidget {

final GlobalKey<NavigatorState>? navigatorKey;

  const GetMaterialApp({
    Key? key,
    this.navigatorKey,
  });
  
 
  @override
  Widget build(BuildContext context) => GetBuilder<GetMaterialController>(
    builder: (_) => 
      MaterialApp(
      navigatorKey: (navigatorKey == null
        ? Get.key
        : Get.addKey(navigatorKey!)),
  )
}

可以看到,在Get中如果我们传入了navigatorKey就调用addKey替换它的默认key,如果没有传入就使用Get的默认key

看到这里我们也就能明白Get.context是如何起作用和获取到了。

总结

  • GloblKey最终赋值给Widget、在对应的Element中调用BuildOwner进行添加和取消
  • GlobalKey获取到的key是在BuildOwner中添加时传入的当前Element
  • Get中有默认的GlobalKey,在GetMaterialApp中如果我们传入了navigatorKey,就会替代默认的key
  • Get.context原理就是GlobalKeyGet只是对这种方式的一种封装

关于BuildOwner的知识如果有不了解的,Flutter框架分析 -- runApp初始化里面有对WidgetBindingBuildOwner有的介绍。