Flutter架构综述

lazihuman
发布于 2020-9-6 10:20
浏览
0收藏

本文旨在对Flutter的架构进行高层次的概述,包括构成其设计的核心原则和概念。

Flutter是一个跨平台的UI工具包,它的设计目的是允许跨iOS和Android等操作系统的代码重用,同时也允许应用程序直接与底层平台服务对接。其目标是让开发者能够交付在不同平台上感觉自然的高性能应用,在尽可能多的代码共享的同时,拥抱存在差异的地方。

在开发过程中,Flutter应用运行在一个虚拟机中,该虚拟机提供有状态的变化热重载,而不需要完全重新编译。发布时,Flutter应用直接编译成机器代码,无论是Intel x64,还是ARM指令,如果针对Web,则编译成JavaScript。该框架是开源的,采用允许的BSD许可证,并拥有一个繁荣的第三方包生态系统,补充核心库功能。

本概述分为若干部分。

layer model:建Flutter的部分。
Reactive user interfaces:lutter用户界面开发的核心概念。
widgets:lutter用户界面的基本构件。
rendering process:lutter如何将UI代码转化为像素。
platform embedders:移动和桌面操作系统执行Flutter应用程序的代码。
Integrating Flutter with other code:于Flutter应用程序可用的不同技术的信息。
Support for the web:于Flutter在浏览器环境下的特性的总结。
Achitectural layers
Flutter被设计成一个可扩展的、分层的系统。它作为一系列独立的库存在,每个库都依赖于底层。任何一层都没有特权访问下面的一层,框架层的每一部分都被设计成可选择和可替换的。

Flutter架构综述-鸿蒙开发者社区

对底层操作系统而言,Flutter应用程序与其他本地应用程序一样,以相同的方式进行打包。一个平台特定的嵌入器提供了一个入口点;与底层操作系统协调,以访问服务,如渲染表面、可访问性和输入;并管理消息事件循环。嵌入器是用适合平台的语言编写的:目前Android的Java和C++,iOS和macOS的Objective-C/Objective-C++,Windows和Linux的C++。使用嵌入器,Flutter代码可以作为一个模块集成到现有的应用程序中,也可以是应用程序的全部内容。Flutter包含了许多针对常见目标平台的嵌入器,但也存在其他嵌入器。

Flutter的核心是Flutter引擎,它主要用C++编写,支持所有Flutter应用所需的基元。每当需要绘制新的帧时,该引擎负责对合成场景进行光栅化。它提供了Flutter核心API的低层实现,包括图形(通过Skia)、文本布局、文件和网络I/O、可访问性支持、插件架构以及Dart运行时和编译工具链。

该引擎通过dart:ui暴露给Flutter框架,它将底层的C++代码封装在Dart类中。这个库暴露了最底层的基元,例如用于驱动输入、图形和文本渲染子系统的类。

通常情况下,开发人员通过Flutter框架与Flutter进行交互,Flutter框架提供了一个用Dart语言编写的现代、反应式框架。它包括一套丰富的平台、布局和基础库,由一系列的层组成。从底层到顶层,我们有:

基础类和构件服务,如动画,绘画和手势,在底层基础上提供了常用的抽象。
渲染层提供了一个处理布局的抽象。通过这一层,你可以建立一个可渲染对象的树。你可以动态地操作这些对象,树会自动更新布局以反映你的变化。
widgets层是一个组成抽象。渲染层中的每个渲染对象在widgets层中都有一个对应的类。此外,widgets层还允许你定义可以重用的类的组合。这是引入反应式编程模型的一层。
Material和Cupertino库提供了全面的控件集,这些控件使用widget层的组合基元来实现Material或iOS设计语言。
Flutter框架相对较小;许多开发者可能会用到的更高级别的功能都是以包的形式实现的,包括像摄像头和webview这样的平台插件,以及像字符、http和动画这样的平台无关的功能,这些都是建立在核心Dart和Flutter库的基础上的。其中一些包来自更广泛的生态系统,涵盖应用内支付、苹果认证和动画等服务。

这篇综述的其余部分从UI开发的反应式范式开始,大致浏览了各个层次。然后,我们描述了如何将widget组合在一起,并将其转换为可作为应用程序的一部分进行渲染的对象。我们描述了Flutter如何在平台层面与其他代码进行交互,然后简要总结了Flutter的Web支持与其他目标的不同之处。

Reactive user interfaces
从表面上看,Flutter是一个被动的、伪声明式的UI框架,开发者提供一个从应用状态到界面状态的映射,当应用状态发生变化时,框架在运行时承担更新界面的任务。这种模式的灵感来自于Facebook为自己的React框架所做的工作,其中包括对很多传统设计原则的重新思考。

在大多数传统的UI框架中,用户界面的初始状态被描述一次,然后由用户代码在运行时响应事件单独更新。这种方法的一个挑战是,随着应用程序的复杂性增加,开发人员需要意识到状态变化如何在整个UI中级联。例如,考虑以下UI。

