阅读 136

Flutter 架构概览(翻译)

本文旨在提供Flutter体系结构的高级概述,包括构成Flutter设计的核心原则和概念。

Flutter是一个跨平台的UI工具包,旨在允许iOS和Android等操作系统之间的代码重用,同时也允许应用程序直接与底层平台服务交互。开发人员在不同的平台上实现尽可能多的代码共享,这是一个自然的目标,即在不同的平台上实现尽可能多的代码共享。

在开发过程中,Flutter应用程序运行在一个VM中,该VM提供有状态的热重新加载更改,而不需要完全重新编译。发布时,Flutter应用程序直接编译为机器代码,无论是intelx64还是ARM指令,如果是针对web的,则编译成JavaScript。该框架是开放源码的,具有许可的BSD许可证,并且有一个蓬勃发展的第三方软件包生态系统,补充了核心库功能。

本概述分为多个部分: 

  1.  层级模型:构建Flutter的架构层级。 
  2. 响应式用户界面:Flutter UI开发的核心概念。
  3. widgets简介:Flutter用户界面的基本构建单元。
  4. 渲染过程:Flutter如何将UI代码转换为像素。
  5. 平台嵌入程序概述:允许移动和桌面操作系统执行Flutter应用程序的代码。
  6. 将Flutter与其他代码集成:关于Flutter应用程序可用的不同技术的信息。
  7. 对web的支持:关于浏览器环境中Flutter特性的结束语。

架构层级

Flutter设计为一个可扩展的分层系统。它作为一系列独立的库存在,每个库都依赖于底层。框架的每一层都被设计成可替换的,而且每一层的访问都是可替换的。

Architectural
diagram

对于底层操作系统,Flutter应用程序的打包方式与任何其他本机应用程序相同。特定于平台的嵌入式程序提供入口点;与底层操作系统协调以访问服务(如呈现Surface、可访问性和事件输入);并管理消息事件循环。Embedder是用适合平台的语言编写的:目前java和C++为Android、ObjyOb/ObjuleC++、IOS和MaOS,以及C++用于Windows和Linux。使用嵌入程序,Flutter代码可以作为一个模块集成到现有的应用程序中,或者代码可以是应用程序的全部内容。Flutter包括许多用于通用目标平台的嵌入物,但也存在其他嵌入物。 Flutter的核心是Flutter引擎,它主要是用C++编写的,支持支持所有Flutter应用所必需的基元。每当需要绘制新帧时,引擎负责将合成的场景栅格化。它提供了Flutter核心API的底层实现,包括图形(通过Skia)、文本布局、文件和网络I/O、可访问性支持、插件架构以及Dart运行时和编译工具链。

 发动机通过Dart:用户界面,它在DART类中封装底层C++代码。这个库公开了最底层的原语,例如用于驱动输入、图形和文本呈现子系统的类。 

 通常,开发人员通过Flutter框架与Flutter进行交互,该框架提供了一个用Dart语言编写的现代的响应式框架。它由一组丰富的平台、布局和基础库等层级组成。自下而上,我们有:

  1. 基本的基础类和构建块服务,如动画、绘画和手势,它们提供了底层基础上常用的抽象。
  2. 渲染层提供了处理布局的抽象。使用此层,可以构建可渲染对象的树。您可以动态操作这些对象,树会自动更新布局以反映您的更改。
  3. widgets层是一个组合抽象。渲染层中的每个渲染对象在小部件层中都有一个相应的类。此外,widgets层允许您定义可以重用的类的组合。这是反应式规划模型的引入层。
  4. Material和Cupertino库提供了一组全面的控件,这些控件使用widget层的组合原语来实现Material或iOS设计语言。

许多开发人员可能会在平台上使用一些类似于Flutter平台的小插件,比如Flutter平台的特性,比如Flutter-camera等。其中一些软件包来自更广泛的生态系统,包括应用内支付、苹果认证和动画等服务。 

 这篇综述的其余部分从UI开发的反应式范例开始,大致沿着各个层进行。然后,我们将描述如何将小部件组合在一起并转换为可以作为应用程序的一部分呈现的对象。在简要总结Flutter的web支持与其他目标有何不同之前,我们将描述Flutter如何在平台级别与其他代码进行互操作。

响应式用户界面

