type
status
date
slug
summary
tags
category
icon

Flutter 入门介绍

对于开发者而言,Flutter 是什么?他是用什么语言编写的?包含那几部分?是如何运行到设备上的?Flutter 工程和 Native 工程有何差别?支持热跟新吗?Flutter 界面是如何构建的...
这里对 Flutter 做一个大致方向的介绍,让你开始了解它
<!-- more -->

Flutter 是什么

Flutter 是一个使用 Dart 语言开发的跨平台移动 UI 框架,通过自建的绘制引擎,能高性能,高保证地进行 Android 和 iOS 开发
在 Flutter 出现之前,已经有了很多跨平台 UI 框架:React Native、Weex 等。但是 Weex 和 React Native 都存在两个问题:
  1. 性能不如原生:主要开销耗费在 JSBridge 的沟通上
  1. 平台相关性太强:框架最后生成的都是平台相关的控件,两端有差异,兼容性不佳,需要处理大量平台相关的逻辑
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. 健全的类型系统,同时支持静态类型检查和运行时类型检查。
  1. 代码体积优化(Tree Shaking),编译时只保留运行时需要调用的代码(不允许反射这样的隐式引用),所以庞大的 Widgets 库不会造成发布体积过大。
  1. 丰富的底层库,Dart 自身提供了非常多的库。
  1. 多生代无锁垃圾回收器,专门为 UI 框架中常见的大量 Widgets 对象创建和销毁优化。
  1. 跨平台,iOS 和 Android 共用一套代码。
  1. JIT & AOT运行模式,支持开发时的快速迭代和正式发布后最大程度发挥硬件性能。
在 Dart 中,有一些重要的基本概念需要了解:
  • 所有变量的值都是对象,也就是类的实例。甚至数字、函数和 null 也都是对象,都继承自 Object
  • 虽然 Dart 是强类型语言,但是显式变量类型声明是可选的,Dart 支持类型推断。如果不想使用类型推断,可以用 dynamic 类型
  • Dart 支持泛型,List<int> 表示包含 int 类型的列表,List<dynamic> 则表示包含任意类型的列表,(Dart 中不存在类型擦除现象)
  • Dart 支持顶层(top-level)函数和类成员函数,也支持嵌套函数和本地函数
  • Dart 支持顶层变量和类成员变量
  • Dart 没有 public、protected 和 private 这些关键字,使用下划线“_”开头的变量或者函数,表示只在库内可见。参考 库和可见性
DartVM 的内存分配策略非常简单,创建对象时只需要在现有堆上移动指针,内存增长始终是线形的,省去了查找可用内存段的过程
notion image
Dart 中类似线程的概念叫做 Isolate,每个 Isolate 之间是无法共享内存的,所以这种分配策略可以让 Dart 实现无锁的快速分配
关于 Isolate,在下文介绍 Flutter 编译产物的时候会解说
Dart 的垃圾回收也采用了多生代算法,新生代在回收内存时采用了“半空间”算法,触发垃圾回收时 Dart 会将当前半空间中的“活跃”对象拷贝到备用空间,然后整体释放当前空间的所有内存:
notion image
整个过程中 Dart 只需要操作少量的“活跃”对象,大量的没有引用的“死亡”对象则被忽略,这种算法也非常适合 Flutter 框架中大量 Widget 重建的场景
Flutter 中的 Widget 是不可改变的,所以当内容发生变化需要重建

Flutter 架构介绍

notion image
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 的工程目录结构如下:
notion image
lib 目录下面包含了当前工程 Flutter 的业务 dart 代码 ios 和 android 就是标准的 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 模式下的编译

notion image
这里先介绍下 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 下面:
notion image
apk 新安装后,会根据一个判断逻辑(packageinfo 中的 versionCode 结合 lastUpdateTime)来决定是否拷贝 APK 中的 assets 到 app 的私有缓存目录

Debug 模式下的编译

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

Flutter 的运行

在 Android 上,Flutter 的布局是显示在一个 FlutterView 上面的,FlutterView 是一个 SurfaceView,对于 Flutter 而言,它只是个画布(这也是为什么 Flutter 平台无关性很强)
Flutter app 是由 Widget 组成的
我们用 dart 写页面其实就是写 Widget
Widget 不等于 View,他是轻量级的,而且不是可编辑的,一旦创建了就不变了
最后 Flutter 把当前页面的 Widget 转换成 RenderObject 树,RendObject 才是真正绘制在屏幕上的对象

Widget 介绍

关于 Wdiget 的介绍闲鱼出过一片很好的入门文章 Flutter快速上车之Widget,可以通过这篇文章了解 flutter 的 Widget
Flutter混合栈管理探索阻尼动画优雅的实现方式
Loading...