Flutter架构综述-鸿蒙开发者社区

有很多地方可以改变状态:颜色框、色调滑块、单选按钮。当用户与用户界面交互时,变化必须反映在其他每个地方。更糟糕的是,除非小心翼翼,否则对用户界面的一个部分的微小改变可能会对看似不相关的代码产生涟漪效应。
一种解决方案是像MVC这样的方法,通过控制器将数据变化推送到模型,然后模型通过控制器将新的状态推送到视图。然而,这也是有问题的,因为创建和更新UI元素是两个独立的步骤,很容易不同步。
Flutter与其他反应式框架一样,采用了另一种方法来解决这个问题,通过明确地将用户界面与其底层状态解耦。使用React风格的API,你只需要创建UI描述,而框架则负责使用这一个配置来创建和/或适当更新用户界面。
在Flutter中,widget(类似于React中的组件)由不可变的类来表示,这些类用于配置对象树。这些widgets用于管理单独的对象树进行布局,然后用于管理单独的对象树进行合成。Flutter的核心是一系列机制,用于有效地行走树的修改部分,将对象树转换为低级对象树,并在这些树上传播变化。
一个widget通过覆盖build()方法来声明其用户界面,build()方法是一个将状态转换为UI的函数。

UI = f(state)

build()方法在设计上是快速执行的,并且应该没有副作用,允许框架在任何需要的时候都可以调用它(有可能每渲染一帧就调用一次)。

这种方法依赖于语言运行时的某些特性(特别是快速对象实例化和删除)。幸运的是,Dart特别适合这个任务。

Widgets
如前所述,Flutter强调widget是一个组成单位。Widget是Flutter应用的用户界面的构件,每个widget都是用户界面的一部分不可改变的声明。

小组件形成了一个基于组成的层次结构。每个widget都嵌套在它的父体内部,并且可以从父体接收上下文。这种结构一直延续到根widget(承载Flutter应用的容器,通常是MaterialApp或CupertinoApp),正如这个琐碎的例子所示。

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('My Home Page')),
        body: Center(
          child: Builder(
            builder: (BuildContext context) {
              return Column(
                children: [
                  Text('Hello World'),
                  SizedBox(height: 20),
                  RaisedButton(
                    onPressed: () {
                      print('Click!');
                    },
                    child: Text('A button'),
                  ),
                ],
              );
            },
          ),
        ),
      ),
    );
  }
}

在前面的代码中,所有实例化的类都是widget。

应用程序通过告诉框架用另一个widget替换层次结构中的一个widget来响应事件(如用户交互)更新用户界面。然后,框架会比较新旧widget,并有效地更新用户界面。

Flutter对每个UI控件都有自己的实现,而不是服从于系统提供的控件:例如,iOS Switch控件和Android对应的控件都有一个纯Dart的实现。

这种方法提供了几个好处:

提供了无限的可扩展性。开发者如果想要Switch控件的变体,可以以任意方式创建一个,而不局限于操作系统提供的扩展点。
通过允许Flutter一次性合成整个场景,避免了显著的性能瓶颈,而无需在Flutter代码和平台代码之间来回过渡。
将应用行为与任何操作系统的依赖关系解耦。应用程序在所有版本的操作系统上看起来和感觉是一样的,即使操作系统改变了其控件的实现。
Composition
小部件通常由许多其他小的、单一用途的小部件组成,这些小部件组合起来可以产生强大的效果。

在可能的情况下,设计概念的数量保持在最低限度,同时允许总词汇量很大。例如,在widgets层中,Flutter使用相同的核心概念(一个Widget)来表示绘制到屏幕上、布局(定位和大小)、用户交互性、状态管理、主题、动画和导航。在动画层,一对概念Animations和Tweens覆盖了大部分的设计空间。在渲染层中,RenderObjects用于描述布局、绘画、命中测试和可访问性。在每一种情况下,对应的词汇量最终都会很大:有数百个widgets和渲染对象,以及几十种动画和Tweens类型。

类的层次结构是刻意的浅而宽,以最大限度地增加可能的组合数量,专注于小型的、可组合的widgets,每个widgets都能做好一件事。核心功能是抽象的,即使是基本的功能,如padding和align,也是作为单独的组件实现的,而不是内置在核心中。(这也与传统的API形成了鲜明的对比,在传统的API中,像padding这样的功能是内置于每个布局组件的通用核心中的。)。所以,举例来说,要让一个小组件居中,而不是调整一个名义上的 Align 属性,你可以把它包裹在一个 Center 小组件中。

有用于填充、对齐、行、列和网格的小组件。这些布局部件没有自己的视觉表示。相反,它们的唯一目的是控制另一个部件的布局的某些方面。Flutter还包括利用这种组合方法的实用工具部件。