从表面上看,Flutter是一个响应式的、伪声明性的UI框架,在这个框架中,开发人员提供了从应用程序状态到接口状态的映射,并且当应用程序状态发生变化时,该框架承担在运行时更新接口的任务。这个模型的灵感来源于Facebook为自己的React框架所做的工作,其中包括对许多传统设计原则的重新思考。 在大多数传统的UI框架中,用户界面的初始状态只描述一次,然后在运行时由用户代码单独更新,以响应事件。这种方法的一个挑战是,随着应用程序复杂性的增加,开发人员需要知道状态更改是如何在整个UI中级联的。例如,考虑以下UI:

有很多地方可以改变状态:颜色框、色调滑块、单选按钮。当用户与UI交互时,更改必须反映在其他地方。更糟糕的是,除非小心,否则对用户界面的某个部分的微小更改可能会对看似不相关的代码片段造成连锁反应。 

 一种解决方案是类似MVC的方法,通过控制器将数据更改推送到模型中,然后模型通过控制器将新状态推送到视图中。但是,这也有问题,因为创建和更新UI元素是两个单独的步骤,很容易失去同步。

 Flutter与其他反应式框架一起,通过显式地将用户界面与其底层状态解耦,对这个问题采取了另一种方法。使用React样式的api,您只需创建UI描述,框架会根据需要使用该配置来创建和/或更新用户界面。 

 在Flutter中Widget(类似于React中的组件)由不可变的类表示,这些类用于配置对象树。这些小部件用于管理单独的布局对象树,然后用于管理单独的对象树进行合成。Flutter的核心是一系列机制,用于有效地遍历树的修改部分,将对象树转换为对象的低层树,并在这些树上传播变化。 小部件通过重写 build()方法声明其用户界面,该方法是一个将状态转换为UI的函数:

UI = f(state)
复制代码

build()方法的执行速度很快,应该没有任何副作用,允许框架在需要时调用它(可能每渲染帧调用一次)。 这种方法依赖于语言运行时的某些特性(特别是快速对象实例化和删除)。幸运的是,Dart特别适合这个任务。

Widgets

如前所述,Flutter强调将widget作为一个组合单元。widget是Flutter应用程序用户界面的构建块,每个widget都是用户界面部分的不可变声明。 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),
                  ElevatedButton(
                    onPressed: () {
                      print('Click!');
                    },
                    child: Text('A button'),
                  ),
                ],
              );
            },
          ),
        ),
      ),
    );
  }
} 
复制代码

在上面的代码中,所有实例化的类都是Widget。 应用程序通过告诉框架用另一个Widget替换层次结构中的Widget来更新用户界面,以响应事件(例如用户交互)。然后,框架会比较新的和旧的Widget,并有效地更新用户界面。 Flutter对每个UI控件都有自己的实现,而不是遵从系统提供的实现:例如,iOS交换机控件和Android等效控件都有一个纯Dart实现。 这种方法有几个好处:

  •  提供无限的扩展性。想要一个Switch控件变体的开发人员可以任意创建一个,而不限于OS提供的扩展点。
  •  通过允许Flutter一次合成整个场景,而无需在Flutter代码和平台代码之间来回转换,避免了显著的性能瓶颈。
  •  将应用程序行为与任何操作系统依赖项分离。应用程序在所有版本的操作系统上看起来和感觉都一样,即使操作系统更改了其控件的实现。

组合方式

小部件通常由许多其他小的、单一用途的小部件组成,它们组合在一起可以产生强大的效果。 在可能的情况下,设计概念的数量被保持在最小,同时允许总词汇量很大。

例如,在widgets层中,Flutter使用相同的核心概念(Widget)来表示屏幕上的绘图、布局(定位和调整大小)、用户交互、状态管理、主题化、动画和导航。在动画层,一对概念,动画和二者,覆盖了大部分的设计空间。

在渲染层中,RenderObjects用于描述布局、绘制、命中测试和可访问性。在每一种情况下,相应的词汇表最终都很大:有数百个小部件和渲染对象,以及几十个动画和tween类型。 类的层次结构故意是浅而宽的,以最大限度地增加组合的数量,重点放在每个小部件都能很好地完成一件事。核心特性是抽象的,甚至像填充和对齐这样的基本特性都是作为单独的组件实现的,而不是构建在核心中。(这也与更传统的api形成了对比,后者在每个布局组件的公共核心都内置了填充等功能。)

因此,例如,为了使小部件居中,而不是调整概念上的Align属性,可以将其包装在中心小部件中。 有一些小部件用于填充、对齐、行、列和网格。这些布局小部件没有自己的可视化表示。相反,它们的唯一目的是控制另一个小部件布局的某些方面。Flutter还包括利用这种组合方法的实用程序小部件。

 例如,Container是一个常用的小部件,由几个负责布局、绘制、定位和调整大小的小部件组成。具体地说,容器由LimitedBox、ConstrainedBox、Align、Padding、DecoratedBox和Transform小部件组成,您可以通过阅读其源代码看到。

