爆裂吧现实

不想死就早点睡

0%

Flutter入门介绍

Flutter 入门介绍

对于开发者而言,Flutter 是什么?他是用什么语言编写的?包含那几部分?是如何运行到设备上的?Flutter 工程和 Native 工程有何差别?支持热跟新吗?Flutter 界面是如何构建的…

这里对 Flutter 做一个大致方向的介绍,让你开始了解它

Flutter 是什么

Flutter 是一个使用 Dart 语言开发的跨平台移动 UI 框架,通过自建的绘制引擎,能高性能,高保证地进行 Android 和 iOS 开发

在 Flutter 出现之前,已经有了很多跨平台 UI 框架:React Native、Weex 等。但是 Weex 和 React Native 都存在两个问题:

  1. 性能不如原生:主要开销耗费在 JSBridge 的沟通上
  2. 平台相关性太强:框架最后生成的都是平台相关的控件,两端有差异,兼容性不佳,需要处理大量平台相关的逻辑

Flutter 开辟了一种全新的方式,为了解决上面两个问题,Flutter 从头到尾重写了一套跨平台的 UI 框架,包括 UI 控件,渲染逻辑甚至开发语言。渲染引擎依靠跨平台的Skia图形库来实现,依赖系统的只有图形绘制相关的接口,可以在最大程度上保证不同平台、不同设备的体验一致性,逻辑处理使用支持AOT的Dart语言,执行效率也比JavaScript高得多。

Flutter 除了可以在 google 自家的 Android Studio 上开发,也可以在 VisualStudio Code 上开发

Dart 语言简介

Dart 是一种强类型、跨平台的客户端开发语言。具有专门为客户端优化、高生产力、快速高效、可移植(兼容ARM/x86)、易学的OO编程风格和原生支持响应式编程(Stream & Future)等优秀特性。Dart 主要由 Google 负责开发和维护,在 2011 年 10 启动项目,2017 年 9 月发布第一个2.0-dev 版本

Flutter 在筛选了 20 多种语言后,最终选择 Dart 作为开发语言主要有几个原因:

  1. 健全的类型系统,同时支持静态类型检查和运行时类型检查。
  2. 代码体积优化(Tree Shaking),编译时只保留运行时需要调用的代码(不允许反射这样的隐式引用),所以庞大的 Widgets 库不会造成发布体积过大。
  3. 丰富的底层库,Dart 自身提供了非常多的库。
  4. 多生代无锁垃圾回收器,专门为 UI 框架中常见的大量 Widgets 对象创建和销毁优化。
  5. 跨平台,iOS 和 Android 共用一套代码。
  6. JIT & AOT运行模式,支持开发时的快速迭代和正式发布后最大程度发挥硬件性能。

在 Dart 中,有一些重要的基本概念需要了解:

  • 所有变量的值都是对象,也就是类的实例。甚至数字、函数和 null 也都是对象,都继承自 Object
  • 虽然 Dart 是强类型语言,但是显式变量类型声明是可选的,Dart 支持类型推断。如果不想使用类型推断,可以用 dynamic 类型
  • Dart 支持泛型,List<int> 表示包含 int 类型的列表,List<dynamic> 则表示包含任意类型的列表,**(Dart 中不存在类型擦除现象)**
  • Dart 支持顶层(top-level)函数和类成员函数,也支持嵌套函数和本地函数
  • Dart 支持顶层变量和类成员变量
  • Dart 没有 public、protected 和 private 这些关键字,使用下划线“_”开头的变量或者函数,表示只在库内可见。参考 库和可见性

DartVM 的内存分配策略非常简单,创建对象时只需要在现有堆上移动指针,内存增长始终是线形的,省去了查找可用内存段的过程

Dart 中类似线程的概念叫做 Isolate,每个 Isolate 之间是无法共享内存的,所以这种分配策略可以让 Dart 实现无锁的快速分配

关于 Isolate,在下文介绍 Flutter 编译产物的时候会解说

Dart 的垃圾回收也采用了多生代算法,新生代在回收内存时采用了“半空间”算法,触发垃圾回收时 Dart 会将当前半空间中的“活跃”对象拷贝到备用空间,然后整体释放当前空间的所有内存:

整个过程中 Dart 只需要操作少量的“活跃”对象,大量的没有引用的“死亡”对象则被忽略,这种算法也非常适合 Flutter 框架中大量 Widget 重建的场景

Flutter 中的 Widget 是不可改变的,所以当内容发生变化需要重建

Flutter 架构介绍


Flutter 的架构主要分为三层:

  • Framework:使用 dart 实现,包括 Material Design 风格的Widget, Cupertino(针对iOS) 风格的 Widgets,文本/图片/按钮等基础 Widgets,渲染,动画,手势等

  • Engine: 使用 C++ 实现,主要包括: Skia, Dart 和 Text。

    • Skia 是开源的二维图形库,提供了适用于多种软硬件平台的通用 API。其已作为 Google Chrome,Chrome OS,Android, Mozilla Firefox, Firefox OS 等其他众多产品的图形引擎
    • Dart 部分主要包括:Dart Runtime,Garbage Collection(GC),如果是Debug模式的话,还包括JIT(Just In Time)支持。Release和Profile模式下,是AOT(Ahead Of Time)编译成了原生的arm代码,并不存在JIT部分。
    • Text 即文本渲染,其渲染层次如下:衍生自 minikin 的libtxt 库(用于字体选择,分隔行);HartBuzz用于字形选择和成型; Skia 作为渲染/GPU后端,在 Android 和 Fuchsia 上使用FreeType 渲染,在 iOS 上使用 CoreGraphics 来渲染字体。
  • Embedder 层是一个嵌入层,即把 Flutter 嵌入到各个平台上去,这里做的主要工作包括渲染 Surface 设置,线程设置,以及插件(这里的插件是指 Flutter 和 Native 交互通道)等。从这里可以看出,Flutter 的平台相关层很低,平台 (如 iOS) 只是提供一个画布,剩余的所有渲染相关的逻辑都在 Flutter 内部,这就使得它具有了很好的跨端一致性。

