Flutter学习总结(六)渲染机制

渲染机制似乎是所有前端框架开发者都要深入了解的知识。

绘图原理

计算机绘图原理:

屏幕显示器一般以60Hz的固定频率刷新,每一帧图像绘制完成后,会继续绘制下一帧,这时显示器就会发出一个Vsync信号,按60Hz计算,屏幕每秒会发出60次这样的信号。CPU计算好显示内容提交给GPU,GPU渲染好传递给显示器显示。

Flutter也遵循这种模式:
Flutter渲染流程图

  • GPU的VSync信号同步给到UI线程,UI线程使用Dart来构建抽象的视图结构
  • 绘制好的抽象视图数据结构在GPU线程中进行图层合成,然后提供给Skia引擎渲染为GPU数据,最后通过OpenGL或者 Vulkan提供给 GPU。

Flutter框架

flutter架构主要分三部分:

  1. framework 主要有Dart开发业务逻辑和各种UI组件。
  2. engine 主要使用 C++ 编写,提供了 Flutter 核心 API 的底层实现。
  3. embedder 充当着宿主操作系统和 Flutter 之间的粘合剂的角色,类似java和c通信的jni层。

Flutter架构概览

其中负责渲染的主要是framework和engine。

Framework

Flutter Framework 是一个纯 Dart实现的 SDK。它实现了一套基础库, 用于处理动画、绘图和手势。并且基于绘图封装了一套 UI组件库,然后根据 Material 和Cupertino两种视觉风格区分开来。这个纯 Dart实现的 SDK被封装为了一个叫作 dart:ui的 Dart库。我们在使用 Flutter写 App的时候,直接导入这个库即可使用组件等功能。

Engine

Flutter Engine 是一个纯 C++实现的 SDK。囊括了 Skia引擎、Dart运行时、文字排版引擎等。它是 Dart的一个运行时,它可以以 JIT、JIT Snapshot 或者 AOT的模式运行 Dart代码。这个运行时还控制着 VSync信号的传递、GPU数据的填充等,并且还负责把客户端的事件传递到运行时中的代码。

绘图流程

UI框架绘图流程

1.首先是获取到用户的操作,然后你的应用会因此显示一些动画,接着 Flutter 开始构建 Widget 对象。

2.Widget 对象构建完成后进入渲染阶段,这个阶段主要包括三步:

  • 布局元素:决定页面元素在屏幕上的位置和大小;
  • 绘制阶段:将页面元素绘制成它们应有的样式;
  • 合成阶段:按照绘制规则将之前两个步骤的产物组合在一起。

3.最后的光栅化由 Engine 层来完成。

布局

  • 布局约束,由父控件约束子控件。父控件通过传递Containers参数,告诉子控件自己的大小(布局约束),以此来决定子控件的位置。
  • 大小尺寸,由子控件传给父控件。子控件的位置不存储在自己的容器中,而是存储在自己的parentData字段里,所以当他的位置发生变化时,并不需要重新布局或绘制。
  • 子节点不关心自己所在的位置,父节点也不关心子节点具体长什么样子。

布局约束和大小尺寸

Flutter三棵树

在渲染阶段,有三颗重要的树,Widget树,Element树,Render树。其中,控件树(widget)最终会转换成对应的渲染对象(RenderObject)树,在 Rendering 层进行布局和绘制。

Flutter三棵树

Widget树

  • 整个Flutter项目结构也是由很多个Widget构成的, 本质上就是一个Widget Tree。
  • Widget里面存储了一个视图的配置信息,包括布局、属性等。
  • Widget仅仅是配置文件,比较轻量,不参与直接的绘制。
  • Widget频繁创建和销毁不会太影响性能。

Element树

  • Element是Widget在树中具有特定位置的是实例化。当一个Widget首次被创建的时候,那么这个Widget会通过Widget.createElement,创建一个element。
  • Element创建的同时还持有 Widget和 RenderObject的引用。
  • 当Widget Tree所依赖的状态发生改变,Element根据拿到之前所保存的旧的Widget和新的Widget做一个对比, 判断两者的Key和类型是否是相同的, 相同的就不需要重新创建,只需要更新对应的属性,并将真正需要修改的部分同步到真实的RenderObject树中。

Render树

  • RenderObject层是渲染库的核心,主要负责layout、paint等复杂操作,最终Flutter Engin是把RenderObject真正渲染到界面上的。
  • 并不是所有的Widget都会被独立渲染,只有继承RenderObjectWidget的才会创建RenderObject对象。

树的创建过程

  • Widget隐式调用Widget.createElement 创建一个Element实例,记为element;
  • 接着调用element.mount(parentElement,newSlot),将element相关联的renderObject插入到渲染树中,插入到渲染树后的element就处于“active”状态,处于“active”状态后就可以显示在屏幕上了。
  • 为了进行Element复用,在Element重新构建前会先尝试是否可以复用旧树上相同位置的element,element节点在更新前都会调用其对应Widget的canUpdate方法,主要是判断newWidget与oldWidget的runtimeType和key是否同时相等,如果同时相等就返回true,表示复用旧Element,否则返回false,重新创建。

大体流程是:根据Widget生成Element,然后创建相应的RenderObject并关联到Element.renderObject属性上,最后再通过RenderObject来完成布局排列和绘制。

不同的Widget会生成不同的Element,以下是源码中常见额三种Element:

  • StatelessElement StatelessWidget会创建StatelessElement,这里主要就是调用build方法,将Element传出去。
  • StatefulElement StatefulWidget会创建StatefulElement,创建出来后调用createState方法,创建state,将Widget赋值给state,最后调用state的build方法,并且将Element传出去。
  • RenderElement RenderElement主要是创建RenderObject对象,只有继承自RenderObjectWidget的Widget会创建RenderObjectElement,创建步骤先创建RanderElement,创建出来后调用mount方法,在mount方法中会调用createRenderObject方法,来创建RenderObject。

BuildContext

创建Element之后,创建出来的elment会拿到传过来的widget,然后调用widget自己的build方法。针对StatefulWidget,调用build的时候,调用的是state中的build方法。build方法传入的参数都是Element自己,所以本质上BuildContext就是当前的Element。

构造函数中的Key

我们在自定义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的分类

Key本身是一个抽象类,子类包含LocalKey和GlobalKey。

LocalKey可以派生出多个子类,用于不同的场景:

  • ValueKey 以一个数据作为Key,如:数字,字符
  • ObjectKey 以object对象作为Key,例如 ObjectKey(Text(‘222’))
  • UniqueKey 可以保证key的唯一性(一旦使用UniqueKey那么就不存在Element复用了)

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<ChildPage> {
int count = 0;
String data = 'hello';
@override
Widget build(BuildContext context) {
  return Center(
    child: Column(
      children: <Widget>[
        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来决定是否能够直接更新。

参考

  • https://www.jianshu.com/p/9650780dcbf5
  • https://www.jianshu.com/p/bcc74f37aba5
  • https://www.jianshu.com/p/7fe3cedb67da
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值