Flutter渲染完全解读系列:启动与三棵树的构建

Posted Android开发骆驼

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flutter渲染完全解读系列:启动与三棵树的构建相关的知识,希望对你有一定的参考价值。

现在,flutter这门技术已经算是逐渐走向成熟,大大小小的互联网公司不少都开始投入使用。还没有上手的移动开发们可以放心上手了,毕竟好不好用,也只有用了才知道。

学习一门知识最好的办法就是学以致用,并总结出自己的方法论。也只有真正理解了,才能总结出有用的经验。下面是知乎一个大佬的学习经验分享,欢迎大家一起探讨学习。

大佬原文地址:https://zhuanlan.zhihu.com/p/388724188

调用图

我们用下面的例子来分析Flutter启动所做的事情

void main() 
  runApp(MyApp());


class MyApp extends StatelessWidget 
  const MyApp(Key key) : super(key: key);

  @override
  Widget build(BuildContext context) 
    return Container();
  

初始化Binding

runApp

void runApp(Widget app) 
  WidgetsFlutterBinding.ensureInitialized()
    ..scheduleAttachRootWidget(app)
    ..scheduleWarmUpFrame();

runApp中,我们会调用WidgetsFlutterBinding.ensureInitialized()来初始化Binding

Binding

Binding是一系列单例,在启动的时候初始化,其中包括了WidgetsFlutterBindingBindingBaseGestureBindingSchedulerBindingServicesBinding等,下面来分析它们的作用

  • BindingBase: 是所有类的基类,负责初始化其它类、初始化一些Native相关的信息(如平台是android还是ios)、注册基本的Native事件(如热更新、退出)
  • GestureBinding: 提供window.onPointerDataPacket回调,接受Native事件,负责事件转换及分发
  • SchedulerBinding: 使用了window.scheduleFrame来通知Native及使用window.onBeginFramewindow.onDrawFrame回调来接收消息,主要是负责通知Native在下一侦的事件下发与事件注册,当我们调用setState后,就会触发此类的方法,等待事件下发后进行渲染
  • ServicesBinding: 使用window.onPlatformMessage回调,负责通道相关的初始化及通信相关的处理
  • PaintingBinding: 与绘制相关的函数绑定,还处理一些图片渲染相关的缓存
  • SemanticsBinding: 注册平台相关的辅助函数
  • RendererBinding: 初始化PipelineOwnerrenderViewonMetricsChangedonTextScaleFactorChangedonPlatformBrightnessChangedonSemanticsEnabledChanged onSemanticsAction等,用于监听并处理平台渲染相关如字体、状态栏改变时的事件,是渲染输与Flutter engine沟通的桥梁
  • WidgetsBinding: 初始化BuildOwner,注册window.onLocaleChangedonBuildScheduled等回调。它是Flutter widget层与engine的桥梁。

渲染树的构建

attachRootWidget

[-> packages/flutter/lib/src/widgets/binding.dart]

void attachRootWidget(Widget rootWidget) 
  _readyToProduceFrames = true;
  _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
    container: renderView,
    debugShortDescription: '[root]',
    child: rootWidget,
  ).attachToRenderTree(buildOwner!, renderViewElement as RenderObjectToWidgetElement<RenderBox>?);

上面会先初始化一个RenderObjectToWidgetAdapter,它是一个特殊的Widget,主要用Flutter树根节点的初始化,其中传了一个renderView,上面提到了是在RendererBinding初始化时就已经初始化了,它是根RenderObject节点,rootWidget就是我们runApp时传入的Widget

后面调用了attachToRenderTree会开始一系列的树构建

attachToRenderTree

[-> packages/flutter/lib/src/widgets/binding.dart]

RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [ RenderObjectToWidgetElement<T>? element ]) 
  // 如果element==null执行,第一次渲染element一定为null
  if (element == null) 
    owner.lockState(() 
      // 调用createElement创建Element
      element = createElement();
      assert(element != null);
      // 将owner传给element,让element持有owner
      element!.assignOwner(owner);
    );
    owner.buildScope(element!, () 
      element!.mount(null, null);
    );
    SchedulerBinding.instance!.ensureVisualUpdate();
   else 
    element._newWidget = this;
    element.markNeedsBuild();
  
  return element!;

上面会先根据createElement创建一个Element对象,然后传入ownerBuildOwner是Flutter处理更新的类,主要是处理脏节点收集与重绘,需要注意的是,根据Flutter的设计,全局只有一个BuildOwner对象,在此处传入后,它会在子节点传递。

