BuildContext

每次我们在编写界面部分代码的时候,都是在build函数中进行操作。而build函数则需要默认传入一个BuildContext。我们来看看这到底是啥。

1
2
3
4
5
6
7
8
abstract class BuildContext {
/// The current configuration of the [Element] that is this [BuildContext].
Widget get widget;

/// The [BuildOwner] for this context. The [BuildOwner] is in charge of
/// managing the rendering pipeline for this context.
BuildOwner get owner;
...

我们可以看到BuildContext其实是一个抽象类,但是每次build函数传进来的是什么呢。我们来看看构建视图的时候到底发生了什么。

Flutter如何构建视图

在Flutter中,Everything is Widget,我们通过构造函数嵌套Widget来编写UI界面。实际上,Widget并不是真正要显示在屏幕上的东西,只是一个配置信息,它永远是immutable(不可变)的,并且可以在多处重复使用。那真正显示在屏幕上的视图树是什么呢?Element Tree

那我们来看一下,在构建视图的时候究竟发生了什么。这里以StatelessWidget为例。

1
2
3
4
5
abstract class StatelessWidget extends Widget {
const StatelessWidget({ Key key }) : super(key: key);
@override
StatelessElement createElement() => StatelessElement(this);
...

当要把这个widget装进视图树的时候,首先会去createElement,并将当前widget传给Element。

我们再来看一看这个StatelessElement是什么

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class StatelessElement extends ComponentElement {
/// Creates an element that uses the given widget as its configuration.
StatelessElement(StatelessWidget widget) : super(widget);

@override
StatelessWidget get widget => super.widget;

@override
Widget build() => widget.build(this);

@override
void update(StatelessWidget newWidget) {
super.update(newWidget);
assert(widget == newWidget);
_dirty = true;
rebuild();
}
}

我们可以看到,通过将widget传入StatelessElement的构造函数,StatelessElement保留了widget的引用,并且将会调用build方法。

而这个build方法真正调用的则是widget的build方法,并将this,也就是该StatelessElement对象传入。我们知道,build方法需要传入的是一个BuildContext,为什么传进去了StatelessElement?于是我们继续看。

1
2
3
4
5
class StatelessElement extends ComponentElement
...
abstract class ComponentElement extends Element
...
abstract class Element extends DiagnosticableTree implements BuildContext

实际上是Element类实现了BuildContext,并由ComponentElement -> StatelessElement 继承。

所以我们现在再来看官方对于BuildContext的解释:

BuildContext objects are actually Element objects. The BuildContext interface is used to discourage direct manipulation of Element objects.

BuildContext对象实际上就是Element对象,BuildContext 接口用于阻止对 Element 对象的直接操作。

我们现在终于知道这个BuildContext是哪里来的了。让我们再来梳理一下,flutter构建视图究竟做了什么。

视图树装载过程

StatelessWidget

  • 首先它会调用StatelessWidget的 createElement 方法,并根据这个widget生成StatelesseElement对象。
  • 将这个StatelesseElement对象挂载到Element树上。
  • StatelesseElement对象调用widget的build方法,并将element自身作为BuildContext传入。

StatefulWidget

  • 首先同样也是调用StatefulWidget的 createElement方法,并根据这个widget生成StatefulElement对象,并保留widget引用。
  • 将这个StatefulElement挂载到Element树上。
  • 根据widget的 createState 方法创建State。
  • StatefulElement对象调用state的build方法,并将element自身作为BuildContext传入。

所以我们在build函数中所使用的context,正是当前widget所创建的Element对象。

What do you need to know about context

