Flutter框架和原理剖析

2023-10-26

Flutter是Google推出并开源的跨平台开发框架,主打跨平台、高保真、高性能。开发者可以通过Dart语言开发Flutter应用,一套代码同时运行在ios和Android平台。不仅如此,flutter还支持web、桌面、嵌入应用的开发。flutter提供了丰富的组件、接口,开发者可以很快地为flutter添加native扩展。同时flutter还使用skia引擎(其实Android原生中页面绘制也是由skia引擎来渲染的)渲染视图,这无疑为用户提供良好地体验

Flutter框架地整体架构

在这里插入图片描述
简单来讲,Flutter 从上到下可以分为三层:框架层、引擎层和嵌入层。

框架层

Flutter Framework,即框架层。这是一个纯 Dart实现的 SDK,它实现了一套基础库,自底向上,我们来简单介绍一下:

  • 底下两层(Foundation 和 Animation、Painting、Gestures)在 Google 的一些视频中被合并为一个dart UI层,对应的是Flutter中的dart:ui包,它是 Flutter Engine 暴露的底层UI库,提供动画、手势及绘制能力。
  • Rendering 层,即渲染层,这一层是一个抽象的布局层,它依赖于 Dart UI 层,渲染层会构建一棵由可渲染对象组成的渲染树,当动态更新这些对象时,渲染树会找出变化的部分,然后更新渲染。渲染层可以说是Flutter 框架层中最核心的部分,它除了确定每个渲染对象的位置、大小之外还要进行坐标变换、绘制(调用底层 dart:ui )。
  • Widgets 层是 Flutter 提供的一套基础组件库,在基础组件库之上,Flutter 还提供了 Material 和 Cupertino 两种视觉风格的组件库,它们分别实现了 Material 和 iOS 设计规范。

引擎层

Engine,即引擎层。毫无疑问是 Flutter 的核心, 该层主要是 C++ 实现,其中包括了 Skia 引擎、Dart 运行时(Dart runtime)、文字排版引擎等。在代码调用 dart:ui库时,调用最终会走到引擎层,然后实现真正的绘制和显示。

嵌入层

Embedder,即嵌入层。Flutter 最终渲染、交互是要依赖其所在平台的操作系统 API,嵌入层主要是将 Flutter 引擎 ”安装“ 到特定平台上。嵌入层采用了当前平台的语言编写,例如 Android 使用的是 Java 和 C++, iOS 和 macOS 使用的是 Objective-C 和 Objective-C++,Windows 和 Linux 使用的是 C++。 Flutter 代码可以通过嵌入层,以模块方式集成到现有的应用中,也可以作为应用的主体。Flutter 本身包含了各个常见平台的嵌入层,假如以后 Flutter 要支持新的平台,则需要针对该新的平台编写一个嵌入层。

Flutter绘制原理

为了熟悉flutter地绘制原理,我们先从屏幕显示图像地基本原理说起:我们在买显示器时,都会关注显示器地刷新频率;那么对于手机屏幕也是一样,通常手机屏幕地刷新频率是60Hz,当然现在也有不少高刷新频率地手机也在推出,如:90Hz,120Hz。
在这里插入图片描述
一般来说,计算机系统中,CPU、GPU和显示器以一种特定地方式协作:CPU将计算好地显示内容提交给GPU,GPU渲染后放入帧缓冲区,然后视频控制器按照VSync信号从帧缓冲区取帧数据传递给显示器显示。当一帧图像绘制完毕后准备绘制下一帧时,显示器会发出一个垂直同步信号(VSync),所以60Hz的屏幕就会一秒内发出60次这样的信号。

上面是CPU、GPU和显示器协作方式,对于Flutter也不例外,Flutter也遵循了这种模式:
在这里插入图片描述
GPU的VSync信号同步给到UI线程,UI线程使用Dart来构建抽象的视图结构(这一步是在Framework层中实现的),绘制好的抽象视图结构会在GPU线程中进行图像的合成(这一步在引擎层中完成),然后提供给skia渲染成GPU所需要的数据,最终提供给GPU进行渲染。由此可知,flutter高性能的核心其实就在于根据vsync信号进行图像的快速构建,也就是上图中的绿色部分

Flutter渲染流程

在这里插入图片描述
在Flutter框架中存在着一个渲染流程。这个渲染流水线是由垂直同步信号驱动的,而Vsync信号由系统提供的,如果你的Flutter app是运行在Android上,那Vsync信号就是我们熟悉的Android的那个Vsync信号。