然后会调用owner.buildScapebuildScape主要提供两个作用,一是为Widget Tree更新建立一个作用域并执行回调(像上面这种情况,不过回调也可以不传),二是当作用域中存在脏节点,它会调用Element.rebuild方法进行重新构建(就在此时调用的Widget的build重新渲染Element)。

接着会调用element.mount,这是一个Element非常重要的方法,这里就开始构建三棵树了.mount最简单的作用就是为当前对象绑定父对象及为当前绑定_inheritedWidgets(你认为的没错,就是Flutter中用于数据共享的InheritedWidgetElement)。一般我们的子Element会重写mount方法,用于孩子节点的初始化。

mount

[-> packages/flutter/lib/src/widgets/framework.dart]

@override
void mount(Element? parent, dynamic newSlot) 
  assert(parent == null);
  super.mount(parent, newSlot);
  _rebuild();

void _rebuild() 
  //...
    _child = updateChild(_child, widget.child, _rootChildSlot);
  //...

所以在RenderObjectToWidgetElement中会调用_rebuild然后调用updateChild来初始化孩子节点。

updateChild

[-> packages/flutter/lib/src/widgets/framework.dart]

@protected
Element? updateChild(Element? child, Widget? newWidget, dynamic newSlot) 
  // ...
  final Element newChild;
  if (child != null) 
    bool hasSameSuperclass = true;
    if (hasSameSuperclass && child.widget == newWidget) 
      if (child.slot != newSlot)
        updateSlotForChild(child, newSlot);
      newChild = child;
     else if (hasSameSuperclass && Widget.canUpdate(child.widget, newWidget)) 
      //...
      child.update(newWidget);
      //...
      newChild = child;
     else 
      deactivateChild(child);
      newChild = inflateWidget(newWidget, newSlot);
    
   else 
    newChild = inflateWidget(newWidget, newSlot);
  
  return newChild;

updateChild方法用于初始化或者更新孩子节点,当child != null时,主要是调用child.update来更新child,否则调用inflateWidget,这里第一次渲染当然是会调用inflateWidget

inflateWidget

[-> packages/flutter/lib/src/widgets/framework.dart]

@protected
Element inflateWidget(Widget newWidget, dynamic newSlot) 
  // ...
  final Element newChild = newWidget.createElement();
  // ...
  newChild.mount(this, newSlot);
  return newChild;

inflateWidget方法的作用就是根据Widget生成Element同时调用child.mount。这样,一次树的循环调用就接上了。

Widget树构建

有人问StatelessWidgetStatefulWidget的build方法怎么都没调用。StatelessWidgetStatefulWidget对应的ElementStatelessElementStatefulElement,它们都是继承至ComponentElement。根据上面的分析,mount方法是构建起点,我们可以看下ComponentElement中的mount方法。

_firstBuild

[-> packages/flutter/lib/src/widgets/framework.dart]

@override
void mount(Element? parent, dynamic newSlot) 
  super.mount(parent, newSlot);
  assert(_child == null);
  assert(_lifecycleState == _ElementLifecycle.active);
  _firstBuild();
  assert(_child != null);

调用了_firstBuild方法,然后再调用了Elementrebuild方法,接着调用到ComponentElementperformRebuild方法

performRebuild

[-> packages/flutter/lib/src/widgets/framework.dart]

void rebuild() 
  performRebuild();

//...
@override
void performRebuild() 
  Widget? built;
  try 
    // ...
    built = build();
   catch (e, stack) 
    // ...
   finally 
    _dirty = false;
  
  try 
    _child = updateChild(_child, built, slot);
   catch (e, stack) 
    // ...
    _child = updateChild(null, built, slot);
  

  if (!kReleaseMode && debugProfileBuildsEnabled)
    Timeline.finishSync();

performRebuildComponentElement实现的父类,其中调用了build方法返回一个Widget,然后就看到了熟悉的updateChild方法,这就接上了上面的分析。

RenderObject树的构建

RenderObjectWidget

RenderObject是Flutter中用于实际绘制的类,对于StatefulWidgetStatelessWidget实际上都是没有对应的RenderObject节点的,实际上它们只是提供一个容器,用来组合各种绘制。

只有继承了RenderObjectWidgetWidget才有对应的RenderObject节点。Flutter中为了方便,又提供了三种继承至RenderObjectWidget的类,每个类都有不同作用

  • LeafRenderObjectWidget 用于只有渲染功能,无子节点 (如Switch、Radio等)
  • SingleChildRenderObjectWidget 只含有一个子节点 (如SizedBox、Padding等)
  • MultiChildRenderObjectWidget 含有多个子节点(如Column、Stack等)