  • Context is a link to the location of a widget in the tree structure of widgets.
  • Context can belong to only one widget.
  • If a widget has child widgets, then the context of the parent widget becomes the parent context for the contexts of direct child elements.
  • A widget is visible only in its own context or in the context of its parent context.
    Thus, it becomes clear that knowing the children’s context, you can easily find the parent widget. Conversely, using the parent context you can find the child widget.

如果你还记得这幅图:

of(context)方法

在flutter中我们经常会使用到这样的代码

1
2
3
4
5
6
//打开一个新的页面
Navigator.of(context).push
//打开Scaffold的Drawer
Scaffold.of(context).openDrawer
//获取display1样式文字主题
Theme.of(context).textTheme.display1

那么这个of(context)到底是个什么呢。我们这里以Navigator打开新页面为例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static NavigatorState of(
BuildContext context, {
bool rootNavigator = false,
bool nullOk = false,
}) {
//关键代码-----------------------------------------v

final NavigatorState navigator = rootNavigator
? context.rootAncestorStateOfType(const TypeMatcher<NavigatorState>())
: context.ancestorStateOfType(const TypeMatcher<NavigatorState>());

//关键代码----------------------------------------^
assert(() {
if (navigator == null && !nullOk) {
throw FlutterError(
'Navigator operation requested with a context that does not include a Navigator.\n'
'The context used to push or pop routes from the Navigator must be that of a '
'widget that is a descendant of a Navigator widget.'
);
}
return true;
}());
return navigator;
}

可以看到,关键代码部分通过context.rootAncestorStateOfType向上遍历 Element tree,并找到最近匹配的 NavigatorState。也就是说of实际上是对context跨组件获取数据的一个封装。

而我们的Navigator的 push操作就是通过找到的 NavigatorState 来完成的。

不仅如此,BuildContext还有许多方法可以跨组件获取对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
ancestorInheritedElementForWidgetOfExactType(Type targetType) → InheritedElement

ancestorRenderObjectOfType(TypeMatcher matcher) → RenderObject

ancestorStateOfType(TypeMatcher matcher) → State

ancestorWidgetOfExactType(Type targetType) → Widget

findRenderObject() → RenderObject

inheritFromElement(InheritedElement ancestor, { Object aspect }) → InheritedWidget

inheritFromWidgetOfExactType(Type targetType, { Object aspect }) → InheritedWidget

rootAncestorStateOfType(TypeMatcher matcher) → State

visitAncestorElements(bool visitor(Element element)) → void

visitChildElements(ElementVisitor visitor) → void

需要注意的是,如果我们需要与祖先 Inherit 对象建立长期联系,dependOnInheritedWidgetOfExactType 系列的方法不能在 initState 中调用,为了确保 Widget 在 Inherit 值更改时正确更新自身,请在 State.didChangeDependencies 阶段调用 of 方法。

 

当然,这里的讲解只是非常简略的,如果你想更深入的了解BuildContext,可以参考这篇博客,这篇博客由浅入深地解释了对应的机制。

我在这里摘了一点:

So now we know about two categories of widgets:

  1. Composing widgets. These extend either StatelessWidget or StatefulWidget. They don’t do anything related to rendering but just compose more complex views out of other widgets. They override the build method.
  2. Rendering widgets. These ultimately extend RenderObjectWidget but typically through one of the following subclasses:
  • SingleChildRenderObjectWidget for widgets that paint only one child, like ColoredBox
  • MultiChildRenderObjectWidget for widgets that paint multiple children, like Column
  • LeafChildRenderObjectWidget for widgets that don’t paint any children, only themselves, like ErrorWidget
  1. Those widgets do the actual lower-level rendering via the RenderObject class. They don’t override the build method but rather createRenderObject and updateRenderObject.

During the development of your Flutter apps, you will usually extend the Stateless and Stateful widgets. But it’s important to understand that no matter how aggressively you compose your widget tree, it will always end up with some kind of RenderObjectWidgets.

在了解了WidgetRenderObject后,或者说,widget tree and the render object tree,我们引出 Element

Elements are what glue together the immutable widgets and mutable render objects. The elements are mutable themselves, meaning that their lifecycle is much longer than a widget’s. We’ll cover this more in a bit. Side note: We will be simplifying and skipping a few things on purpose because not all the implementation details are necessary to understand this article.

The very first time that your Widget gets created and inserted into the widget tree, it also creates an Element via its createElement method. But the next time the tree gets rebuilt, the framework checks if the widget that gets returned from the build method can update the element in that place in the tree.

Now, let’s switch our focus back to our RenderObjectWidget for a moment. In the same way the StatelessWidget gets inflated into a StatelessElement, the RenderObjectWidget gets inflated into a RenderObjectElement. But besides updating the link to its widget, it also updates its renderObject. But the RenderObjects are also smart, as they check if there were any changes in the configuration. If there weren’t, then they don’t repaint. If there were, then they repaint those parts.

 

参考

https://juejin.cn/post/6844903777565147150

https://appvesto.medium.com/flutter-what-is-context-ef2ed6dc7162