从源码看Flutter BuildContext的秘密
我们每次在写Flutter代码的时候,都会看到这个参数——BuildContext,在Android开发中,也经常看见一个类似的东西——Context,它们是不是一样的呢?其实说一样也对,它们都是上下文的关键承载者,但是却也不一样,因为它们本质上是两个不同的概念。在Flutter中,BuildContext的源码如下。
从注释中我们就可以看出,[BuildContext]对象实际上是[Element]对象。[BuildContext]接口是用来阻止对[Element]对象的直接操作,它就是为了避免直接操纵Element类而创建的。
Element是Flutter UI中的一个非常重要的组成,Flutter UI在创建时,会通过Widget的createElement方法创建Element,然后Framework会调用Element实例的mount方法,在这个方法中,根据需要创建RenderObject,并挂载到Element的renderObject属性上,实际的布局和绘制,通常都是通过RenderObject的实现类RenderBox来实现的。
所以,我们甚至可以直接把Context强转为Element,从而调用Element的方法,例如下面的代码。
```dart var size = ((context as Element).findRenderObject() as RenderBox).size;
(context as Element).markNeedsBuild(); ```
在使用BuildContext的时候,我们最常见的一个误区就是下面这个例子。
这段代码很简单,就是在当前页面上路由到一个新的页面,但是我们执行后,上面的代码会报错。
从错误原因上我们可以看到,就是Context的问题,也就是of(context)这个方法。类似的代码风格,我们在Flutter中可以找到很多,例如下面这些。
dart
Navigator.of(context)
Scaffold.of(context).openDrawer()
Theme.of(context).copyWith()
……
就以Navigator.of(context)为例,我们来看下of方法的实现。
可以看到,关键代码就是通过context.findRootAncestorStateOfType
而我们的Navigator的push操作就是通过找到的NavigatorState来完成的。
那么我们现在来看下上面的那个错误具体是怎么产生的。当我们在build函数中使用Navigator.of(context)的时候,这个context实际上是通过MyApp这个widget创建出来的Element对象,而of方法向上寻找祖先节点的时候(MyApp的祖先节点),其实并不存在MaterialApp,也就没有它所提供的Navigator,所以就出错了。
那么当我们把Scaffold的部分拆成另外一个widget的时候,我们在FirstPage的build函数中,获取的就是FirstPage的BuildContext,然后向上寻找发现了MaterialApp,并找到它提供的Navigator,于是就可以愉快进行页面跳转了。所以要解决这个问题,一般有两个方法,一个就是抽取出一个新的Widget组件,或者是通过Builder组件,为后续Widget创建一个新的BuildContext环境。
所以,看到这里,你可以认为,BuildContext,实际上就是当前Widget在Element树上的句柄。
下面我们就从源码角度,来看下BuildContext的创建与加载的过程。
我们以StatelessWidget为例,在创建StatelessWidget的时候,首先会去createElement,并将当前widget传给Element,即StatelessElement。
在这个Element中,我们发现它的build方法,实际上就是调用了Widget的build方法,同时,传入了this,这个this,实际上就是我们在Widget的build方法中看到的BuildContext,到处,我们终于理清了,为什么BuildContext就是Element了。
同时,正是由于Flutter视图的树形结构,可以让我们很方便的在树上游走,这就需要我们用到它的一些游走的方法。
| dependOnInheritedElement | InheritedWidget | working on the base of ancestor's widget and rebuilding when ancestors change | | --------------------------------------- | ----------------- | --------------------------------------------------------------------------------- | | dependOnInheritedWidgetOfExactType | T? | runtime type of widgets or functions or model | | describeElement | DiagnosticsNode | descriptions of elements | | describeMissingAncestor | List | List of missing ancestors | | describeOwnershipChain | DiagnosticsNode | describes the ownership chain to the error report | | describeWidget | DiagnosticsNode | describe the details and working features of the widget | | dispatchNotification | void | bubble notification indicator at the context | | findAncestorRenderObjectOfType | T? | runtime type of RenderObjectOfType | | findAncestorStateOfType | T? | runtime type of StateOfType | | findAncestorWidgetOfExactType | T? | runtime type of WidgetOfExactType | | findRenderObject | T? | current render object which is created by itSelf | | findRootAncestorStateOfType | T? | runtime type ancestor's of given T type stateful widgets instance | | getElementForInheritedWidgetOfExactType | InheritedElement? | Type of concrete inheritedWidget subclass | | noSuchMethod | dynamic | dynamic type of method | | toString | string | converting the date to string through .toString methods provided by the framework | | visitAncestorElements | void | works for the call back return function, call back can not be null | | visitChildElements | void | children of the widgets or visitor |
这些方法也不用死记硬背,你只需要时刻记得「那几棵树」即可。
欢迎大家关注我的公众号——【群英传】,专注于「Android」「Flutter」「Kotlin」
我的语雀知识库——https://www.yuque.com/xuyisheng
- 从源码看Flutter BuildContext的秘密
- 闲言碎语-第八期
- kotlin修炼指南9-Sequence的秘密
- 起点客户端精准化测试的演进之路
- Flutter混编工程之打通纹理之路
- Android壁纸还是B站玩得花
- Flutter布局指南之谁动了我的Key
- Material Components——ShapeableImageView
- JetPack指路明灯—Navigation
- Material Components—预备役选手Transition
- 静若处子动若脱兔-Constraintlayout2.0一探究竟
- Kotlin修炼指南5
- 重走Flutter状态管理之路—Riverpod最终篇
- Material Components——MaterialButton
- ConstraintLayout2.0进阶之路-欢迎新同学
- ConstraintLayout使用场景必知必会
- 重走Flutter状态管理之路—Riverpod进阶篇
- 它来了!Flutter3.0新特性全接触
- 重走Flutter状态管理之路—Riverpod入门篇
- 它来了!Flutter3.0发布全解析