上面这些对象只是提供了一个容器给开发者使用,不过不是我们这次分析重点,先看看RenderObjectWidget

[-> packages/flutter/lib/src/widgets/framework.dart]

abstract class RenderObjectWidget extends Widget 
  const RenderObjectWidget( Key? key ) : super(key: key);
  @override
  @factory
  RenderObjectElement createElement();
  @protected
  @factory
  RenderObject createRenderObject(BuildContext context);
  @protected
  void updateRenderObject(BuildContext context, covariant RenderObject renderObject)  
  @protected
  void didUnmountRenderObject(covariant RenderObject renderObject)  

它提供了createRenderObject方法来构建RenderObject,看下其对应的Element->RenderObjectElementmount方法

createRenderObject

[-> packages/flutter/lib/src/widgets/framework.dart]

@override
void mount(Element? parent, dynamic newSlot) 
  super.mount(parent, newSlot);
  _renderObject = widget.createRenderObject(this);
  attachRenderObject(newSlot);
  _dirty = false;

@override
void attachRenderObject(dynamic newSlot) 
  _slot = newSlot;
  _ancestorRenderObjectElement = _findAncestorRenderObjectElement();
  // 将上一步生成的renderObject插入到父renderObject中
  _ancestorRenderObjectElement?.insertRenderObjectChild(renderObject, newSlot);
  final ParentDataElement<ParentData>? parentDataElement = _findAncestorParentDataElement();
  if (parentDataElement != null)
    _updateParentData(parentDataElement.widget);

其会先调用createRenderObject来构建RenderObject,然后调用attachRenderObject将上一步生成的renderObject插入到父renderObject中。这里的通过_findAncestorParentDataElement来查找的父类,因为上面说过,不是每个Element都有对应的RenderObject节点。

_findAncestorRenderObjectElement

[-> packages/flutter/lib/src/widgets/framework.dart]

RenderObjectElement? _findAncestorRenderObjectElement() 
  Element? ancestor = _parent;
  while (ancestor != null && ancestor is! RenderObjectElement)
    ancestor = ancestor._parent;
  return ancestor as RenderObjectElement?;

所以这里通过父ElementElement树中找上一个为RenderObjectElement的类,而insertRenderObjectChild是由上面说的SingleChildRenderObjectWidgetMultiChildRenderObjectWidget来具体实现的,主要是将生成的RenderObject插入到刚刚查找的父RenderObject中。因为根Element节点RenderObjectToWidgetElement是继承至RenderObjectElement,所以,只要通过RenderObjectToWidgetElement.renderObject即可拿到整棵树RenderObject树。

总结

上面我们分析了Flutter第一次启动时的大致过程,WidgetsFlutterBinding是总工头,几乎App所有的事情都是通过它来管理,BuildOwner是构建树的负责人,它负责树的构建和更新,RenderObjectToWidgetAdapter是一个特殊的Widget,它是渲染树的根节点,也是构建树的起点,Widget只是负责与开发人员对接,它就是一个工具人,开发人员把数据放到这里,后面都是通过Element进行处理并生成RenderObject(有些Widget不一定会生成RenderObject)。

现在我们只是生成了一个渲染树,可是界面到底是如何渲染的呢,回到上面的attachToRenderTree方法,其中构件树生成后会调用SchedulerBinding.instance!.ensureVisualUpdate方法,这个方法最后会调用window.scheduleFrame,它会发送通知给Native:我这边树都生成好了,你在下一侦给我发个通知,我要开始渲染页面啦。

想看渲染过程,了解更多的flutter相关内容,可以扫描下方二维码,添加我的个人微信,好友可以免费获取一份一份来自阿里大佬Git高星的 Flutter 学习笔记。

!

2021年还没上手 Flutter 的移动开发们可以看看这份学习笔记了!

整理大纲

  • 为什么Flutter是跨平台开发的终极之选
  • 在Windows上搭建Flutter开发环境
  • 编写您的第一个 Flutter App
  • Flutter开发环境搭建和调试
  • Dart语法篇之基础语法(一)
  • Dart语法篇之集合的使用与源码解析(二)
  • Dart语法篇之集合操作符函数与源码分析(三)
  • Dart语法篇之函数的使用(四)
  • Dart语法篇之面向对象基础(五)
  • Dart语法篇之面向对象继承和Mixins(六)
  • Dart语法篇之类型系统与泛型(七)
  • Flutter中的widget

