原创:有趣知识点摸索型文章
创作不易,请珍惜,之后会持续更新,不断完善
个人比较喜欢做笔记和写总结,毕竟好记性不如烂笔头哈哈,这些文章记录了我的IOS成长历程,希望能与大家一起进步
温馨提示:由于简书不支持目录跳转,大家可通过command + F 输入目录标题后迅速寻找到你所需要的内容
目录
- 一、架构层
- 二、响应式用户界面
- 三、Widgets
- 四、渲染和布局
- 五、Platform embedding
- 六、与其他代码进行集成
该文章旨在提供更深入的 Flutter
架构概览,包含其设计层面的核心原则及概念。
Flutter
是一个跨平台的 UI 工具集,它的设计初衷,就是允许在各种操作系统上复用同样的代码,例如 iOS 和 Android,同时让应用程序可以直接与底层平台服务进行交互。如此设计是为了让开发者能够在不同的平台上,都能交付拥有原生体验的高性能应用,尽可能地共享复用代码的同时,包容不同平台的差异。
在开发中,Flutter
应用会在一个 VM
(程序虚拟机)中运行,从而可以在保留状态且无需重新编译的情况下,热重载相关的更新。对于发行版 (release
) ,Flutter
应用程序会直接编译为机器代码(Intel x64
或 ARM
指令集),或者针对 Web
平台的 JavaScript
。Flutter
的框架代码是开源的,遵循 BSD 开源协议,并拥有蓬勃发展的第三方库生态来补充核心库功能。
概览分为以下几部分内容:
-
分层模型:
Flutter
的构成要素。 -
响应式用户界面:
Flutter
用户界面开发的核心概念。 -
widgets 介绍:构建
Flutter
用户界面的基石。 -
渲染过程:
Flutter
如何将界面布局转化为像素。 -
平台嵌入层的概览:让
Flutter
应用可以在移动端及桌面端操作系统执行的代码。 -
将 Flutter 与其他代码进行集成:
Flutter
应用可用的各项技术的更多信息。 -
Web 支持:
Flutter
在浏览器环境中的特性的概述。
一、架构层
Flutter
被设计为一个可扩展的分层系统。它可以被看作是各个独立的组件的系列合集,上层组件各自依赖下层组件。组件无法越权访问更底层的内容,并且框架层中的各个部分都是可选且可替代的。
对于底层操作系统而言,Flutter
应用程序的包装方式与其他原生应用相同。在每一个平台上,会包含一个特定的嵌入层,从而提供一个程序入口,程序由此可以与底层操作系统进行协调,访问诸如 surface
渲染、辅助功能和输入等服务,并且管理事件循环队列。该嵌入层采用了适合当前平台的语言编写,例如 Android
使用的是 Java
和 C++
, iOS 和 macOS 使用的是 Objective-C
和 Objective-C++
,Windows
和 Linux
使用的是 C++
。 Flutter
代码可以通过嵌入层,以模块方式集成到现有的应用中,也可以作为应用的主体。 Flutter 本身包含了各个常见平台的嵌入层。
Flutter
引擎毫无疑问是 Flutter
的核心,它主要使用 C++
编写。当需要绘制新一帧的内容时,引擎将负责对需要合成的场景进行栅格化。它提供了 Flutter
核心 API 的底层实现,包括图形、文本布局、文件及网络 IO、辅助功能支持、插件架构和 Dart
运行环境及编译环境的工具链。
引擎将底层 C++
代码包装成 Dart
代码,通过 dart:ui
暴露给 Flutter
框架层。通常,开发者可以通过 Flutter 框架层 与 Flutter
交互,该框架提供了以 Dart
语言编写的现代响应式框架。从下层到上层,依次有:
- 基础的
foundational
类及一些基层之上的构建服务,如animation
、painting
和gestures
。 - 有了渲染层,你可以构建一棵可渲染对象的树。在你动态更新这些对象时,渲染树也会自动根据你的变更来更新布局。
- 每一个渲染层中的渲染对象,都在
widgets
层中有一个对应的类。此外,widgets
层让你可以自由组合你需要复用的各种类。响应式编程模型就在该层级中被引入。 -
Material
和Cupertino
库提供了全面的widgets
层的组合。
Flutter
框架相对较小,因为一些开发者可能会使用到的更高层级的功能已经被拆分到不同的软件包中,其中包括平台插件,例如 camera
和 webview
;与平台无关的功能,例如 characters
、 http
和 animations
。还有一些软件包来自于更为宽泛的生态系统中,例如 应用内支付、 Apple
认证 和Lottie
动画。
该概览的其余部分将从 UI 开发的响应式范例开始,浏览各个构建层。而后,我们会讲述 widgets
如何被组织,并转换成应用程序的渲染对象。同时我们也会讲述 Flutter
如何在平台层面与其他代码进行交互,最终,我们会对目前 Flutter
对于 Web
平台的支持与其他平台的异同做一个总结。
二、响应式用户界面
粗略一看,Flutter
是 一个响应式的且伪声明式的 UI 框架,开发者负责提供应用状态与界面状态之间的映射,框架则在运行时将应用状态的更改更新到界面上。这样的模型架构的灵感来自 Facebook 自己的 React
框架 ,其中包含了对传统设计理念的再度解构。 在大部分传统的 UI 框架中,界面的初始状态通常会被一次性定义,然后,在运行时根据用户代码分别响应事件进行更新。
在这里有一项大挑战,即随着应用程序的复杂性日益增长,开发者需要对整个 UI 的状态关联有整体的认知。在用户与 UI 进行交互时,状态的改变可能会影响到每一个位置。更糟糕的是,UI 的细微变动很有可能会引发无关代码的连锁反应,尤其是当开发者并未注意其关联的时候。我们可以通过类似 MVC
的方式进行处理,开发者将数据的改动通过控制器(Controller
)推至模型(Model
),模型再将新的状态通过控制器推至界面(View
)。但这样的处理方式仍然存在问题,因为创建和更新 UI 元素的操作被分离开了,容易造成它们的不同步。
Flutter
与其他响应式框架类似,采用了显式剥离基础状态和用户界面的方式,来解决这一问题。在 Flutter
里,widgets
(类似于 React
中的组件)是用来配置对象树的类。这些 widgets
会管理单独的布局对象树,接着参与管理合成的布局对象树。Flutter
的核心就是一套高效的遍历树的变动的机制,它会将对象树转换为更底层的对象树,并在树与树之间传递更改。
build()
是将状态转化为 UI 的方法,widget
通过重写该方法来声明 UI 的构造:
UI = f(state)
build()
方法在框架需要时都可以被调用(每个渲染帧可能会调用一次),从设计角度来看,它应当能够快速执行且没有额外影响的。这样的实现设计依赖于语言的运行时特征(特别是对象的快速实例化和清除)。幸运的是,Dart
非常适合这份工作。
三、Widgets
如前所述,Flutter
强调以 widgets
作为组成单位。 Widgets
是构建 Flutter
应用界面的基础,每个 widget
都是一部分 UI 声明。
Widgets
通过布局组合形成一种层次结构关系。每个 Widget
都嵌套在其父级的内部,并可以通过父级接收上下文。从根布局(托管 Flutter
应用的容器,通常是 MaterialApp
或 CupertinoApp
)开始,自上而下都是这样的结构,如下面的示例所示:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('My Home Page'),
),
body: Center(
child: Builder(
builder: (context) {
return Column(
children: [
const Text('Hello World'),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
print('Click!');
},
child: const Text('A button'),
),
],
);
},
),
),
),
);
}
}
在上面的代码中,所有实例化的类都是 widgets
。应用会根据事件交互(例如用户操作),通知框架替换层级中的旧 widget
为新 widget
,然后框架会比较新旧 widgets
,高效地更新用户界面。
Flutter 拥有自己的 UI 控件实现。例如, iOS 的 Switch
控件 和 Android
的选择控件均有一个纯 Dart 实现。这样的实现有几个优势:
- 提供了无限的扩展性。当开发者想要一个
Switch
的改装时,他们可以以任意方式创建一个,而不被系统提供的控件所限制。 -
Flutter
可以直接合成所有的场景,而无需在Flutter
与原生平台之间来回切换,从而避免了明显的性能瓶颈。 - 在任意一种系统平台上体验应用,都将是一致的,就算某个系统更改了其控件的实现,也是如此。
1、组成
Widgets
通常由更小的且用途单一的 widgets
组合而成。在设计时,相关的设计概念已尽可能地少量存在,而通过大量的内容进行填充。举个例子,Flutter
在 widgets
层中使用了相同的概念(一个 Widget
)来表示屏幕上的绘制、布局(位置和大小)、用户交互、状态管理、主题、动画及导航。在动画层,Animation
和 Tween
这对概念组合,涵盖了大部分的设计空间。在渲染层,RenderObject
用来描述布局、绘制、触摸判断及可访问性。在这些场景中,最终对应包含的内容都很多:有数百个 widgets
和 render objects
,以及数十种动画和补间类型。
类的层次结构是有意的浅而广,以最大限度地增加可能的组合数量,重点放在小的、可组合的 widget
上,确保每个 widget
都能很好地完成一件事情。核心功能均被抽象,甚至像边距和对齐这样的基础功能,都被实现为单独的组件,而不是内置于核心中。这样的实现也与传统的 API 形成了对比,类似边距这样的功能通常都内置在了每个组件的公共核心内, Flutter
中的 widget
则不同。因此,如果你需要将一个 widget
居中,与其调整 Align
这样的属性,不如将它包裹在一个 Center widget
内。
Flutter
中包含了边距、对齐、行、列和网格系列的 widgets
。这些布局类型的 widgets
自身没有视觉内容,而只用于控制其他 widgets
的部分布局条件。
Flutter
也包含了以这种组合方法组成的实用型 widgets
。例如,一个常用的 widget Container
,是由几个 widget
组合而成,包含了布局、绘制、定位和大小的功能。更具体地说,Container
是由 LimitedBox
、 ConstrainedBox
、Align
、 Padding
、DecoratedBox
和Transform
组成的,你也可以通过查看源码看到这些组合。 Flutter
有一个典型的特征,即你可以深入到任意一个 widget
,查看其源码。因此,你可以通过同样的方式组合其他的 widgets
,也可以参考 Container
来创建其他的 widget
,而不需要继承 Container
来实现自定义的效果。
2、构建 widgets
先前提到,你可以通过重写 build()
方法,返回一个新的元素树,来定义视觉展示。例如,工具栏widget
的 build
方法可能会返回 水平布局,其中可能包含一些文字, 各种各样的按钮。根据需要,框架会递归请求每个 widget
进行构建,直到整棵树都被具体的可渲染对象描述为止。然后,框架会将可渲染的对象缝合在一起,组成可渲染对象树。
每个渲染帧,Flutter
都可以根据变化的状态,调用 build()
方法重建部分 UI。因此,保证 build
方法轻量且能快速返回 widget
是非常关键的,繁重的计算工作应该通过一些异步方法完成,并存储在状态中,在 build
方法中使用。
3、Widget 的状态
框架包含两种核心的 widget
类:有状态的和无状态的widget
。大部分 widget
都没有需要变更的状态:它们并不包含随时变化的属性(例如图标或者标签)。这些 widget
会继承StatelessWidget
。
然而,当widget
拥有需要根据用户交互或其他因素而变化的特有属性,它就是有状态的。例如,计数器 widget
在用户点击按钮时数字递增,那么计数值就是计数器 widget
的状态。当值变化时,widget
则需要被重建以更新相关部分的 UI。这些 widget
会继承 StatefulWidget
,并且「可变的」状态会保存在继承 State
的另一个子类中(因为 widget
本身是不可变的)。StatefulWidget
自身没有 build
方法,而在其对应的 State
对象中。每当你更改 State
对象时(例如计数增加),你需要调用 setState()
来告知框架,再次调用 State
的构建方法来更新 UI。
将状态和 widget
对象分离,可以使其他 widget
无差异地看待无状态和有状态 widget
,而不必担心丢失状态。父级无需担心状态的丢失,可以随时创建新的实例,并不需要通过子级关系保持其状态。框架也会在合适的时间,复用已存在的状态对象。
4、状态管理
那么,在众多 widget
都持有状态的情况下,系统中的状态是如何被传递和管理的呢?与其他类相同,你可以通过 widget
的构造函数来初始化数据,如此一来 build()
方法可以确保子 widget
使用其所需的数据进行实例化。
@override
Widget build(BuildContext context) {
return ContentWidget(importantState);
}
然而,随着 widget
树层级逐渐加深,依赖树形结构上下传递状态信息会变得十分麻烦。这时,第三种类型的 widget
—— InheritedWidget
,提供了一种从共同的祖先节点获取数据的简易方法。你可以使用 InheritedWidget
创建包含状态的 widget
,该 widget
会将一个共同的祖先节点包裹在 widget
树中,如下面的例子所示:
现在,当 ExamWidget
或 GradeWidget
对象需要获取 StudentState
的数据时,可以直接使用以下方式:
final studentState = StudentState.of(context);
调用of(context)
会根据当前构建的上下文(即当前 widget
位置的句柄),并返回类型为 StudentState
的在树中距离最近的祖先节点。 InheritedWidget
同时也包含了 updateShouldNotify()
方法, Flutter
会调用它来判断依赖了某个状态的 widget
是否需要重建。 InheritedWidget
在Flutter
框架中被大量用于共享状态,例如应用的视觉主题,包含了应用于整个应用的 颜色和字体样式等属性。MaterialApp
的 build()
方法会在构建时在树中插入一个主题,更深层级的 widget
便可以使用.of()
方法来查找相关的主题数据,例如:
Container(
color: Theme.of(context).secondaryHeaderColor,
child: Text(
'Text with a background color',
style: Theme.of(context).textTheme.titleLarge,
),
);
类似地,以该方法实现的还有提供了页面路由的 Navigator
、提供了屏幕信息指标,包括方向、尺寸和亮度的 MediaQuery
等。 随着应用程序的不断迭代,更高级的状态管理方法变得更有吸引力,它们可以减少有状态的 widget
的创建。许多 Flutter
应用使用了 provider
用于状态管理,它对 InheritedWidget
进行了进一步的包装。Flutter
的分层架构也允许使用其他实现来替换状态至 UI 的方案,例如 flutter_hooks
。
四、渲染和布局
本节介绍 Flutter
的渲染机制,包括将 widget 层级结构转换成屏幕上绘制的实际像素的一系列步骤。
1、Flutter 的渲染模型
你可能思考过:既然 Flutter
是一个跨平台的框架,那么它如何提供与原生平台框架相当的性能?让我们从安卓原生应用的角度开始思考。当你在编写绘制的内容时,你需要调用 Android
框架的 Java
代码。 Android
的系统库提供了可以将自身绘制到 Canvas
对象的组件,接下来 Android
就可以使用由 C/C++
编写的 Skia
图像引擎,调用 CPU 和 GPU 完成在设备上的绘制。
通常来说,跨平台框架都会在 Android 和 iOS 的 UI 底层库上创建一层抽象,该抽象层尝试抹平各个系统之间的差异。这时,应用程序的代码常常使用 JavaScript
等解释型语言来进行编写,这些代码会与基于 Java
的 Android 和基于 Objective-C
的 iOS 系统进行交互,最终显示 UI 界面。所有的流程都增加了显著的开销,在 UI 和应用逻辑有繁杂的交互时更为如此。
相比之下,Flutter
通过绕过系统 UI 组件库,使用自己的 widget
内容集,削减了抽象层的开销。用于绘制 Flutter
图像内容的 Dart
代码被编译为机器码,并使用 Skia
进行渲染。
2、从用户操作到 GPU
对于 Flutter
的渲染机制而言,首要原则是简单快速。 Flutter
为数据流向系统提供了直通的管道,如以下的流程图所示:
接下来,让我们更加深入了解其中的一些阶段。
3、构建:从 Widget 到 Element
首先观察以下的代码片段,它代表了一个简单的 widget
层次结构:
Container(
color: Colors.blue,
child: Row(
children: [
Image.network('https://www.example.com/1.png'),
const Text('A'),
],
),
);
当 Flutter
需要绘制这段代码时,框架会调用 build()
方法,返回一棵基于当前应用状态来绘制 UI 的 widget
子树。在这个过程中,build()
方法可能会在必要时,根据状态引入新的 widget
。在上面的例子中,Container
的 color
和 child
就是典型的例子。我们可以查看 Container
的源代码,你会看到当 color
属性不为空时,ColoredBox
会被加入用于颜色布局。
if (color != null)
current = ColoredBox(color: color!, child: current);
与之对应的,Image
和 Text
在构建过程中也会引入 RawImage
和 RichText
。如此一来,最终生成的 widget
结构比代码表示的层级更深,在该场景中如下图:
这就是为什么你在使用 Dart DevTools
的 Flutter inspector
调试 widget
树结构时,会发现实际的结构比你原本代码中的结构层级更深。
在构建的阶段,Flutter
会将代码中描述的 widgets
转换成对应的 Element
树,每一个 Widget
都有一个对应的 Element
。每一个Element
代表了树状层级结构中特定位置的 widget
实例。目前有两种 Element
的基本类型:
-
ComponentElement:其他
Element
的宿主。 -
RenderObjectElement:参与布局或绘制阶段的
Element
。
RenderObjectElement
是底层 RenderObject
与对应的 widget
之间的桥梁。任何 widget
都可以通过其 BuildContext
引用到 Element
,它是该 widget
在树中的位置的句柄。类似 Theme.of(context)
方法调用中的 context
,它作为 build()
方法的参数被传递。
由于 widgets
以及它上下节点的关系都是不可变的,因此,对 widget
树做的任何操作(例如将 Text('A')
替换成 Text('B')
)都会返回一个新的 widget
对象集合。但这并不意味着底层呈现的内容必须要重新构建。Element
树每一帧之间都是持久化的,因此起着至关重要的性能作用, Flutter
依靠该优势,实现了一种好似 widget
树被完全抛弃,而缓存了底层表示的机制。 Flutter
可以根据发生变化的 widget
,来重建需要重新配置的 Element
树的部分。
4、布局和渲染
很少有应用只绘制单个 widget
。因此,有效地排布 widget
的结构及在渲染完成前决定每个 Element
的大小和位置,是所有 UI 框架的重点之一。
在渲染树中,每个节点的基类都是 RenderObject
,该基类为布局和绘制定义了一个抽象模型。每一个 RenderObject
都了解其父节点的信息,但对于其子节点,除了如何访问和获得他们的布局约束,并没有更多的信息。
在构建阶段,Flutter
会为 Element
树中的每个 RenderObjectElement
创建或更新其对应的一个从 RenderObject
继承的对象。RenderObject
实际上是原语:渲染文字的 RenderParagraph
、渲染图片的 RenderImage
以及在绘制子节点内容前应用变换的 RenderTransform
是更为上层的实现。
大部分的 Flutter widget
是由一个继承了 RenderBox
的子类的对象渲染的,它们呈现出的 RenderObject
会在二维笛卡尔空间中拥有固定的大小。 RenderBox
提供了 盒子限制模型,为每个 widget
关联了渲染的最小和最大的宽度和高度。
在进行布局的时候,Flutter
会以 DFS
(深度优先遍历)方式遍历渲染树,并将限制以自上而下的方式从父节点传递给子节点。子节点若要确定自己的大小,则必须遵循父节点传递的限制。子节点的响应方式是在父节点建立的约束内将大小以自下而上的方式传递给父节点。
在遍历完一次树后,每个对象都通过父级约束而拥有了明确的大小,随时可以通过调用paint()
进行渲染。盒子限制模型十分强大,它的对象布局的时间复杂度是 O(n):
- 父节点可以通过设定最大和最小的尺寸限制,决定其子节点对象的大小。例如,在一个手机应用中,最高层级的渲染对象将会限制其子节点的大小为屏幕的尺寸。(子节点可以选择如何占用空间。例如,它们可能在设定的限制中以居中的方式布局。)
- 父节点可以决定子节点的宽度,而让子节点灵活地自适应布局高度(或决定高度而自适应宽度)。现实中有一种例子就是流式布局的文本,它们常常会填充横向限制,再根据文字内容的多少决定高度。
这样的盒子约束模型,同样也适用于子节点对象需要知道有多少可用空间渲染其内容的场景,通过使用 LayoutBuilder widget
,子节点可以得到从上层传递下来的约束,并合理利用该约束对象,使用方法如下:
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth < 600) {
return const OneColumnLayout();
} else {
return const TwoColumnLayout();
}
},
);
}
所有 RenderObject
的根节点是 RenderView
,代表了渲染树的总体输出。当平台需要渲染新的一帧内容时(例如一个 vsync
信号或者一个纹理的更新完成),会调用一次 compositeFrame()
方法,它是 RenderView
的一部分。该方法会创建一个 SceneBuilder
来触发当前画面的更新。当画面更新完毕,RenderView
会将合成的画面传递给 dart:ui
中的 Window.render()
方法,控制 GPU 进行渲染。
五、Platform embedding
我们都知道,Flutter
的界面构建、布局、合成和绘制全都由 Flutter
自己完成,而不是转换为对应平台系统的原生组件。获取纹理和联动应用底层的生命周期的方法,不可避免地会根据平台特性而改变。 Flutter
引擎本身是与平台无关的,它提供了一个稳定的 ABI(应用二进制接口),包含一个 平台嵌入层,可以通过其方法设置并使用 Flutter
。
平台嵌入层是用于呈现所有 Flutter
内容的原生系统应用,它充当着宿主操作系统和 Flutter
之间的粘合剂的角色。当你启动一个 Flutter
应用时,嵌入层会提供一个入口,初始化 Flutter
引擎,获取 UI 和栅格化线程,创建 Flutter
可以写入的纹理。嵌入层同时负责管理应用的生命周期,包括输入的操作(例如鼠标、键盘和触控)、窗口大小的变化、线程管理和平台消息的传递。 Flutter
拥有 Android、iOS、Windows、macOS 和 Linux 的平台嵌入层,当然,开发者可以创建自定义的嵌入层。
每一个平台都有各自的一套 API 和限制。以下是一些关于平台简短的说明:
在 iOS 和 macOS 上, Flutter 分别通过 UIViewController
和 NSViewController
载入到嵌入层。这些嵌入层会创建一个 FlutterEngine
,作为 Dart VM
和您的 Flutter
运行时的宿主,还有一个 FlutterViewController
,关联对应的 FlutterEngine
,传递 UIKit 或者 Cocoa 的输入事件到 Flutter
,并将 FlutterEngine
渲染的帧内容通过 Metal
或 OpenGL
进行展示。
在 Android 上,Flutter
默认作为一个 Activity
加载到嵌入层中。此时视图是通过一个 FlutterView
进行控制的,基于 Flutter
内容的合成和 z 排列 (z-ordering
) 的要求,将 Flutter
的内容以视图模式或纹理模式进行呈现。
六、与其他代码进行集成
1、平台通道
对于移动端和桌面端应用而言,Flutter
提供了通过 平台通道 调用自定义代码的能力,这是一种非常简单的在宿主应用之间让Dart
代码与平台代码通信的机制。通过创建一个常用的通道(封装通道名称和编码),开发者可以在Dart
与使用Kotlin
和Swift
等语言编写的平台组件之间发送和接收消息。数据会由Dart
类型(例如 Map
)序列化为一种标准格式,然后反序列化为 Kotlin
(例如 HashMap
)或者 Swift
(例如 Dictionary
)中的等效类型。
下方的示例是在 Kotlin (Android)
或 Swift (iOS)
中处理 Dart
调用平台通道事件的简单接收处理:
// Dart side
const channel = MethodChannel('foo');
final String greeting = await channel.invokeMethod('bar', 'world');
print(greeting);
// Android (Kotlin)
val channel = MethodChannel(flutterView, "foo")
channel.setMethodCallHandler { call, result ->
when (call.method) {
"bar" -> result.success("Hello, ${call.arguments}")
else -> result.notImplemented()
}
}
// iOS (Swift)
let channel = FlutterMethodChannel(name: "foo", binaryMessenger: flutterView)
channel.setMethodCallHandler {
(call: FlutterMethodCall, result: FlutterResult) -> Void in
switch (call.method) {
case "bar": result("Hello, \(call.arguments as! String)")
default: result(FlutterMethodNotImplemented)
}
}
2、在 Flutter 应用中渲染原生内容
Flutter
通过引入了平台 widget
(AndroidView
和 UiKitView
) 解决了这个问题,开发者可以在每一种平台上嵌入此类内容。平台视图可以与其他的 Flutter
内容集成。这些 widget
充当了底层操作系统与 Flutter
之间的桥梁。例如在 Android
上,AndroidView
主要提供了三项功能:
- 拷贝原生视图渲染的图形纹理,在
Flutter
每帧渲染时提交给Flutter
渲染层进行合成。 - 响应命中测试和输入手势,将其转换为等效的原生输入事件。
- 创建类似的可访问性树,并在原生层与
Flutter
层之间传递命令和响应。
但不可避免的是,这样的同步操作必然会带来相应的开销。因此该方法通常更适合复杂的控件,例如谷歌地图这种不适合在 Flutter
中重新实现的。 通常 Flutter
应用会在 build()
方法中基于平台判断来实例化这些 widget
。例如在 google_maps_flutter
插件中:
if (defaultTargetPlatform == TargetPlatform.android) {
return AndroidView(
viewType: 'plugins.flutter.io/google_maps',
onPlatformViewCreated: onPlatformViewCreated,
gestureRecognizers: gestureRecognizers,
creationParams: creationParams,
creationParamsCodec: const StandardMessageCodec(),
);
} else if (defaultTargetPlatform == TargetPlatform.iOS) {
return UiKitView(
viewType: 'plugins.flutter.io/google_maps',
onPlatformViewCreated: onPlatformViewCreated,
gestureRecognizers: gestureRecognizers,
creationParams: creationParams,
creationParamsCodec: const StandardMessageCodec(),
);
}
return Text(
'$defaultTargetPlatform is not yet supported by the maps plugin');
如上文所述,AndroidView
和 UiKitView
通常是利用平台通道的机制与原生进行通信。
3、在上层应用中托管 Flutter 内容
与上一个场景相反的是,将Flutter widget
集成至现有的Android
或iOS
应用中。先前提到,新创建的 Flutter
应用,在移动设备上是在一个 Android
的 Activity
或 iOS 的 UIViewController
中运行。开发者可以使用相同的嵌入 API 将 Flutter
内容集成至现有的 Android
或 iOS 应用中。
Flutter
模块模板设计简单,易于嵌入。开发者可以将其作为源代码依赖项集成到Gradle
或Xcode
构建定义中,或者将其打包成 Android Archive (AAR)
或iOS Framework
二进制供其他开发者使用,而无需安装 Flutter
。
Flutter
引擎需要一段短暂的时间做初始化,用于加载 Flutter
的共享库、初始化 Dart
的运行时、创建并运行 Dart isolate
线程并将渲染层与 UI 进行绑定。为了最大限度地减少呈现 Flutter
界面时的延迟,最好是在应用初始化时或至少在第一个 Flutter
页面展示前,一并初始化 Flutter
引擎,如此一来用户不会在首个 Flutter
页面加载时感到突然地卡顿。另外,Flutter
的引擎分离使得多个 Flutter
页面可以复用引擎,共享必要库加载时的内存消耗。
4、Flutter 对 Web 的支持
虽然 Flutter
支持的所有平台的都适用于同一个架构概念,但是在 Web
平台的支持上有一些独特的特征值得说明。Dart
语言存在之初就已经支持直接编译成 JavaScript
。由于Flutter
框架是Dart
编写的,将其编译成 JavaScript
相对而言更为简单。
然而,使用 C++ 编写的 Flutter
引擎是为了与底层操作系统进行交互的,而不是 Web
浏览器。因此我们需要另辟蹊径。Flutter
在 Web
平台上以浏览器的标准 API 重新实现了引擎。目前我们有两种在 Web
上呈现内容的选项:HTML
和 WebGL
。在HTML
模式下,Flutter
使用 HTML
、CSS
、Canvas
和 SVG
进行渲染。而在 WebGL
模式下,Flutter
使用了一个编译为 WebAssembly
的 Skia
版本,名为 CanvasKit
。 HTML
模式提供了最佳的代码大小,CanvasKit
则提供了浏览器图形堆栈渲染的最快途径,并为原生平台的内容提供了更高的图形保真度。
Web
版本的分层架构图如下所示:
与其他运行Flutter
的平台相比,最明显的区别也许是Flutter
不再需要提供Dart
的运行时。取而代之的是Flutter
框架本身(和你写的代码)一并编译成JavaScript
。另外值得注意的是,Dart
在不同模式下(JIT
和 AOT
、平台原生和 Web
编译)的语义几乎没有差异,大部分开发者绝对可以无差异地编写这两种模式下的代码。
在进行开发时,Web
版本的 Flutter
使用支持增量编译的编译器 dartdevc
进行编译,以支持应用热重启(尽管目前尚未支持热重载)。相反,当你准备好创建一个生产环境的 Web
应用时,Dart
深度优化的编译器 dart2js
将会用于编译,将 Flutter
核心框架和你的应用打包至缩小的源文件中,可部署在任何服务器上。代码可以在单个文件中提供,也可拆分至多个文件以 延迟加载库 提供。