Flutter的一个定义特性是,您可以深入到任何小部件的源代码中并对其进行检查。因此,您可以用新颖的方式组合容器和其他简单的小部件,或者仅仅使用容器作为灵感创建一个新的小部件,而不是将容器子类化以产生定制效果。

构建Widget 

如前所述,通过重写build()函数返回新element树来确定小部件的可视化表示。这个树用更具体的术语表示widget的UI部分。

例如,工具栏小部件可能有一个构建函数,该函数返回一些文本和各种按钮的水平布局。根据需要,框架递归地要求每个小部件构建,直到树完全由具体的可呈现对象描述。然后,框架将可渲染对象缝合到可渲染对象树中。 

 widget的build()方法不应该有副作用。每当调用build()函数时,不管之前返回了什么,widget都应该返回一个新的widgets树。该框架执行繁重的工作,以确定需要基于呈现对象树调用哪些构建方法(稍后将详细介绍)。关于这个过程的更多信息可以在内部Flutter主题中找到。

 在每帧的绘制过程中,Flutter可以只绘制 通过调用widget的build()方法重新创建UI中状态而发生更改的部分。因此,重要的是build方法应该快速返回,繁重的计算工作应该以异步的方式完成,结果存储在state中 供build方法使用。

虽然这种方法相对简单,但这种自动比较非常有效,能够实现高性能、交互式应用程序。而且,build函数的设计通过集中声明小部件是由什么组成的,而不是将用户界面从一种状态更新到另一种状态的复杂性来简化代码。

Widget 状态

该框架引入了两个主要的小部件类:stateful widget and stateless widget.。

许多小部件没有可变状态:它们没有随时间变化的任何属性(例如,图标或标签)。这些小部件子类无状态小部件。 但是,如果小部件的独特特性需要根据用户交互或其他因素而改变,则该小部件是有状态的。

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

每当你改变一个State对象(例如,通过递增计数器),你必须调用setState()来通过再次调用State的build方法来通知框架更新用户界面。

拥有独立的state和widget对象可以让其他小部件以完全相同的方式处理无状态和有状态的小部件,而不必担心丢失状态。父级可以随时创建子级的新实例,而无需保留子级以保留其状态,而不会丢失子级的持久状态。该框架在适当的时候完成查找和重用现有状态对象的所有工作。

状态管理

那么,如果许多小部件都可以包含状态,那么状态是如何管理和在系统中传递的呢? 

 与任何其他类一样,您可以在小部件中使用构造函数来初始化其数据,因此build()方法可以确保任何子小部件都实例化了所需的数据:

@override
Widget build(BuildContext context) {
   return ContentWidget(importantState);
}
复制代码

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

Inherited widgets

每当ExamWidget或GradeWidget对象之一需要StudentState的数据时,它现在可以使用以下命令访问它:

final studentState = StudentState.of(context);
复制代码

调用获取构建上下文(当前小部件位置的句柄),并返回树中与StudentState类型匹配的最近的祖先。确定是否还应该调用一个子控件的updateShootify()来重新生成该控件的子控件。

of(context)调用 build 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包。

渲染和布局 

本节描述渲染管道,这是flatter将小部件的层次结构转换为绘制在屏幕上的实际像素所需的一系列步骤。

Flutter渲染模型 

您可能想知道:如果Flutter是一个跨平台框架,那么它如何提供与单平台框架相当的性能? 从思考传统Android应用程序的工作原理开始是很有用的。在绘图时,首先调用Android框架的Java代码。Android系统库提供了负责将自己绘制到画布对象的组件,Android可以用SKIa渲染,这是一个用C/C++编写的图形引擎,调用CPU或GPU完成设备上的绘图。 跨平台框架通常通过在底层原生Android和iOS UI库上创建抽象层来工作,试图消除每个平台表示的不一致性。应用程序代码通常是用解释性语言(如JavaScript)编写的,JavaScript必须与基于Java的Android或基于Objective-C的iOS系统库交互才能显示UI。所有这些都会增加开销,这可能非常重要,特别是在UI和应用程序逻辑之间存在大量交互的情况下。