注:鉴于目前网上没有比较规范、系统的整理,该学习手册中的内容都是根据笔者的一个框架在网上进行的搜集整理。本文开源,仅用于技术交流分享,感谢大佬熊猫先生Dart语法部分的博客分享,感谢一同整理资料的小伙伴。大家可以动动小手,点波关注,了解更多flutter内容!

资料详情

为什么Flutter是跨平台开发的终极之选

  • 这是为什么?
  • 跨平台开发
  • 什么是 Flutter
  • Flutter 的特性
  • Flutter 构建应用的工具
  • 使用 Flutter 构建的热门应用
  • 构建 Flutter 应用的成本
  • ……

在Windows上搭建Flutter开发环境

  • 使用镜像
  • 系统要求
  • 获取Flutter SDK
  • 编辑器设置
  • Android设置
  • 起步: 配置编辑器
  • 起步: 体验
  • 体验热重载
  • 创建新的应用
  • 运行应用程序
  • ……

第三章 编写您的第一个 Flutter App

  • 第1步: 创建 Flutter app
  • 第2步: 使用外部包(package)
  • 第3步: 添加一个 有状态的部件(Stateful widget)
  • 第4步: 创建一个无限滚动ListView
  • 第5步: 添加交互
  • 第6步: 导航到新页面
  • 第7步:使用主题更改UI
  • ……

第四章 Flutter开发环境搭建和调试

  • 开发环境的搭建
  • 模拟器的安装与调试
  • 开发环境的搭建
  • 模拟器的安装与调试
  • ……

第五章 Dart语法篇之基础语法(一)

  • Hello Dart
  • 数据类型
  • 变量和常量
  • 集合(List、Set、Map)
  • 流程控制
  • 运算符
  • 异常
  • 函数
  • ……

第六章 Dart语法篇之集合的使用与源码解析(二)

  • List
  • Set
  • Map
  • Queue
  • LinkedList
  • HashMap
  • Map、HashMap、LinkedHashMap、SplayTreeMap区别
  • 命名构造函数from和of的区别以及使用建议
  • ……

第七章 Dart语法篇之集合操作符函数与源码分析(三)

  • Iterable(Iterable类关系图、Iterable类方法图……)
  • forEach(介绍、使用方式、源码解析……)
  • map(介绍、使用方式、源码解析……)
  • any(介绍、使用方式、源码解析……)
  • every(介绍、使用方式、源码解析……)
  • where(介绍、使用方式、源码解析……)
  • firstWhere和singleWhere和lastWhere(介绍、使用方式、源码解析……)
  • join(介绍、使用方式、源码解析……)
  • take(介绍、使用方式、源码解析……)
  • takeWhile(介绍、使用方式、源码解析……)
  • ……(内容太多)

第八章 Dart语法篇之函数的使用(四)

  • 函数参数
  • 匿名函数(闭包,lambda)
  • 箭头函数
  • 局部函数
  • 顶层函数和静态函数
  • main函数
  • ……

第九章 Dart语法篇之面向对象基础(五)

  • 属性访问器(accessor)函数setter和getter
  • 面向对象中的变量
  • 构造函数
  • 抽象方法、抽象类和接口
  • 类函数
  • ……

第十章 Dart语法篇之面向对象继承和Mixins(六)

  • 类的单继承
  • 基于Mixins的多继承
  • ……

第十一章 Dart语法篇之类型系统与泛型(七)

  • 可选类型
  • 接口类型
  • 泛型
  • 类型具体化
  • ……

第十二章 Flutter中的widget

  • Flutter页面-基础Widget
  • Widget
  • StatelessWidget
  • Stateful Widget
  • State生命周期
  • 基础widget
  • 文本显示
  • ……

这份笔记免费开源分享,有需要的朋友直接【一键三连】,然后扫描【下方二维码】找我领取。

最后

希望这份资料可以给想要了解 Flutter 并快速上手的朋友帮助以及一个参考方向。

千里之行始于足下,愿你我共勉。

以上是关于Flutter渲染完全解读系列:启动与三棵树的构建的主要内容,如果未能解决你的问题,请参考以下文章

Flutter渲染完全解读系列:启动与三棵树的构建

Flutter介绍和dart语法解读(上)

Flutter-渲染原理&三棵树详解

Flutter UI 渲染原理概览

Flutter UI 渲染原理概览

Flutter 的 runApp 与三棵树诞生流程源码分析