当Vsync信号到来了以后,Flutter框架会按照图里的顺序执行一系列动作:

  • 动画
  • 构建
  • 布局
  • 绘制

最终生成一个场景之后送往底层,由GPU绘制到屏幕上。

  • 动画阶段:因为动画会随着每个Vsync信号的到来而改变状态(State),所以动画阶段是流水线的第一个阶段;
  • 构建阶段:在这个阶段那些需要被重新构建的Widget会在此时被重新构建。也就是我们熟悉的build()方法被调用的时候。
  • 布局阶段:这时会确定各个显示元素的位置、尺寸;此时是RenderObject.performLayout()被调用的时候;
  • 绘制阶段:此时是RenderObject.paint()被调用的时候

Flutter组件的生命周期

在这里插入图片描述

  • createState():当框架要创建一个StatefulWidget时,它会立即调用State的createState()
  • initState():当State的构造方法被执行后,会调用一次initState(),需要指出的是initState在State生命周期中只会被调用一次
  • build():这个方法会被经常调用,比如:setState以及配置改变都会触发build方法的调用
  • didUpdateConfig():当收到一个新的config时调用
  • setState():当需要修改页面状态,比如刷新数据等的时候我们可以通过调用setState来实现
  • dispose():当移除State对象时,将调用dispose方法;通常在该方法中进行取消订阅,取消所有动画、流等操作。

Flutter渲染机制——为了高性能而生的三棵树

在 Flutter 中, widget 的功能是“描述一个UI元素的配置信息”,它就是说, Widget 其实并不是表示最终绘制在设备屏幕上的显示元素,所谓的配置信息就是 Widget 接收的参数,比如对于 Text 来讲,文本的内容、对齐方式、文本样式都是它的配置信息。

既然 Widget 只是描述一个UI元素的配置信息,那么真正的布局、绘制是由谁来完成的呢?Flutter 框架的处理流程是这样的:

  1. 根据 Widget 树生成一个 Element 树,Element 树中的节点都继承自 Element 类。
  2. 根据 Element 树生成 Render 树(渲染树),渲染树中的节点都继承自RenderObject 类。
    3。 根据渲染树生成 Layer 树,然后上屏显示,Layer 树中的节点都继承自 Layer 类。

真正的布局和渲染逻辑在 Render 树中,Element 是 Widget 和 RenderObject 的粘合剂,可以理解为一个中间代理。

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

接下来,我们重点看下Element这个东西,Element的生命周期如下:

  1. Framework 调用Widget.createElement 创建一个Element实例,记为element。
  2. Framework 调用 element.mount(parentElement,newSlot) ,mount方法中首先调用element所对应Widget的createRenderObject方法创建与element相关联的RenderObject对象,然后调用element.attachRenderObject方法将element.renderObject添加到渲染树中插槽指定的位置(这一步不是必须的,一般发生在Element树结构发生变化时才需要重新添加)。插入到渲染树后的element就处于“active”状态,处于“active”状态后就可以显示在屏幕上了(可以隐藏)。
  3. 当有父Widget的配置数据改变时,同时其State.build返回的Widget结构与之前不同,此时就需要重新构建对应的Element树。为了进行Element复用,在Element重新构建前会先尝试是否可以复用旧树上相同位置的element,element节点在更新前都会调用其对应Widget的canUpdate方法,如果返回true,则复用旧Element,旧的Element会使用新Widget配置数据更新,反之则会创建一个新的Element。Widget.canUpdate主要是判断newWidget与oldWidget的runtimeType和key是否同时相等,如果同时相等就返回true,否则就会返回false。根据这个原理,当我们需要强制更新一个Widget时,可以通过指定不同的Key来避免复用。
  4. 当有祖先Element决定要移除element 时(如Widget树结构发生了变化,导致element对应的Widget被移除),这时该祖先Element就会调用deactivateChild 方法来移除它,移除后element.renderObject也会被从渲染树中移除,然后Framework会调用element.deactivate 方法,这时element状态变为“inactive”状态。
  5. “inactive”态的element将不会再显示到屏幕。为了避免在一次动画执行过程中反复创建、移除某个特定element,“inactive”态的element在当前动画最后一帧结束前都会保留,如果在动画执行结束后它还未能重新变成“active”状态,Framework就会调用其unmount方法将其彻底移除,这时element的状态为defunct,它将永远不会再被插入到树中。
  6. 如果element要重新插入到Element树的其他位置,如element或element的祖先拥有一个GlobalKey(用于全局复用元素),那么Framework会先将element从现有位置移除,然后再调用其activate方法,并将其renderObject重新attach到渲染树。

