乐趣区

关于flutter:flutter系列之flutter架构什么的看完这篇文章就全懂了

简介

Flutter 是 google 开发的一个跨平台的 UI 构建工具,flutter 目前最新的版本是 3.0.5。应用 flutter 你能够应用一套代码搭建 android,IOS,web 和 desktop 等不同平台的利用。做到一次编写到处运行的目标。

说到一次编写处处运行,大家可能会想到 java。那么 flutter 跟 java 是不是相似呢?

对于 JAVA 来说,在编写完 JAVA 代码之后,将其编译成为 class 字节码,而后这个 class 字节码就能够不须要进行任何转换的在任何平台上运行。其底层原理是 JAVA 开发了适配不同操作系统和平台的 JVM,class 理论运行在 JVM 中,所以对底层到底运行在哪个平台是无感的。所有的适配都是由 JVM 来执行的。

Flutter 其实更像是 C 或者 C ++,尽管代码是一样的,然而须要依据不同的平台编译成不同的二进制文件。而 Flutter 也是一样,尽管咱们应用同一套 dart 代码编写了 Flutter 程序,然而须要不同的命令编译成不同平台的命令和安装包。

当然,在开发过程中,flutter 提供了虚拟机,实现了 hot reload 的性能, 在代码进行批改之后,能够立即重载,而不须要从新编译整个代码。

FLutter 这么神奇,那么它到底是怎么工作的呢?

Flutter 的架构图

咱们先来看下 Flutter 的架构图,当然这个架构图是官网来的,官网的架构图示意的是权威:

从上图中,咱们能够看到 Flutter 的架构能够分为三局部,从下到上别离是 embedder,Engine 和 Framework。

embedder

embedder 能够称为嵌入器,这是和底层的操作系统进行交互的局部。因为 flutter 最终要将程序打包到对应的平台中,所以这个嵌入器须要和底层的平台接口进行交互。

具体而言,对于 Android 平台应用的是 Java 和 C ++,对于 iOS 和 macOS 平台,应用的是 Objective-C/Objective-C++,对应 Windows 平台和 Linux 平台的是 C ++。

为什么 C ++ 这么弱小? 这里就可以看进去了,基本上所有底层的货色都是用 C ++ 写的。

回到 embedder, 为什么叫做嵌入器呢?这是因为 Flutter 打包的程序,能够作为整个应用程序,也能够作为现有程序的一部分被嵌入应用。

engine

engine 也叫做 flutter engine,它是 flutter 中最外围的局部。

Flutter engine 基本上应用 C ++ 写的。engine 的存在是为了反对 Dart Framework 的运行。它提供了 Flutter 的外围 API,包含作图、文件操作、网络 IO、dar 运行时环境等外围性能。

engine 次要是通过 dart:ui 裸露给 Flutter framework 层的。

Flutter framework

这一层是用户编程的接口,咱们的应用程序须要和 Flutter framework 进行交互,最终构建出一个应用程序。

Flutter framework 次要是应用 dart 语言来编写的。

framework 从下到上,咱们有最根底的 foundational 包,和构建在其上的 animation, painting 和 gestures。

再下面就是 rendering 层,rendering 为咱们提供了动静构建可渲染对象树的办法, 通过这些办法,咱们能够对布局进行解决。

接着是 widgets layer, 它是 rendering 层中对象的组合,示意一个小挂件。

最初是 Material 和 Cupertino 库, 这些库应用 widegts 层中提供的小部件,组合成了不同格调的控件集。

Flutter framework 就是这样一层层的构建起来的。

当然,下面的 embedder 和 engine 属于比拟底层的货色,咱们只须要晓得 Flutter 有这么一个货色,是这么应用的即可。

真正和咱们程序员相干的,就是 Flutter framework 了。因为咱们在编写代码的过程中,须要和 Flutter framework 打交道。

接下来,咱们重点关注下 Flutter framework 中的几个外围局部。

Widgets

Widgets 翻译成中文就是小插件的意思。Widgets 是 Flutter 中用户界面的根底。你在 flutter 界面中可能察看到的用户界面,都是 Widgets。

当然这些大的 Widgets 又是由一个个的小的 Widgets 组成的,而这些小的 Widgets 又是由更小的 Widgets 组成的。

