Flutter架构概览

本文详细介绍了Flutter的架构,包括其分层模型、Widget构建、渲染过程、状态管理机制以及与平台的交互。重点讲解了Widgets在构建UI中的作用,以及如何通过InheritedWidget和Provider进行状态共享和高级状态管理。
摘要由CSDN通过智能技术生成
  • Widgets

    • 组成
  • 构建widgets

  • 状态管理

  • 渲染和布局

    • Flutter的渲染模型
  • 从用户操作到GPU

  • 构建:从Widget到Element

  • 布局和渲染

  • Platform embedding

前言

=================================================================

本文总结Flutter架构概览,包含其设计层面的核心原则以及概念。

Flutter是一个跨平台的UI工具集,它允许在各种操作系统上复用相同的代码,同时应用程序直接与底层平台交互,避免了不同平台视图的差异,同时也让开发者能够在不同平台上都能交付拥有原生体验的高性能应用。

开发阶段,FLutter应用会在一个VM(程序虚拟机)中运行,从而可以保留状态且无需重新编译的情况下,热重载相关的更新。对于发行版(release),Flutter程序会直接编译错机器码,或者针对Web平台的JavaScript。

概览分为以下几个部分:

  1. 分层模型:Flutter的构成要素

  2. 响应式用户界面:Flutter用户界面开发的核心概念

  3. widgets介绍:构建Flutter用户界面的基石

  4. 渲染过程:Flutter如何将界面布局转化为像素

  5. 平台嵌入层的概念:让Flutter应用可以再移动端以及桌面端操作系统执行的代码

架构层

==================================================================

Flutter被设计为一个可扩展的分层系统。它可以被看做是各个独立的组件系列合集,上层的组件各自依赖下层的组件。组件无法越权访问底层的内容,并且框架层的各个部分都是可选且可替代。

对于底层操作系统而言,Flutter应用程序的包装方式与其他原生应用相同。在每一个平台上,都回去包含一个特定的嵌入层,从而提供一个程序入口,程序由此可以与底层操作系统进行协调,访问诸如Surface渲染,辅助功能和输入等等服务,并且管理时间循环队列。该嵌入层采用了适合当前平台语言编写,例如Android使用的是Java/C++,IOS和MacOSSierra使用的是OC和OC++,Windows和Linux使用的是C++,Flutter代码可以通过嵌入层,以模块方式集成到现有的应用中,也可以作为应用的主体。Flutter本身包含了各个常见平台的嵌入层,同时也存在一些其他的嵌入层。

Flutter引擎毫无疑问是Flutter的核心,它主要是C++编写,并提供了Flutter应用所需要的原语。当需要绘制新的一帧的内容时,引擎将负责对需要合成的场景进行栅格化。它提供了Flutter核心API的底层实现,包括图形(通过Skia)、文本布局、文件以及网络IO、辅助功能支持、插件架构和Dart运行环境以及编译环境的工具链。

引擎将C++ 代码包装成Dart代码,通过dart:ui暴露给Flutter框架层。该库暴露了最底层的原语,包括用于驱动图形输入、图形、和文本渲染的子系统的类。

通常,开发者可以通过Flutter Framework与Flutter进行交互,该Framework提供了以Dart语音编写的现代响应式框架。它包括由一系列层组成的一组丰富的平台,布局和基础库。从下层到上层,依次有:

  • 基础的 foundational 类及一些基层之上的构建块服务,如 animationpaintinggestures,它们可以提供上层常用的抽象。

  • 渲染层 用于提供操作布局的抽象。有了渲染层,你可以构建一棵可渲染对象的树。在你动态更新这些对象时,渲染树也会自动根据你的变更来更新布局。

  • widget 层 是一种组合的抽象。每一个渲染层中的渲染对象,都在 widgets 层中有一个对应的类。此外,widgets 层让你可以自由组合你需要复用的各种类。响应式编程模型就在该层级中被引入。

  • MaterialCupertino 库提供了全面的 widgets 层的原语组合,这套组合分别实现了 Material 和 iOS 设计规范。

Flutter 框架相对较小,因为一些开发者可能会使用到的更高层级的功能已经被拆分到不同的软件包中,使用 Dart 和 Flutter 的核心库实现,其中包括平台插件,例如 camerawebview;与平台无关的功能,例如 charactershttpanimations。还有一些软件包来自于更为宽泛的生态系统中,例如 应用内支付Apple 认证Lottie 动画

该概览的其余部分将从 UI 开发的响应式范例开始,浏览各个构建层。而后,我们会讲述 widgets 如何被组织,并转换成应用程序的渲染对象。同时我们也会讲述 Flutter 如何在平台层面与其他代码进行交互,最终,我们会对目前 Flutter 对于 Web 平台的支持与其他平台的异同做一个总结。

响应式用户界面

======================================================================

Flutter 是一个响应式的且伪声明式的 UI 框架,开发者负责提供应用状态与界面状态之间的映射,框架则在运行时将应用状态的更改更新到界面上。在大部分传统的 UI 框架中,界面的初始状态通常会被一次性定义,然后,在运行时根据用户代码分别响应事件进行更新。

Flutter 与其他响应式框架类似,采用了显式剥离基础状态和用户界面的方式,来解决这一问题。你可以通过 React 风格的 API,创建 UI 的描述,让框架负责通过配置优雅地创建和更新用户界面。

在 Flutter 里,widgets(类似于 React 中的组件)是用来配置对象树的不可变类。这些 widgets 会管理单独的布局对象树,接着参与管理合成的布局对象树。 Flutter 的核心就是一套高效的遍历树的变动的机制,它会将对象树转换为更底层的对象树,并在树与树之间传递更改。