现在,读者应该能够知道为什么笔者说:“三棵树是为了性能而生的”。因为,视图中的控件都是Render树中的对象,我们知道new出一个对象都是需要消耗操作系统很多资源的,通过三棵树就可以做到对已有render树中的对象进行复用,而不是每更改一个widget就new出一个新的render对象。

这里额外多提一嘴:我们在日常开发的过程中经常会接触到BuildContext这个东西。那这个东西究竟是什么呢?笔者就直接说答案了,有兴趣的同学可以看下源码。BuildContext就是widget对应的Element,所以我们可以通过context在StatelessWidget和StatefulWidget的build方法中直接访问Element对象。我们获取主题数据的代码Theme.of(context)内部正是调用了Element的dependOnInheritedWidgetOfExactType()方法。

三棵树的运用

我们可以看到Element是Flutter UI框架内部连接widget和RenderObject的纽带,大多数时候开发者只需要关注widget层即可,但是widget层有时候并不能完全屏蔽Element细节,所以Framework在StatelessWidget和StatefulWidget中通过build方法参数又将Element对象也传递给了开发者,这样一来,开发者便可以在需要时直接操作Element对象。
那么,现在笔者有个问题:如果没有widget层,单靠Element层是否可以搭建起一个可用的UI框架?如果可以应该是什么样子?

  • 案例如下:
class HomeView extends ComponentElement{
  HomeView(Widget widget) : super(widget);
  String text = "123456789";

  @override
  Widget build() {
    Color primary=Theme.of(this).primaryColor; //1
    return GestureDetector(
      child: Center(
        child: TextButton(
          child: Text(text, style: TextStyle(color: primary),),
          onPressed: () {
            var t = text.split("")..shuffle();
            text = t.join();
            markNeedsBuild(); //点击后将该Element标记为dirty,Element将会rebuild
          },
        ),
      ),
    );
  }
}
  1. 上面build方法不接收参数,这一点和在StatelessWidget和StatefulWidget中build(BuildContext)方法不同。代码中需要用到BuildContext的地方直接用this代替即可,如代码注释1处Theme.of(this)参数直接传this即可,因为当前对象本身就是Element实例。

  2. 当text发生改变时,我们调用markNeedsBuild()方法将当前Element标记为dirty即可,标记为dirty的Element会在下一帧中重建。实际上,State.setState()在内部也是调用的markNeedsBuild()方法。

  3. 上面代码中build方法返回的仍然是一个widget,这是由于Flutter框架中已经有了widget这一层,并且组件库都已经是以widget的形式提供了,如果在Flutter框架中所有组件都像示例的HomeView一样以Element形式提供,那么就可以用纯Element来构建UI了HomeView的build方法返回值类型就可以是Element了。

如果我们需要将上面代码在现有Flutter框架中跑起来,那么还是得提供一个“适配器”widget将HomeView结合到现有框架中,下面CustomHome就相当于“适配器”:

class CustomHome extends Widget {
  @override
  Element createElement() {
    return HomeView(this);
  }
}

后记

给大家推荐一本flutter相关的电子书籍:《Flutter实战·第二版》

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Flutter框架和原理剖析 的相关文章