这样就形成了 Widgets 的档次依赖构造, 这些层次结构的关联关系是通过 Widget 中的 child Widget 进行关联的。

在这种层次结构中,子 Widgets 能够共享父 Widgets 的上下文环境。

Flutter 中的 Widgets 跟其余语言中的相似的 Widgets 组合有什么不同呢?

他们最大的不同是,Flutter 中的 Widgets 更多,每个 Widgets 专一的性能更小。即使是一个很小很小性能,在 Flutter 中都能够找到与之对应的 Widgets。

这样做的益处就是,你能够应用不同的,十分根底的 Widgets 任意组合,从而构建出非常复杂的,个性化的大的 Widgets。

当然,它的毛病也非常明显,就是代码外面的 Widgets 太多了,导致代码中的层级构造特地的多,可能会看的目迷五色。

举个简略的例子,Container 是 flutter 提供的一个根本的容器 Widget, 咱们通常这样来应用它:

 Container(
   constraints: BoxConstraints.expand(height: Theme.of(context).textTheme.headline4!.fontSize! * 1.1 + 200.0,
   ),
   padding: const EdgeInsets.all(8.0),
   color: Colors.blue[600],
   alignment: Alignment.center,
   child: Text('Hello World',
     style: Theme.of(context)
         .textTheme
         .headline4!
         .copyWith(color: Colors.white)),
   transform: Matrix4.rotationZ(0.1),
 )

咱们向 Container 中传入了 constraints,padding,color,alignment,child,transform 等信息。

咱们先来猜一下,这些信息中,哪些是用来构建 Widget 的?

大家第一工夫想到的应该是 child, 它自身就是一个 Widget,用来示意 Container 中蕴含的子对象,这个很好了解。

然而,除了 child 这个 Widget 之外,其余的 constraints,padding,color,alignment,transform 等都是形成 Widget 的元素!

咱们来看下 Container 的 build 办法:

 Widget build(BuildContext context) {
    Widget? current = child;

    if (child == null && (constraints == null || !constraints!.isTight)) {
      current = LimitedBox(
        maxWidth: 0.0,
        maxHeight: 0.0,
        child: ConstrainedBox(constraints: const BoxConstraints.expand()),
      );
    }

    if (alignment != null)
      current = Align(alignment: alignment!, child: current);

    final EdgeInsetsGeometry? effectivePadding = _paddingIncludingDecoration;
    if (effectivePadding != null)
      current = Padding(padding: effectivePadding, child: current);

    if (color != null)
      current = ColoredBox(color: color!, child: current);

    if (clipBehavior != Clip.none) {assert(decoration != null);
      current = ClipPath(
        clipper: _DecorationClipper(textDirection: Directionality.maybeOf(context),
          decoration: decoration!,
        ),
        clipBehavior: clipBehavior,
        child: current,
      );
    }

    if (decoration != null)
      current = DecoratedBox(decoration: decoration!, child: current);

    if (foregroundDecoration != null) {
      current = DecoratedBox(
        decoration: foregroundDecoration!,
        position: DecorationPosition.foreground,
        child: current,
      );
    }

    if (constraints != null)
      current = ConstrainedBox(constraints: constraints!, child: current);

    if (margin != null)
      current = Padding(padding: margin!, child: current);

    if (transform != null)
      current = Transform(transform: transform!, alignment: transformAlignment, child: current);

    return current!;
  }

从代码中能够看到,Container 先是创立了 LimitedBox,而后将其嵌入到 Align 中,再顺次嵌入到 Padding,ColoredBox,ClipPath,DecoratedBox,ConstrainedBox,Padding 和 Transform 中。这些所有的对象都是 Widget。

这里应该能够了解 Flutter 中 Widget 的设计思维了。在 Flutter 中所有皆可为 Widget。

Widgets 的可扩展性

和其余的编译成原生语言个性的跨平台实现如 React native 相比,Flutter 对于每个 UI 都有本人的实现,而不是依赖于操作系统提供的接口。

这样做的益处就是一切都是由 Flutter 本人管制的,使用者能够在 Flutter 的根底上进行有限扩大,而不必受限于零碎底层的实现限度。

另一方面,这样能够缩小 Flutter 在出现过程中在 Flutter 代码和平台代码之间来回转换,缩小了性能瓶颈,晋升效率。

