渲染机制似乎是所有前端框架开发者都要深入了解的知识。
计算机绘图原理:
屏幕显示器一般以60Hz的固定频率刷新,每一帧图像绘制完成后,会继续绘制下一帧,这时显示器就会发出一个Vsync信号,按60Hz计算,屏幕每秒会发出60次这样的信号。CPU计算好显示内容提交给GPU,GPU渲染好传递给显示器显示。
flutter架构主要分三部分:
其中负责渲染的主要是framework和engine。
Flutter Framework 是一个纯 Dart实现的 SDK。它实现了一套基础库, 用于处理动画、绘图和手势。并且基于绘图封装了一套 UI组件库,然后根据 Material 和Cupertino两种视觉风格区分开来。这个纯 Dart实现的 SDK被封装为了一个叫作 dart:ui的 Dart库。我们在使用 Flutter写 App的时候,直接导入这个库即可使用组件等功能。
Flutter Engine 是一个纯 C++实现的 SDK。囊括了 Skia引擎、Dart运行时、文字排版引擎等。它是 Dart的一个运行时,它可以以 JIT、JIT Snapshot 或者 AOT的模式运行 Dart代码。这个运行时还控制着 VSync信号的传递、GPU数据的填充等,并且还负责把客户端的事件传递到运行时中的代码。
1.首先是获取到用户的操作,然后你的应用会因此显示一些动画,接着 Flutter 开始构建 Widget 对象。
2.Widget 对象构建完成后进入渲染阶段,这个阶段主要包括三步:
3.最后的光栅化由 Engine 层来完成。
在渲染阶段,有三颗重要的树,Widget树,Element树,Render树。其中,控件树(widget)最终会转换成对应的渲染对象(RenderObject)树,在 Rendering 层进行布局和绘制。
Widget.createElement
创建一个Element实例,记为element;element.mount(parentElement,newSlot)
,将element相关联的renderObject插入到渲染树中,插入到渲染树后的element就处于“active”状态,处于“active”状态后就可以显示在屏幕上了。canUpdate
方法,主要是判断newWidget与oldWidget的runtimeType和key是否同时相等,如果同时相等就返回true,表示复用旧Element,否则返回false,重新创建。大体流程是:根据Widget生成Element,然后创建相应的RenderObject并关联到Element.renderObject属性上,最后再通过RenderObject来完成布局排列和绘制。
不同的Widget会生成不同的Element,以下是源码中常见额三种Element:
创建Element之后,创建出来的elment会拿到传过来的widget,然后调用widget自己的build方法。针对StatefulWidget,调用build的时候,调用的是state中的build方法。build方法传入的参数都是Element自己,所以本质上BuildContext就是当前的Element。
我们在自定义Widget的时候,每一个Widget, 在其构造方法中我们都会看到一个参数Key,其实这个key就是更新Element用的,通过Widget的canUpdate
做到有效的增量更新:
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType
&& oldWidget.key == newWidget.key;
}
默认情况下我们几乎不设置key,Element的重用机制主要靠runtimeType来决定,大部分情况都是可重用的,所以想要Element强制重建,我们可以自定一个唯一的key,传入进去既可。
Key本身是一个抽象类,子类包含LocalKey和GlobalKey。
LocalKey可以派生出多个子类,用于不同的场景:
GlobalKey 可以获取到对应的widget的state对象,GlobalKey 使用了一个静态常量 Map 来保存它对应的 Element。你可以通过 GlobalKey 找到持有该GlobalKey的 Widget,State 和 Element。需要注意的是:GlobalKey 是非常昂贵的,需要谨慎使用。
以下是GlobalKey使用场景:
class GlobalKeyDemo extends StatelessWidget {
final GlobalKey<_ChildPageState> _globalKey = GlobalKey();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('GlobalKeyDemo'),
),
body: ChildPage(
key: _globalKey,
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
_globalKey.currentState.data =
'old:' + _globalKey.currentState.count.toString();
_globalKey.currentState.count++;
_globalKey.currentState.setState(() {});
},
),
);
}
}
class ChildPage extends StatefulWidget {
ChildPage({Key key}) : super(key: key);
@override
_ChildPageState createState() => _ChildPageState();
}
class _ChildPageState extends State {
int count = 0;
String data = 'hello';
@override
Widget build(BuildContext context) {
return Center(
child: Column(
children: [
Text(count.toString()),
Text(data),
],
),
);
}
}
1.createState 方法在什么时候调用?state 里面为啥可以直接获取到 widget 对象?
答:Flutter 会在遍历 Widget 树时调用 Widget 里面的 createElement 方法去生成对应节点的 Element 对象,同时执行 StatefulWidget 里面的 createState 方法创建 state,并且赋值给 Element 里的 _state 属性,当前 widget 也同时赋值给了 state 里的_widget,state 里面有个 widget 的get 方法可以获取到 _widget 对象。
2.build 方法是在什么时候调用的?
答:Element 创建好以后 Flutter 框架会执行 mount 方法,对于非渲染的 ComponentElement 来说 mount 主要执行 widget 里的 build 方法,StatefulElement 执行 build 方法的时候是执行的 state 里面的 build 方法,并且将自身传入,也就是常见的 BuildContext。
3.BuildContext 是什么?
答:StatefulElement 执行 build 方法的时候是执行的 state 里面的 build 方法,并且将自身传入,也就是 常见的 BuildContext。简而言之 BuidContext 就是 Element。
4.Widget 频繁更改创建是否会影响性能?复用和更新机制是什么样的?
答:不会影响性能,widget 只是简单的配置信息,并不直接涉及布局渲染相关。Element 层通过判断新旧 widget 的runtimeType 和 key 是否相同决定是否可以直接更新之前的配置信息,也就是替换之前的 widget,而不必每次都重新创建新的 Element。
5.创建 Widget 里面的 Key 到底是什么作用?
答:Key 作为 Widget 的标志,在widget 变更的时候通过判断 Element 里面之前的 widget 的 runtimeType 和 key来决定是否能够直接更新。