例如,Container,一个常用的widget,是由几个widget组成的,负责布局,绘画,定位和大小。具体来说,Container是由LimitedBox、ConstrainedBox、Align、Padding、DecoratedBox和Transform小组件组成的,你可以通过阅读它的源代码看到。Flutter的一个定义特性是,你可以钻进任何一个widget的源头并检查它。所以,你可以用新奇的方式将它和其他简单的widget组合起来,或者直接用Container作为灵感创建一个新的widget,而不是通过子类Container来产生自定义的效果。

Building widgets
如前所述,您通过重载build()函数来确定widget的视觉表现,以返回一个新的元素树。这个树以更具体的方式表示小组件在用户界面中的部分。例如,一个工具条小组件可能有一个构建函数,它返回一些文本和各种按钮的水平布局。根据需要,框架会递归地要求每个小组件进行构建,直到树完全由具体的可渲染对象来描述。然后,框架将这些可渲染对象缝合到一个可渲染对象树中。

一个widget的构建函数应该是没有副作用的。每当函数被要求构建时,widget应该返回一个新的widgets树1,不管widget之前返回的是什么。框架会做繁重的工作,根据渲染对象树来决定哪些构建方法需要被调用(后面会详细介绍)。关于这个过程的更多信息可以在Inside Flutter主题中找到。

在每个渲染帧上,Flutter可以通过调用该widget的build()方法,仅仅重新创建UI中状态已经改变的部分。因此,构建方法应该快速返回,重计算工作应该以某种异步方式完成,然后作为状态的一部分存储起来,供构建方法使用,这一点非常重要。

虽然这种自动对比的方法比较幼稚,但却相当有效,可以实现高性能、交互式的应用。而且,构建函数的设计通过专注于声明一个widget是由什么组成的,而不是将用户界面从一个状态更新到另一个状态的复杂性来简化你的代码。

Widget state
该框架引入了两大类widget:有状态和无状态widget。

许多widget没有可改变的状态:它们没有任何随时间变化的属性(例如,一个图标或一个标签)。这些widget是StatelessWidget的子类。

然而,如果一个小组件的独特特性需要根据用户交互或其他因素而改变,那么该小组件是有状态的。例如,如果一个小组件有一个计数器,每当用户点击一个按钮时就会递增,那么计数器的值就是该小组件的状态。当该值发生变化时,该小组件需要重新构建以更新其UI部分。这些widget是StatefulWidget的子类,(因为widget本身是不可变的)它们将可变的状态存储在一个单独的State子类中。StatefulWidgets没有构建方法;相反,它们的用户界面是通过State对象构建的。

每当你突变一个State对象时(例如,通过递增计数器),你必须调用setState()来向框架发出信号,通过再次调用State的构建方法来更新用户界面。

拥有独立的状态和widget对象,让其他widget以完全相同的方式对待无状态和有状态的widget,而不用担心丢失状态。父对象不需要紧紧抓住一个子对象来保存它的状态,而是可以在任何时候创建一个新的子对象实例而不会丢失子对象的持久化状态。框架会在适当的时候完成所有寻找和重用现有状态对象的工作。

State management
那么,如果许多widget可以包含状态,那么如何管理状态并在系统中传递呢?

和其他类一样,你可以在widget中使用构造函数来初始化它的数据,所以build()方法可以确保任何子widget被实例化时都有它需要的数据。

@override
Widget build(BuildContext context) {
   return ContentWidget(importantState);
}


然而,随着小组件树的深入,在树的层次结构中上下传递状态信息变得很麻烦。因此,第三种小组件类型 InheritedWidget 提供了一种从共享祖先中抓取数据的简单方法。您可以使用 InheritedWidget 来创建一个状态小组件,该小组件在小组件树中包装一个共同的祖先,如本例所示。

 

Flutter架构综述-鸿蒙开发者社区

每当一个 ExamWidget 或 GradeWidget 对象需要来自 StudentState 的数据时,它现在可以通过一个命令来访问它,例如

final studentState = StudentState.of(context);

of(context)调用接收构建上下文(当前小组件位置的句柄),并返回树中与StudentState类型匹配的最近的祖先。InheritedWidgets还提供了一个updateShouldNotify()方法,Flutter调用该方法来决定状态变化是否应该触发使用它的子部件的重建。
Flutter本身广泛使用InheritedWidget作为共享状态框架的一部分,例如应用程序的视觉主题,其中包括颜色和类型样式等属性,这些属性在整个应用程序中是普遍存在的。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。
随着应用程序的增长,更先进的状态管理方法,减少了创建和使用有状态小部件的仪式,变得更有吸引力。许多Flutter应用程序使用了像provider这样的实用程序包,它提供了一个围绕InheritedWidget的包装器。Flutter的分层架构也使其他方法能够实现状态到UI的转换,例如flutter_hooks包。

作者:xuyisheng
来源:掘金

分类
标签
已于2020-9-6 10:20:17修改
收藏
回复
举报
回复
    相关推荐