最初,因为 UI 的实现和底层的操作系统是拆散的,所以 Flutter 的 APP 在不同的平台下面能够有对立的外观和实现,能够保障格调的对立。

Widgets 的状态治理

Widgets 示意的是不可变的用户 UI 界面构造。尽管构造是不可能变动的,然而 Widgets 外面的状态是能够动态变化的。

依据 Widgets 中是否蕴含状态,Widgets 能够分为 stateful 和 stateless widget, 对应的类是 StatefulWidget 和 StatelessWidget。

对于有些 Widgets 来说,比方 icon 或者 Label,它外面自身就不须要状态,这些 Widgets 就是 StatelessWidget。

然而如果有些 Widgets 中的某些内容可能须要依据用户或者其余起因来动态变化,则就须要应用 StatefulWidget。

之前提到了 Widgets 是不可变的,StatefulWidget 中的可变数据是寄存在对应的 State 中的,所以 StatefulWidgets 自身并没有 build 办法,所有用户界面都是通过 State 对象来构建的。

当 State 发生变化的时候,须要调用 setState() 办法来告诉 flutter 框架来调用 State 的 build 办法,从而将变动反馈到用户界面中。

既然 StatefulWidget 是带有状态的,那么这些状态是怎么进行治理和传递的呢?

State 自身提供了一个 build 办法,用于构建初始的状态:

Widget build(BuildContext context);

如果在一个 StatefulWidget 中须要嵌入另外一个 StatefulWidget,那么能够在其对应的 State 中调用另外一个 StatefulWidget 的构造函数,将要传递的数据,以结构函数参数的模式传递给子 Widget。

当然这样做是没问题的。然而如果组件的嵌套层数过多的话,这种构造函数的传递形式,显然不能满足咱们的需要。

于是 Flutter 提供了一个 InheritedWidget 类,如果咱们自定义的类须要共享数据给子 Widgets,则能够继承 InheritedWidget。

Inherited widgets 有两个作用:第一,子 Widget 能够通过 Inherited widgets 提供的动态 of 办法拿到离他最近的父 Inherited widgets 实例。

第二,当 Inherited widgets 扭转 state 之后,会主动触发 state 消费者的 rebuild 行为。

先来看一下 inherited widgets 类的定义:

abstract class InheritedWidget extends ProxyWidget {const InheritedWidget({ Key? key, required Widget child})
    : super(key: key, child: child);

  @override
  InheritedElement createElement() => InheritedElement(this);

  @protected
  bool updateShouldNotify(covariant InheritedWidget oldWidget);
}

能够看到 InheritedWidget 是对理论 Widget 对象的代理,另外还将 InheritedWidget 封装到了 InheritedElement 中。

这里不多解说 InheritedElement,InheritedElement 是底层告诉机制的实现。

咱们看到 InheritedWidget 还增加了一个 updateShouldNotify,这个办法能够提供给咱们管制以后 InheritedWidget rebuilt 的时候,是否须要 rebuilt 继承它的子 Widget。

上面咱们看一个 InheritedWidget 的具体实现:

class FrogColor extends InheritedWidget {
  const FrogColor({
    Key? key,
    required this.color,
    required Widget child,
  }) : super(key: key, child: child);

  final Color color;

  static FrogColor of(BuildContext context) {final FrogColor? result = context.dependOnInheritedWidgetOfExactType<FrogColor>();
    assert(result != null, 'No FrogColor found in context');
    return result!;
  }

  @override
  bool updateShouldNotify(FrogColor old) => color != old.color;
}

FrogColor 中定义了一个 Color 属性,当 Color 发生变化的时候,就会调用 updateShouldNotify。

另外,FrogColor 还提供了一个 of 办法,承受的参数是 BuildContext,而后调用 context.dependOnInheritedWidgetOfExactType 去查找离该 context 最近的 FrogColor。

为什么要应用 of 办法对 context.dependOnInheritedWidgetOfExactType 进行封装呢?这是因为,context.dependOnInheritedWidgetOfExactType 办法不肯定可能找到要找的对象,所以咱们须要进行一些异样值的解决。

另外,有可能 of 办法返回的对象和 context.dependOnInheritedWidgetOfExactType 中查找的对象不一样,这都是能够的。

