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/

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!

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

原文地址:https://www.cnblogs.com/flydean/p/16588500.html

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


简介 java中使用jar包来封装有用的功能,然后将其分发到maven仓库中,供其他人使用。同样的在dart中也有类似的概念叫做packages。packages就是可以用来共享的软件包,可以包含libraries和tools。 你可以在pub.dev网站中查到dart中所有的共享packages的
简介 flutter是google在2015年dart开发者峰会上推出的一种开源的移动UI构建框架,使用flutter可以非常方便的编译成运行在原始android,ios,web等移动平台上的移动应用。 flutter是使用dart来编写的,最新的flutter版本是2.5.3,而最新的Dart语言
简介 dart作为一种面向对象的语言,class是必不可少的。dart中所有的class,除了Null都继承自Object class。 要想使用dart中的类就要构造类的实例,在dart中,一个类的构造函数有两种方式,一起来看看吧。 传统的构造函数 和JAVA一样,dart中可以使用和class名
简介 Exception是程序中的异常情况,在JAVA中exception有checked Exception和unchecked Exception。那么在dart中的情况是不是一样的呢?一起来看看吧。 Exception和Error Dart中表示异常的类有两个,分别是Exception和Err
简介 虽然dart中的类只能有一个父类,也就是单继承的,但是dart提供了mixin语法来绕过这样限制。 今天,和大家一起来探讨一下dart类中的继承。 使用extends 和JAVA一样,dart中可以定义一个父类,然后使用extends来继承他,得到一个子类,如下所示: class Studen
简介 pubspec.yaml是所有dart项目的灵魂,它包含了所有dart项目的依赖信息和其他元信息,所以pubspec.yaml就是dart项目的meta! pubspec.yaml支持的字段 根据dart的定义,pubspec.yaml中可以包含下面的字段: 字段名 是否必须字段 描述 nam
dart系列之:dart语言中的特殊操作符 简介 有运算就有操作符,dart中除了普通的算术运算的操作符之外,还有自定义的非常特殊的操作符,今天带大家一起来探索一下dart中的特殊操作符。 普通操作符 普通操作符就很好解释了,就是加减乘除,逻辑运算符,比较运算符和位运算符等。 这些操作符和其他语言的
简介 在dart系统中,有pubspec.yaml文件的应用就可以被成为一个package。而Libray package是一类特殊的package,这种包可以被其他的项目所依赖. 也就是通常所说的库。 如果你也想你写的dart程序可以上传到pub.dev上,或者提供给别人使用,则来看看这篇文章吧。
简介 和所有的编程语言一样,dart有他内置的语言类型,这些内置类型都继承自Object,当然这些内置类型是dart语言的基础,只有掌握了这些内置类型才能够在使用dart语言的时候得心应手。 今天就给大家讲解一下dart语言的内置类型。 Null 在dart中用null来表示空。那么null和Nul
简介 函数是所有编程语言都有的内容,不管是面向对象还是面向过程,函数都是非常重要的一部分。dart中的函数和java中的函数有什么区别呢? dart作为一种面向对象的编程语言,它的函数也是一个对象,用Function来表示。先看下函数的定义: abstract class Function { ex
简介 熟悉JAVA的朋友可能知道,JAVA在8中引入了泛型的概念。什么是泛型呢?泛型就是一种通用的类型格式,一般用在集合中,用来指定该集合中应该存储的对象格式。 有了泛型可以简化我们的编程,并且可以减少错误的产生,非常的方便。 dart语言中也有泛型。一起来看看吧。 为什么要用泛型 使用泛型的主要目
简介 熟悉javascript的朋友应该知道,在ES6中引入了await和async的语法,可以方便的进行异步编程,从而摆脱了回调地狱。dart作为一种新生的语言,没有理由不继承这种优秀的品质。很自然的,dart中也有await和async语言,一起来看看吧。 为什么要用异步编程 那么为什么要用异步
简介 要想熟悉一种语言,最简单的做法就是熟悉dart提供的各种核心库。dart为我们提供了包括dart:core,dart:async,dart:math,dart:convert,dart:html和dart:io这几种常用的库。 今天给大家介绍一下dart:core中的数字和字符串的使用。 数字
简介 ES6中在引入异步编程的同时,也引入了Generators,通过yield关键词来生成对应的数据。同样的dart也有yield关键词和生成器的概念。 什么时候生成器呢?所谓生成器就是一个能够持续产生某些数据的装置,也叫做generator。 两种返回类型的generator 根据是同步生成还是
简介 Flutter的基础是widget,根据是否需要跟用户进行交互,widget则可以分为StatelessWidget和StatefulWidget。StatelessWidget只能根据传入的状态进行简单的初始化widget,如果要实现跟用户交互这种复杂的功能,则需要用到StatefulWid
简介 时间和日期是我们经常会在程序中使用到的对象。但是对时间和日期的处理因为有不同时区的原因,所以一直以来都不是很好用。就像在java中,为时间和日期修改和新增了多次API,那么作为新生的语言dart而言,会有什么不一样的地方吗? dart中关于日期和时间的两个非常重要的类是DateTime和Dur
简介 Library是dart用来组织代码的一种非常有用的方式,通过定义不同的Library,可以将非常有用的dart代码进行封装,从而提供给其他的项目使用。虽然我们可以自由使用import或者export来对library进行导入和导入。但是什么样的用法才是最合适的用法呢? 一起来看看吧。 使用p
简介 dart中的集合有三个,分别是list,set和map。dart在dart:core包中提供了对于这三种集合非常有用的方法,一起来看看吧。 List的使用 首先是list的创建,可以创建空的list或者带值的list: var emptyList =[]; var nameList = [&#
简介 dart:html包为dart提供了构建浏览器客户端的一些必须的组件,之前我们提到了HTML和DOM的操作,除了这些之外,我们在浏览器端另一个常用的操作就是使用XMLHttpRequest去做异步HTTP资源的请求,也就是AJAX请求。 dart同样提供了类似JS中XMLHttpRequest
简介 Flutter是google开发的一个跨平台的UI构建工具,flutter目前最新的版本是3.0.5。使用flutter你可以使用一套代码搭建android,IOS,web和desktop等不同平台的应用。做到一次编写到处运行的目的。 说到一次编写处处运行,大家可能会想到java。那么flut