Flutter 核心原理与混合开发模式

原创 airingdeng [腾讯音乐技术团队]

在 「Fan 直播」的 Flutter 混合开发实践中,我们总结了一些 Flutter 混合开发的经验。本文第一篇章将从 Flutter 原理出发,详细介绍 Flutter 的绘制原理,借由此在第二章来对比三种跨端方案;之后再进入第三篇章 Flutter 混合开发模式的讲解,主要是四种不同的 Flutter 混合模式的原理分析;最后的第四篇章,简单分享一下混合工程的工程化探索。

“唯有深入,方能浅出”,对于一门技术,只有了解的深入,才能用最浅显、通俗的话语描述出。在此之前,我写过一些 Flutter 的文章,但性质更偏向于学习笔记与源码阅读笔记,因此较为晦涩,且零碎繁乱。本文作为阶段性的总结,我尽可能以浅显易懂的文字、循序渐进地来分享 Flutter 混合开发的知识,对于关键内容会辅以源码或源码中的关键函数来解读,但不会成段粘贴源码。源码学习的效果主要在于自身,所以若对源码学习感兴趣的,可以自行阅读 Framework 与 Engine 的源码,也可以阅读我过往的几篇文章。

好了,那废话不多说,直接开始吧!

1. Flutter 核心原理

1.1 Flutter 架构

Flutter架构

注:此图引自 Flutter System Overview

传统惯例,只要说到 Flutter 原理的文章,在开头都会摆上这张图。不论讲的好不好,都是先摆出来,然后大部分还是靠自行领悟。因为这张图实在太好用了。

摆出这张图,还是简单从整体上来先认识了一下什么是 Flutter,否则容易陷入“盲人摸象”的境地。

Flutter 架构采用分层设计,从下到上分为三层,依次为:Embedder、Engine、Framework。

  1. Embedder:操作系统适配层,实现渲染 Surface 设置、线程设置等。
  2. Engine:实现 FLutter 渲染引擎、文字排版、事件处理、Dart 运行时等功能。包括了 Skia 图形绘制库、Dart VM、Text 等,其中 Skia 和 Text 为上层接口提供了调用底层渲染和排版的能力。
  3. Framework:是一个用 Dart 实现的 UI SDK,从上之下包括了两大风格组件库、基础组件库、图形绘制、手势识别、动画等功能。

至于更多详情,这张图配合源码食用体验会更好。但由于本文不是源码解析,所以这个工作本文就不展开了。接下来,我会以 Flutter 绘制流程为例,来讲解 Flutter 是如何工作的。这也能更好地帮助你理解源码的思路。

1.2 Flutter 绘制原理

Flutter 绘制流程总结了一下大体上如下图所示:

flutter-render.png

首先是用户操作,触发 Widget Tree 的更新,然后构建 Element Tree,计算重绘区后将信息同步给 RenderObject Tree,之后实现组件布局、组件绘制、图层合成、引擎渲染。

作为前置知识,我们先来看看渲染过程中涉及到的数据结构,再来具体剖析渲染的各个具体环节。

1.3 Flutter 渲染过程中的数据结构

Data Model

渲染过程中涉及到的关键的数据结构包括三棵树和一个图层,其中 RenderObject 持有了 Layer,我们重点先看一下三棵树之间的关系。

举个栗子,比如有这么一个简单的布局:

布局

那么对应的三棵树之间的关系如下图所示:

三棵树
1.3.1 Widget Tree
Widget Tree

第一棵树,是 Widget Tree。它是控件实现的基本逻辑单位,是用户对界面 UI 的描述方式。

需要注意的是,Widget 是不可变的(immutable),当视图配置信息发生变化时,Flutter 会重建 Widget 来进行更新,以数据驱动 UI 的方式构建简单高效。

那为什么将 Widget Tree 设计为 immutable?Flutter 界面开发是一种响应式编程,主张“simple is fast”,而由上到下重新创建 Widget Tree 来进行刷新,这种思路比较简单,不用额外关系数据更变了会影响到哪些节点。另外,Widget 只是一个配置是数据结构,创建是轻量的,销毁也是做过优化的,不用担心整棵树重新构建带来的性能问题。

1.3.2 Element Tree
Element Tree

第二棵树,Element Tree。它是 Widget 的实例化对象(如下图,Widget 提供了 createElement 工厂方法来创建 Element),持久存在于运行时的 Dart 上下文之中。它承载了构建的上下文数据,是连接结构化的配置信息到最终完成渲染的桥梁。

之所以让它持久地存在于 Dart 上下文中而不是像 Widget 重新构建,因为 Element Tree 的重新创建和重新渲染的开销会非常大,所以 Element Tree 到 RenderObject Tree 也有一个 Diff 环节,来计算最小重绘区域。

@immutableabstract class Widget extends DiagnosticableTree {  /// Initializes [key] for subclasses.  const Widget({ this.key });  final Key key;    @protected  @factory  Element createElement();  /// ... 省略其他代码}

需要注意的是,Element 同时持有 Widget 和 RenderObject,但无论是 Widget 还是 Element,其实都不负责最后的渲染,它们只是“发号施令”,真正对配置信息进行渲染的是 RenderObject。

1.3.3 RenderObject Tree
RenderObject Tree

第三棵树,RenderObject Tree,即渲染对象树。RenderObject 由 Element 创建并关联到Element.renderObject 上(如下图),它接受 Element 的信息同步,同样的,它也是持久地存在 Dart Runtime 的上下文中,是主要负责实现视图渲染的对象。

RenderObject get renderObject {  RenderObject result;  void visit(Element element) {    assert(result == null); // this verifies that there's only one child    if (element is RenderObjectElement)      result = element.renderObject;    else      element.visitChildren(visit);  }  visit(this);  return result;}

RenderObject Tree 在 Flutter 的展示过程分为四个阶段:

  1. 布局
  2. 绘制
  3. 合成
  4. 渲染

其中,布局和绘制在 RenderObject 中完成,Flutter 采用深度优先机制遍历渲染对象树,确定树中各个对象的位置和尺寸,并把它们绘制到不同的图层上。绘制完毕后,合成和渲染的工作则交给 Skia 处理。

那么问题来了,为什么是三棵树而不是两棵?为什么需要中间的 Element Tree,由 Widget Tree 直接构建 RenderObject Tree 不可以吗?

理论上可以,但实际不可行。因为如果直接构建 RenderObject Tree 会极大地增加渲染带来的性能损耗。因为 Widget Tree 是不可变的,但 Element 却是可变的。实际上,Element 这一层将 Widget 树的变化做了抽象(类似 React / Vue 的 VDOM Diff),只将真正需要修改的部分同步到 RenderObject Tree 中,由此最大程度去降低重绘区域,提高渲染效率。可以发现,Flutter 的思想很大程度上是借鉴了前端响应式框架 React / Vue。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值