相比之下,flutter将这些抽象最小化,绕过系统UI小部件库而使用自己的小部件集。绘制flatter视觉效果的Dart代码被编译成本机代码,使用Skia进行渲染。Flutter还嵌入了自己的Skia副本作为引擎的一部分,允许开发者升级他们的应用程序,以保持最新的性能改进,即使手机还没有更新新的Android版本。对于其他本机平台(如iOS、Windows或macOS)上的flutter也是如此。

从用户输入到GPU 

Flutter应用于其渲染管道的首要原则是简单即快速。对于数据如何流向系统,Flutter有一个简单明了的管道,如下图所示

Render pipeline sequencing
diagram

让我们更详细地看看这些阶段。 

Build: 从Widget到 Element

考虑下面这个简单的代码片段,它演示了一个简单的Widget层次结构: 

Container(
  color: Colors.blue,
  child: Row(
    children: [
      Image.network('https://www.example.com/1.png'),
      Text('A'),
    ],
  ), 
);
复制代码

当Flutter需要呈现这个片段时,它调用build()方法,该方法返回基于当前应用程序状态呈现UI的Widget子树。在此过程中,build()方法可以根据其状态根据需要引入新的Widget。作为一个简单的例子,在前面的代码片段中,Container具有color和child属性。从Container的源代码可以看到,如果color不为null,它将插入一个ColoredBox来表示该颜色:

if (color != null)
  current = ColoredBox(color: color, child: current);
复制代码

相应地,图像和文本小部件可能会在构建过程中插入诸如RawImage和RichText之类的子部件。因此,最终的小部件层次结构可能比代码所表示的更深,如在这种情况下:Render pipeline sequencing
diagram

这就解释了为什么当你通过一个调试工具(比如Flutter inspector)来检查树时,你可能会看到一个比原始代码更深的结构。 

 在构建阶段,Flutter将用代码表示的小部件转换成相应的元素树,每个小部件有一个元素。每个元素表示树层次结构中给定位置的小部件的特定实例。元素有两种基本类型:

  • ComponentElement,其他元素的宿主。  
  • RenderObjectElement,参与布局或绘制阶段的元素。

Render pipeline sequencing
diagram

RenderObjectElements是它们的Widget层和底层RenderObject之间的中介,我们将在后面讨论。

任何Widget的元素都可以通过其BuildContext引用,BuildContext是树中Widget位置的句柄。这是函数调用中的context,例如 Theme.of(context),并作为参数提供给build()方法。 因为小部件是不可变的,包括节点之间的父/子关系,对小部件树的任何更改(例如在前面的示例中将Text('A')更改为Text('B'))都会导致返回一组新的小部件对象。但这并不意味着必须重建底层表示。Element树是从一个帧到另一个帧的持久化的,因此在缓存其底层表示时,Flutter可以将widget层次结构视为完全可丢弃的。通过只遍历更改的小部件,Flutter可以重建Element树中需要重新配置的部分。

布局和渲染 

在应用程序只画一个小部件这将是罕见。因此,任何UI框架的一个重要部分就是能够有效地布局Widget的层次结构,在它们显示在屏幕上之前确定每个元素的大小和位置。 渲染树中每个节点的基类是RenderObject,它定义了一个用于布局和绘制的抽象模型。这是非常普遍的:它不致力于一个固定数量的维度,甚至笛卡尔坐标系(这个例子演示了一个极坐标系)。每个RenderObject都知道它的父对象,但是除了如何访问它们和它们的约束之外,对它的子对象知之甚少。这为RenderObject提供了足够的抽象,能够处理各种用例。

 在构建阶段,Flutter为元素树中的每个RenderObjectElement创建或更新一个从RenderObject继承的对象。RenderObjects是基本体:

  • RenderParagraph渲染文本,
  • RenderImage渲染图像,
  • RenderTransform在绘制其子对象之前应用变换。

Differences between the widgets hierarchy and the element and render
trees大多数Flutter小部件都是由一个继承了RenderBox子类的对象来呈现的,RenderBox子类表示2D笛卡尔空间中固定大小的RenderBox对象。RenderBox提供了框约束模型的基础,为要渲染的每个小部件建立了最小和最大宽度和高度。 要执行布局,Flutter在深度优先遍历中遍历渲染树,并将大小约束从父对象传递到子对象。在确定其大小时,子对象必须遵守其父对象给它的约束。子对象的响应方式是在父对象建立的约束内向其父对象传递大小。

Constraints go down, sizes go
up