咱们看下 of 办法的具体应用:

class MyPage extends StatelessWidget {const MyPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: FrogColor(
        color: Colors.green,
        child: Builder(builder: (BuildContext innerContext) {
            return Text(
              'Hello Frog',
              style: TextStyle(color: FrogColor.of(innerContext).color),
            );
          },
        ),
      ),
    );
  }
}

还有一个问题,of 办法传入的是 BuildContext 对象,留神,这里的 BuildContext 必须是 InheritedWidget 对象自身的后辈,也就是说在对象树中,必须是 InheritedWidget 的子树。再看上面的例子:

class MyOtherPage extends StatelessWidget {const MyOtherPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: FrogColor(
        color: Colors.green,
        child: Text(
          'Hello Frog',
          style: TextStyle(color: FrogColor.of(context).color),
        ),
      ),
    );
  }
}

这个例子中,FrogColor.of 办法中的 context 是 FrogColor 的父 context, 所以是找不到 FrogColor 对象的,这样的应用是谬误的。

当然,除了 InheritedWidget,Flutter 还提供了很多状态治理的工具,比方 provider,bloc,flutter_hooks 等,也是十分好用的。

渲染和布局

渲染就是将下面咱们提到的 widgets 转换成用户肉眼能够感知的像素的过程。

Flutter 作为一种跨平台的框架,它和一般的跨平台的框架或者原生的框架有什么区别呢?

首先来考虑一下原生框架。以 android 为例,首先调用的是 andorid 框架的 java 代码,通过调用 android 零碎库提供的进行绘制的组件,最初调用底层的 Skia 来进行绘制。Skia 是一种用 C/C++ 编写的图形引擎,它调用 CPU 或 GPU 在设施上实现绘制。

那么常见的跨平台框架是怎么运行的呢?它们实际上在原生的代码框架下面又封装了一层。通常应用 javascript 这样的解释性语言来进行编写,而后编写的代码再和 andorid 的 JAVA 或者 IOS 的 Objective- C 零碎库进行交互。这样的后果就是在 UI 交互或者调用之间会造成显著的性能开销。这也就是通用的跨平台语言不如原生的性能好的起因。

然而 flutter 不一样,它并不是用零碎自带的 UI 控件,而是领有本人的实现。Flutter 代码会间接被编译成应用 Skia 进行渲染的原生代码, 从而晋升渲染效率。

接下来,咱们具体看一下 flutter 从代码到渲染的整个流程。首先看一段代码:

Container(
  color: Colors.blue,
  child: Row(
    children: [Image.network('http://www.flydean.com/1.png'),
      const Text('A'),
    ],
  ),
);

下面的代码是构建一个 Container widget。当 flutter 想要渲染这个 widget 的时候,会去调用 build() 办法,而后生成一个 widget 汇合.

为什么是 Widget 汇合呢?在下面咱们也剖析过,Container 这个 widget 是由很多个其余的 widget 组成的,所以,下面的 Container 会生成上面的 widget 树:

下面的就是代码中生成的 widget,这些 widget 在 build 的过程中,会被转换为 element tree。一个 element 和一个 widget 对应。

element 示意的 widget 的实例。flutter 中有两种类型的 element,别离是:ComponentElement 和 RenderObjectElement.

ComponentElement 是其余 Element 的容器,而 RenderObjectElement 是真正参加 layout 和渲染的 element。

因为 Widget 自身是不可变的,所以任何对于 Widget 的批改都会返回一个新的 Widget。那么是不是所有的变动,都会导致整个 element tree 从新渲染呢?

答案是不会的,flutter 仅会从新渲染须要被从新绘制的 element。

接下来,咱们看下渲染树是怎么构建的,渲染树中的每个元素叫做 RenderObject,它定义了布局和绘制的形象模型。

下面咱们提到的 RenderObjectElement 会在渲染的时候转换成为 RenderObject, 如下所示:

当然,不同的 Render element 会转换成为不同的 Render 对象。

总结

Widget 和 Layout 是咱们理论在做 flutter 开发的时候,常常须要应用到的局部,大家须要深刻理解和熟练掌握。

更多内容请参考 http://www.flydean.com/01-flutter-architectural/

最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!

欢送关注我的公众号:「程序那些事」, 懂技术,更懂你!

退出移动版