随机推荐

  • 金融tag对照表

    tag 说明 格式 长度 值 描述 4F 应用标识符 AID b 注册应用提供商标识 RID 和专用标识符扩展 A000000333010101A000000333确定UICS注册应用提供商 所有的卡片都一样 010101表明UICS借记应
  • Axios post请求

    1 常见post请求种类 1 form表单提交 method post 是同步的 要素 页面是否刷新 2 axios post 异步操作 1 1axios post请求入门案例 1 1 1编辑前端JS h1 Axios测试案例 2 h1
  • MySQL——使用mysqldump命令备份

    使用mysqldump命令备份 mysqldump命令可以将数据库中的数据备份成一个文本文件 表的结构和表中的数据将存储在生成的文本文件中 本节将介绍mysqldump命令的工作原理和使用方法 mysqldump命令的工作原理很简单 它先查
  • java基础面试题系列(81-90)

    请你说明ConcurrentHashMap有什么优势 1 7和1 8有什么区别 参考链接 https www cnblogs com like minded p 6805301 html 请你说明一下TreeMap的底层结构 TreeMap
  • 第十篇 -- Windows 下免费的GIF录制工具

    网址 https blog csdn net u013019701 article details 80550411 本人用的第二个 亲测好用 转载于 https www cnblogs com smart zihan p 11461101
  • [CISCN2019 华北赛区 Day2 Web1]Hack World

    1 测试过滤 我想到到了 联合注入 unin被过滤 报错注入 and or updatexml被过滤 bool注入和time注入 and or被过滤 可以通过fuzz测试 模糊测试 发现哪些字符被过滤了 length为482的 全都是被过滤
  • LLVM编译流程

    LLVM概述 LLVM是构架编译器 compliter 的框架系统 以C 编写而成 用于优化以任意程序语言编写的程序的便是时间 compile time 链接时间 link time 运行时间 run time 以及空闲时间 idle ti
  • 网络编程--TCP/IP协议

    参考 https lijie blog csdn net article details 105297532 https blog csdn net qq 20785973 article details 83104695 https bl
  • 华为OD机试 - 分苹果(Python)

    题目描述 A B两个人把苹果分为两堆 A希望按照他的计算规则等分苹果 他的计算规则是按照二进制加法计算 并且不计算进位 12 5 9 1100 0101 9 B的计算规则是十进制加法 包括正常进位 B希望在满足A的情况下获取苹果重量最多 输
  • JJWT三种算法的工具类实现

    前言 最近学习jwt生成token 一直各种报错 不知道怎么生成对应的秘钥 周末研究了一下 把jjwt的HMAC RSA ECDSA三种签名算法方式都实现了 并记录下来 依赖版本如下
  • 波场链通过Tron JS SDK TronWeb发送带备注的TRC - 20 转账及使用简介

    波场链通过tronWeb发送带备注的TRC 20 转账 var contractAddress TRC 20 合约 选择合约 法 let functionSelector transfer address uint256 根据 法构造参数
  • 应对程序员面试,你必须知道的八大数据结构

    大数据文摘出品 编译 Hope 睡不着的iris 胡笳 云舟 瑞士计算机科学家Niklaus Wirth在1976年写了一本书 名为 算法 数据结构 编程 40多年后 这个等式仍被奉为真理 这就是为什么在面试过程中 需要考察软件工程师对数据
  • java在大量增强for循环中找到某个特定对象去分析的方法-推荐使用debug工具(idea为例)

    场景 代码中有一部分增强for循环 里面是很复杂的处理逻辑 并且处理的AObjectList列表数量非常大 在这串代码中想看一下某个name为 小白 的对象的处理过程 如果断点直接打在for循环体内 可能要重复百次甚至千次以上才能找到这个对
  • 毕业设计-基于深度学习的目标检测算法

    目录 前言 课题背景和意义 实现技术思路 一 两阶段深度学习算法 二 单阶段深度学习方法 实现效果图样例 最后 前言 大四是整个大学期间最忙碌的时光 一边要忙着备考或实习为毕业后面临的就业升学做准备 一边要为毕业设计耗费大量精力 近几年各个
  • 华为OD机试 - 最大平分数组( Python)

    题目描述 给定一个数组nums 可以将元素分为若干个组 使得每组和相等 求出满足条件的所有分组中 最大的平分组个数 输入描述 第一行输入 m 接着输入m个数 表示此数组 数据范围 1 lt M lt 50 1 lt nums i lt 50
  • 使用C++的libcurl库实现HTTP的POST请求

    简介 libcurl库是由C语言编写的轻量级网络库 可以实现客户端的一些基本功能 本文使用libcurl库实现了HTTP的POST请求 代码 C 代码 使用POST方式完成对以下两个API的访问 localhost 8050 api dat
  • .NET框架介绍

    NET平台 如图所示最上层VB C C JScript等为编程语言 这些语言的基础是最底层的操作系统以及com组件和services服务程序 net语言的核心由 公共语言进行时 CLR 和基础类库 Base Class Library 两部
  • 【超分辨率】(EDSR)Enhanced Deep Residual Networks for Single Image Super-Resolution论文阅读笔记

    论文名称 Enhanced Deep Residual Networks for Single Image Super Resolution 论文下载地址 https arxiv org pdf 1707 02921 pdf 论文代码地址
  • 7.6、LSM6DSL_SENSOR_HUB模式

    7 6 LSM6DSL SENSOR HUB模式 文章目录 7 6 LSM6DSL SENSOR HUB模式 7 6 1 简介 7 6 2 LSM6DSL SENSOR HUB模式 7 6 3 sensor hub直连模式 7 6 4 测试
  • Flutter框架和原理剖析

    Flutter是Google推出并开源的跨平台开发框架 主打跨平台 高保真 高性能 开发者可以通过Dart语言开发Flutter应用 一套代码同时运行在ios和Android平台 不仅如此 flutter还支持web 桌面 嵌入应用的开发