在这个遍历树的最后,每个对象在其父对象的约束内都有一个定义的大小,并且可以通过调用paint()方法进行绘制。 长方体约束模型非常强大,可以在O(n)时间内布局对象: 父对象可以通过将最大和最小约束设置为相同的值来指定子对象的大小。例如,手机应用程序中最上面的渲染对象将其子对象约束为屏幕大小。(孩子们可以选择如何使用这个空间。例如,它们可能只是将要渲染的内容置于指定约束的中心。) 父对象可以指定子对象的宽度,但赋予子对象相对于高度的灵活性(或指定高度,但提供宽度的灵活性)。一个真实的例子是流文本,它可能需要适应一个水平约束,但垂直变化取决于文本的数量。 即使子对象需要知道它有多大的可用空间来决定它将如何呈现其内容,这种模型也是有效的。通过使用LayoutBuilder小部件,子对象可以检查传递的约束,并使用这些约束来确定如何使用它们,例如:

  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        if (constraints.maxWidth < 600) {
          return OneColumnLayout();
        } else {
          return TwoColumnLayout();
        }
      },
    ); 
复制代码

有关约束和布局系统的更多信息以及工作示例,可以在“了解约束”主题中找到。

 所有RenderObjects的根是RenderView,它表示渲染树的总输出。当平台要求渲染新帧时(例如,由于vsync或纹理解压缩/上载已完成),将调用compositeFrame()方法,该方法是渲染树根处RenderView对象的一部分。这将创建一个SceneBuilder来触发场景的更新。场景完成后,RenderView对象将合成的场景传递给dart:ui 的Window.render()方法,它将控制权传递给GPU来呈现它。 在这篇文章中,更多的细节和细节可以在这篇文章的高层次上找到。

平台嵌入 

正如我们所看到的,Flutter用户界面不是被翻译成等效的OS小部件,而是由Flutter自己构建、布局、合成和绘制的。获取纹理和参与底层操作系统的应用程序生命周期的机制不可避免地会根据平台的独特关注点而有所不同。该引擎与平台无关,提供了一个稳定的ABI(应用程序二进制接口),为平台嵌入程序提供了一种设置和使用Flutter的方法。 平台嵌入程序是托管所有Flutter内容的本地操作系统应用程序,充当主机操作系统和Flutter之间的粘合剂。

当你启动一个Flutter应用程序时,嵌入程序提供入口点,初始化Flutter引擎,获取UI和rastering的线程,并创建一个Flutter可以写入的纹理。嵌入程序还负责应用程序的生命周期,包括输入手势(如鼠标、键盘、触摸)、窗口大小、线程管理和平台消息。Flutter包括适用于Android、iOS、Windows、macOS和Linux的平台嵌入程序;您还可以创建一个定制的平台嵌入程序,比如在这个支持通过VNC样式的帧缓冲区远程处理Flutter会话的示例中,或者在这个Raspberry Pi的示例中。

 每个平台都有自己的一组api和约束。一些针对特定平台的简要说明:

  • 在iOS和macOS上,Flutter分别作为UIViewController或NSViewController加载到嵌入程序中。平台嵌入程序创建了一个作为Dart虚拟机和Flutter运行时主机的FlutterEngine,以及一个连接到fluttle引擎的FlutterViewController,将UIKit或Cocoa输入事件传递到Flutter中,并显示由FlutterEngine使用Metal或OpenGL呈现的帧。
  •  在Android上,Flutter默认作为一个活动加载到嵌入程序中。视图由FlutterView控制,该视图根据Flutter内容的组成和z-order要求,将Flutter内容渲染为视图或纹理。  
  • 在Windows上,Flutter托管在一个传统的Win32应用程序中,内容使用ANGLE来呈现,ANGLE是一个将openglapi调用转换为directx11等效调用的库。目前正在努力提供一个使用UWP应用模型的Windows嵌入程序,并通过directx12将ANGLE替换为更直接的GPU路径。

与其他代码集成 

Flutter提供了各种互操作性机制,无论您访问的是用Kotlin或Swift这样的语言编写的代码或API,调用基于C的原生API,在Flutter应用程序中嵌入本机控件,还是在现有应用程序中嵌入Flutter。

平台通道 

对于移动和桌面应用程序,Flutter允许您通过平台通道调用自定义代码,这是一种简单的机制,用于在您的Dart代码和宿主应用程序的特定于平台的代码之间进行通信。通过创建一个公共channel(封装名称和编解码器),您可以在Dart和用Kotlin或Swift等语言编写的平台组件之间发送和接收消息。数据从Dart类型(如Map)序列化为标准格式,然后反序列化为Kotlin(如HashMap)或Swift(如Dictionary)中的等效表示形式。