build() 是将状态转化为 UI 的方法,widget 通过重写该方法来声明 UI 的构造。build() 方法在框架需要时都可以被调用(每个渲染帧可能会调用一次),从设计角度来看,它应当能够快速执行且没有额外影响的。这样的实现设计依赖于语言的运行时特征(特别是对象的快速实例化和清除)。幸运的是,Dart 非常适合这份工作。

Widgets

======================================================================

应用额如前所述,FLutter强调以widgets作为组成单位。Widgets是构建Flutter应用界面的基础块,每个widget都是一部分不可变的UI声明。

Widgets通过布局组合形成一种层次结构关系。每个Widget都是嵌套在其父级的内部,并可以通过父级接收上下文。从根布局(托管Flutter应用的容器,通常是MaterialApp或者CupertinoApp)开始,自上而下就是这样的结构,如下面实例;

import ‘package:flutter/material.dart’;

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {

const MyApp({Key? key}) : super(key: key);

@override

Widget build(BuildContext context) {

return MaterialApp(

home: Scaffold(

appBar: AppBar(

title: const Text(‘My Home Page’),

),

body: Center(

child: Builder(

builder: (BuildContext 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实现。

这样的实现有几个优势:

  • 提供了诬陷的扩展性。

  • Flutter可以直接合成所有的场景,而无需在Flutter与原生平台之间来回的切换,从而避免了明显的性能瓶颈。

  • 将应用的行为与操作系统的依赖解耦。

组成


Widget通常由更小的且用途单一的widgets组合而成,提供更强大的功能。

在设计的时候,相关的概念设计已尽可能地少量存在,而通过大量的内容进行填充。eg,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 来实现自定义的效果。

构建widgets


先前提到,可以通过重写build()方法,返回一个新的元素树,来定义视觉展示。这棵树用更为具体的术语表示了widget在UI中的部分。例如,工具栏widget的build方法可能会返回水平布局,其中可能包含了一些文字,各种各样的按钮。根据需要,框架会递归请求每个widget进行构建,直到整棵树都被具体的可渲染的对象描述为止。然后框架会将可渲染的对象缝合在一起,组成可渲染的对象树。

Widget的build方法应该是没有副作用的。每当一个方法要求构建市,widget都应当能返回一个widget的元素树,与先签返回的widget也没有关联。框架会根据渲染对象树来确定哪些构建方法需要被调用,这是一响略显繁重的工作。

每个渲染帧,Flutter都可以根据变换的状态,调用build()方法重建部分UI。因此,保证build方法轻量且能够快速返回widget是非常关键的,繁重的计算工作应该通过一些异步的方法来完成,然后作为构建方法build的一部分存储。

尽管这样的实现看起来不够成熟,但是这样的自动对比方法非常有效,可以实现高性能的交互应用。同时,以这种方式设计的build方法,将重点放在widget组合的声明上,从而简化了代码,而不是以一种状态去更新另一种状态这样的复杂过程。

状态管理


那么,在众多的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)会根据当前构建的上下文(即当前的widge位置的句柄),并返回类型为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.headline6,

),

);

类似的,以该方法实现的还有提供了路由页面的Navigator,提供了屏幕信息指标,包括方向,尺寸和高度的MediaQuery等等。

随着应用程序的不断迭代,更高级的状态管理方法变得更加有吸引力,它们可以减少有状态的widget的创建。许多Flutter应用使用了provider用于状态管理,它对InheritedWidget进行了进一步的包装。FLutter的分层架构也允许使用其他的实现来替换状态管理只UI的方案,例如flutter_hooks

渲染和布局

====================================================================

本节介绍Flutter的渲染机制,包括将widget层级结构转换成屏幕上绘制的实际像素的一系列步骤。

Flutter的渲染模型


你可能思考过:既然Flutter是一个跨平台的框架,那么它又是如何提供与原生平台框架相当的性能的呢?

让我们从Android原生应用的角度开始思考。当你在编写绘制的内容的时候,你需要调用Android框架的Java代码。Android的系统库提供了可以将自身绘制到Canvas对象的组件,接下来Android就可以使用由C/C++编写的Skia图形引擎,调用CPU和GPU完成在设备上的绘制。

跨平台框架都会在Android和IOS的UI底层库上创建一层抽象,该抽象层尝试抹平各个系统之间的差异。这时,应用程序的代码通常使用JavaScript等解释型语言来进行编写,这些代码会与基于Java的Android和基于OC的IOS进行交互,最终展示UI界面。所有流程都增加了显著的开销,在UI和应用逻辑有凡在的交互时更为如此。

相比之下,Flutter通过染过系统UI组件库,使用自己的widget内容集,消减了抽象层的开销。用于绘制Flutter图像内容的Dart代码被编译成机器码,并使用Skia进行渲染。Flutter同时也嵌入了自己的Skia副本作文引擎的一部分,让开发者能再设备未更新到最新系统时,也能跟进升级自己的应用,保证稳定性并提升性能。

从用户操作到GPU


对于Flutter渲染机制而言,首要原则就是简单快捷。Flutter为数据流向系统提供了直通的通道,如以下的流程图所示:

Render pipeline sequencing diagram

接下来让我们更加深入了解其中的一些阶段。

构建:从Widget到Element


学习路线+知识梳理

花了很长时间,就为了整理这张详细的知识路线脑图。当然由于时间有限、能力也都有限,毕竟嵌入式全体系实在太庞大了,包括我那做嵌入式的同学,也不可能什么都懂,有些东西可能没覆盖到,不足之处,还希望小伙伴们一起交流补充,一起完善进步。

这次就分享到这里吧,下篇见
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值