Flutter 工程结构

这里用 flutter beta v0.8.2,以 hello_flutter 工程为例,Flutter 的工程目录结构如下:

lib 目录下面包含了当前工程 Flutter 的业务 dart 代码
iosandroid 就是标准的 native 开发工程
pubspec.yaml 是pub依赖管理,用于管理 dart 依赖

Flutter 模式

对于 Flutter,它支持常见的 debug, release 模式,多了一个profile 模式,但它又有些不一样。

Debug 模式: 对应了 Dart 的 JIT 模式,又称检查模式或者慢速模式。支持设备,模拟器 (iOS/Android),此模式下打开了断言,包括所有的调试信息。此模式为快速开发和运行做了优化,但并未对执行速度(页面达不到 60fps),包大小和部署做优化。Debug 模式下,编译使用 JIT 技术,支持广受欢迎的亚秒级有状态的 hot reload

Release 模式: 对应了 Dart 的 AOT 模式,此模式目标即为部署到终端用户。只支持真机,不包括模拟器。关闭了所有断言,尽可能多地去掉了调试信息,关闭了所有调试工具。为快速启动,快速执行,包大小做了优化。禁止了所有调试辅助手段,服务扩展。

Profile 模式: 类似 Release 模式,只是多了对于 Profile 模式的服务扩展的支持,支持跟踪,以及最小化使用跟踪信息需要的依赖,例如,observatory可以连接上进程。Profile 并不支持模拟器的原因在于,模拟器上的诊断并不代表真实的性能。

Profile同Release在编译原理等上无差异

Flutter 编译和运行(Android)

Release 模式下的编译


这里先介绍下 dart 编译出来的四个文件是啥, vm 开头的是虚拟机相关的,isolate 开头的对应我们应用的 dart 代码

  • vm_snapshot_data: 表示隔离区之间共享的 Dart 堆的初始状态,只读属性不可写。这里的隔离区就是指 isolate,isolate 在 dart 中起到线程的作用(dart 中没有线程的概念),和线程的区别在于:线程间是内存共享的,isolate 之间是内存不共享的,后续会解释一波 isolate
  • vm_snapshot_instr: 包含 VM 中所有 Dart 隔离区之间共享的常用的 AOT 指令
  • isolate_snapshot_data: 表示一个隔离区的 Dart 堆初始状态
  • isolate_snapshot_instr: 隔离区执行用的 AOT 代码

VM snapshot 可以被所有的 隔离区(isolate) 使用,所以 VM snaoshot 在 Dart VM 初始化的时候就加载好了

关于隔离区(isolate),这里附上我的理解:它是一个在隔离环境运行的程序,拥有自己的堆栈,自己的线程,一个隔离区的代码总是顺序执行的(理解为单线程),所以 dart 里面没有线程这个东西,取而代之的是 isolateisolate 之间内存不共享,隔离区之间通信方式是消息通信。

因为隔离区之间内存不共享,所以这里有个很大的好处:无锁并发,dart 里面没有锁


flutter.jar 是 Engine 编译出来的,内部含有 libflutter.so , jar 里面还包含了 Embedder 部分的内容 (FlutterMain, FlutterView, FlutterNativeView)

实际项目并不会编译,而是直接使用 flutter 仓库里面编译好的,位于 /bin/cache/artifacts/engine/android*


assets 中包含了 flutter 需要的资源文件,包括:图片,字体…


最后打包成 apk 的时候,snapshot 文件和 assets 文件都会拷贝到 app 中的 assets 目录里面,so 文件放到 lib 下面:

apk 新安装后,会根据一个判断逻辑(packageinfo 中的 versionCode 结合 lastUpdateTime)来决定是否拷贝 APK 中的 assets 到 app 的私有缓存目录


Debug 模式下的编译

Debug 模式相比较于 Release 模式主要有两点不一样:

  1. flutter.jar: 因为是 Debug,此模式下 jar 是有 JIT 支持的,Release 模式下没有 JIT 部分
  2. 没有了 xx_snapshot_instr 指令:指令中包含的是 AOT 指令,所以在 Debug 模式下这部分是没有的,取而代之的是一个 kernel_blob.bin 文件,这部分的 snapshot 是脚本快照,里面是简单的标记化的源代码。所有的注释,空白字符都被移除,常量也被规范化,没有机器码

Flutter 的运行

在 Android 上,Flutter 的布局是显示在一个 FlutterView 上面的,FlutterView 是一个 SurfaceView,对于 Flutter 而言,它只是个画布(这也是为什么 Flutter 平台无关性很强)

Flutter app 是由 Widget 组成的

1
Everthing's a Widget (image, layout, gesture...)

我们用 dart 写页面其实就是写 Widget

Widget 不等于 View,他是轻量级的,而且不是可编辑的,一旦创建了就不变了

最后 Flutter 把当前页面的 Widget 转换成 RenderObject 树,RendObject 才是真正绘制在屏幕上的对象

Widget 介绍

关于 Wdiget 的介绍闲鱼出过一片很好的入门文章 Flutter快速上车之Widget,可以通过这篇文章了解 flutter 的 Widget

参考文章:
Flutter原理与美团的实践
Flutter快速上车之Widget
深入理解 Flutter 的编译原理与优化
Flutter Engine Wiki
Flutter Engine Operation in AOT Mode