How platform channels allow Flutter to communicate with host
code

下面是对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)
  }
}
复制代码

有关使用平台通道的更多示例,包括macOS的示例,可以在flutter/plugins repository3中找到。还有数以千计的插件已经为Flutter提供,涵盖了许多常见的场景,从Firebase到广告,再到像摄像头和蓝牙这样的设备硬件。

外部功能接口

 对于基于C的api,包括那些可以为使用Rust或Go等现代语言编写的代码生成的api,Dart提供了一种直接机制,可以使用dart:ffi library。外部函数接口(FFI)模型可以比平台通道快得多,因为传递数据不需要序列化。相反,Dart运行时提供了在由Dart对象支持的堆上分配内存以及调用静态或动态链接库的功能。FFI可用于除web之外的所有平台,js包的作用是等效的。 若要使用FFI,请为每个Dart和非托管方法签名创建typedef,并指示Dart VM在它们之间进行映射。作为一个简单的示例,下面是调用传统Win32 MessageBox()API的代码片段:

typedef MessageBoxNative = Int32 Function(
    IntPtr hWnd, Pointer<Utf16> lpText, Pointer<Utf16> lpCaption, Int32 uType);
typedef MessageBoxDart = int Function(
    int hWnd, Pointer<Utf16> lpText, Pointer<Utf16> lpCaption, int uType);

final user32 = DynamicLibrary.open('user32.dll');
final MessageBox =
    user32.lookupFunction<MessageBoxNative, MessageBoxDart>('MessageBoxW');

final result = MessageBox(
    0, // No owner window
    Utf16.toUtf16('Test message'),   // Message
    Utf16.toUtf16('Window caption'), // Window title
    0 // OK button only
    );
复制代码

在Flutter应用程序中呈现native控件 

在Android的内部控件中没有类似于Flutter的内部控件,因为它的内部控件并没有被绘制出来。对于那些希望在Flutter应用程序中包括现有平台组件(如浏览器控件)的开发人员来说,这是一个问题。 Flutter通过引入平台视图小部件(AndroidView和UiKitView)来解决这一问题,它们允许您在每个平台上嵌入此类内容。平台视图可以与其他Flutter内容集成。每个小部件都充当底层操作系统的中介。例如,在Android上,AndroidView提供三个主要功能:

  •  制作由原生视图渲染的图形纹理的副本,并在每次绘制帧时将其作为颤振渲染曲面的一部分呈现给Flutter进行合成。 
  •  响应命中测试和输入手势,并将其转换为等效的本机输入。 
  • 创建可访问性树的模拟,并在本机层和Flutter层之间传递命令和响应。 

不可避免地,这种同步会带来一定的开销。因此,一般来说,这种方法最适合于复杂的控件,比如googlemaps,在Flutter中重新实现是不实际的。 通常,Flutter应用程序会基于平台测试在build()方法中实例化这些小部件。例如,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底层的本机代码通信通常使用平台通道机制进行,如前所述。 目前,平台视图不适用于桌面平台,但这并不是架构上的限制;将来可能会添加支持。

在原生用应用中托管Flutter内容 

与前面的场景相反,在现有的Android或iOS应用程序中嵌入Flutter小部件。如前一节所述,在移动设备上运行的新创建的Flutter应用程序托管在Android活动或iOS UIViewController中。Flutter内容可以使用相同的嵌入API嵌入到现有的Android或iOS应用程序中。 Flutter模块模板设计为易于嵌入;您可以将其作为源依赖项嵌入到现有的Gradle或Xcode构建定义中,也可以将其编译为Android归档文件或iOS框架二进制文件以供使用,而不需要每个开发人员都安装Flutter。 

 Flutter引擎需要很短的时间来初始化,因为它需要加载Flutter共享库、初始化Dart运行时、创建并运行Dart isolate,以及将渲染面附加到UI。为了最小化呈现Flutter内容时的任何UI延迟,最好在整个应用程序初始化过程中初始化Flutter引擎,或者至少在第一个Flutter屏幕之前初始化,这样用户在加载第一个颤振代码时不会遇到突然的停顿。此外,分离Flutter引擎允许它在多个Flutter屏幕上重用,并共享加载所需库所需的内存开销。 有关如何将Flutter加载到现有Android或iOS应用程序的更多信息,请参阅加载顺序、性能和内存主题

文章分类
Android
文章标签