iOS老司机万字整理, 可能是最全的Flutter Tips

908 阅读17分钟

我正在参加「掘金·启航计划」

1. 什么是Flutter?

1.1 Flutter介绍

1.1.1 官方英文介绍

image.png

  • Flutter是一个UI SDK(Software Development Kit).
  • 可以进行移动端、Web端、桌面应用开发的跨平台解决方案, 目标是一统大前端.
  • 目前使用Flutter技术的大厂: Google、阿里闲鱼团队、腾讯、字节...

1.1.2 特点

  1. 美观
  • 使用Flutter内置美观的Material Design和Cupertino Widget、丰富的motion API、平滑自然的滑动效果和平台感知, 为用户带来流畅的操作体验.
  1. 快速
  • Flutter的UI渲染性能很好. 在生产环境下, Flutter将代码编译成机器码执行, 并充分利用GPU的图形加速能力, 因此使用Flutter开发的移动应用即使在低配手机上也能实现流畅的UI渲染速度(16.7ms完成一帧渲染).
  • Flutter引擎用C++编写, 包括高效的Skia 2D渲染引擎、Dart运行时和文本渲染库.
  1. 高效
  • 把Hot Reload(热重载)技术由前端带入到移动端(( Ĭ ^ Ĭ )).
  1. 开放
  • 由Google主导, 完全开源的项目.

1.2 跨平台方案对比

1.2.1 基于JavaScript和WebView的跨平台方案

  • 代表框架有PhoneGap、Apache Cordova、Ionic...
  • 主要是通过H5来构建页面, 再将其显示在各个平台的WebView中.
  • 但是它默认是不能调用Native的一些服务的(如相机、蓝牙等硬件), 所以需要通过JavaScript进行桥接调用Native的一些API来完成某些基于硬件的功能.
  • 本身的用户体验、性能相对Native控件并不是太流畅, Native更新迭代的过程中容易产生很多适配问题. image.png

1.2.2 久经考验的React Native

  • React Native是Facebook在2015.4开源出来的跨平台开发框架, 是FB早先开源的JS框架React在原生移动应用平台的衍生应用.
  • React Native使用JS语言, 类似于H5的JSX, 以及CSS来开发移动App, 降低了使用React框架的H5前端同学的学习成本.
  • 在保留基本渲染能力的基础上, 用原生自带的UI组件实现核心渲染引擎, 从而保证了良好的渲染效果.
  • 但是, 由于React Native的本质是通过JavaScript VM调用原生接口, 相对Native组件通信较低效, 而且RN框架本身不负责渲染, 是间接通过原生进行渲染的.(参考大厂Airbnb由RN转向Native.)

1.2.3 Google主推的跨平台解决方案Flutter

image.png

  • Flutter使用Skia渲染引擎, 直接通过CPU、GPU进行图形绘制, 不需要依赖任何Native控件.
  • Android操作系统中, 我们编写的Native控件实际上也是依赖于Skia进行绘制渲染, 所以Flutter在某些Android小K做系统上, 甚至还要高于安卓原生(因为安卓原生中的Skia必须随着操作系统进行更新, 二Flutter SDK中总是保持最新的Skia引擎).
  • 而类似于React Native框架, 必须通过桥接操作先转成Native控件进行调用, 之后再进行渲染.

1.2.3.1 Flutter图形绘制原理

image.png

  1. GPU将信号同步到UI线程
  2. UI线程用Dart来构建图层树
  3. 图层树在GPU线程进行合成
  4. 合成后的视图数据提供给Skia引擎
  5. Skia引擎通过OpenGL或者Vulkan将显示内容提供给GPU
  • 这也是Flutter相较React Native的本质区别
  1. React Native之类的解决方案, 只是通过JavaScript VM虚拟机桥接调用Native组件, 由iOS和Android系统进行Native组件的渲染.
  2. Flutter是自己完成了组件渲染的闭环.

1.2.3.2 Skia渲染引擎的前世今生

  • 最初由Skia公司研发, 后被Google收购.
  • Skia(Skia Graphics Library SGL)就是Flutter向GPU提供数据的途径.
  • 是一个由C++编写的开源图形库.
  • 目前Skia已经是Android官方的图像渲染引擎了, 因此Flutter Android SDK无需内嵌Skia引擎就可以获得Skia引擎支持.
  • 对于iOS端, 由于Skia是跨平台的, 因此它作为Flutter iOS渲染引擎被嵌入到Flutter的iOS SDK中, 替代了iOS闭源的Core Graphics/Core Animation等框架, 这也是Flutter iOS SDK打包的App IPA包体积比较大的原因.
  • 打通了底层渲染方式后, 上层开发接口和功能体验就统一了, 开发者从各个Native的不同渲染特性中解放了出来. Skia引擎保证了同一套代码调用在iOS和Android及各端平台上的渲染效果是完全一致的.

2. Flutter基操知识点

2.1 Dart是值传递还是引⽤传递?

  • dart中基本数据类型是值传递,类是引⽤传递

2.2 描述Flutter的核⼼渲染模块三棵树

  • ⾸先核⼼渲染模块的三棵树分别是WidgetTree,ElementTree和RenderTree。
  • WidgetTree:存放渲染内容,它只是⼀个配置数据结构,创建是⾮常轻量的,在⻚⾯刷新的过程中随时会重建
  • Element Tree:是分离WidgetTree和真正的渲染对象的中间层,WidgetTree⽤来描述对应的Element属性。 element同时持有Widget和RenderObject,存放上下⽂信息,通过它来遍历视图树,⽀撑UI结构。
  • RenderObject⼜称渲染树,⽤于应⽤界⾯的布局和绘制,负责真正的渲染。保存了元素的⼤⼩,布局 等信息。实例化⼀个RenderObject是⾮常耗能的。
  • 当应⽤启动时,Flutter会遍历并创建所有的Widget形成WidgetTree。通过调⽤widget上的 createElement⽅法创建每个Element对象,形成Element tree。最后调⽤Element的createRenderObject⽅法创建每个渲 染对象,形成⼀个Render Tree

2.3 Flutter中Widget的分类

  • 组合类:StatelessWidget和StatefulWidget
  • 代理类:InheritedWidget,ParentDataWidget。 其中InheritedWidget⼀般⽤于数据共享。
  • 绘制类:RenderObjectWidget RenderObject的布局相关⽅法调⽤顺序是:layout -> performResize -> performLayout -> markNeedsPaint.

2.4 mixin, extends, implement之间的关系?

  • mixin是混⼊,extends是继承,implement是实现这三者可以同时存在,
  • 前后顺序是extends -》mixins -》implements
  • ⼀个类可以通过extends来继承另⼀个类,但是 Flutter是单继承,所以如果要想同时拥有多个类的⽅法,可以使⽤混⼊。
  • 但是使⽤混⼊需要满⾜:mixins类只能继承⾃object,mixin类不能有构造函数。 ⼀个类可以混⼊多个mixin类。

2.5 简述Dart语⾔特性

  • 在Dart中,⼀切都是对象,所有的对象都继承⾃Object,除了开启空安全的null。
  • Dart是强类型语⾔。使⽤var来声明的变量会⾃动推断类型。
  • 没有赋值的变量默认都会有默认值null,在开启空安全后,变量默认都是有值的,如果要使⽤null需要在类型后添加? Dart⽀持顶层⽅法,如果main⽅法,可以在⽅法内部创建⽅法。
  • Dart⽀持顶层变量,也⽀持类变量或实例变量 Dart没有public,prviate等关键字,如果某个变量以_开头,代表这个变量在库中是私有的。

2.6 Dart中的级联操作符

  • dart的级联操作符是..,或者?.., 调⽤级联操作符返回的是对象。

2.7 Dart的单线程模型是如何运⾏的

  • Dart在单线程中是以消息循环机制来运⾏的。
  • 包含两个任务队列,⼀个是微任务队列(microtask queue),⼀个是事件队列(event queue) 当flutter应⽤启动后,消息循环机制便开启了。
  • 按照先进先出的顺序逐个执⾏微任务列表中的任务。
  • 当所有微任务列表执⾏完后便开始执⾏事件列表中的任务。
  • 事件任务执⾏完毕后再去执⾏微任务,如此循环往复:
1 import "dart:async"; 
2 main(List args) {
    3 // 执⾏到这⾥,直接打印 main start 
    4 print("main start");
    5 // 执⾏到这⾥,将打印task1的任务添加到event queue中 
    6 Future(() => print("task1")); 
    7 // 创建future对象,并添加() => null这个任务到event queue中 
    8 final future = Future(() => null); 
    9 // 添加打印task2的任务到 event queue中 
    10 // 添加打印task3的任务到 event queue中,并在打印task3之后将打印task4的任务放⼊microtask q 
    11 // 添加打印task5的任务到 event queue中 
    12 Future(() => print("task2")).then((_) { 
    13 print("task3"); 
    14 scheduleMicrotask(() => print('task4')); 
    15 }).then((_) => print("task5")); 
    16 // 添加打印task6的任务到queue中 
    17 future.then((_) => print("task6")); 
    18 // 添加打印task7的任务到microtask queue中 
    19 scheduleMicrotask(() => print('task7')); 
    20 // 添加打印task8的任务到event queue中 
    21 // 添加打印task9的任务到event queue中 
    22 // 添加打印task10的任务到event queue中 
    23 Future(() => print('task8')) 
    24 .then((_) => Future(() => print('task9')))
    25 .then((_) => print('task10')); 
    26 // 直接打印 main end 
    27 print("main end"); 
    28 }
    29 // 所以按照对于同步任务⽴刻执⾏,异步任务添加到任务队列,稍后按照FIFO执⾏的原则,打印顺序应该是 
    30 // 所以两个同步执⾏的任务会⽴即打印 main start--main end 
    31 // 然后执⾏microtask queue中的任务 所以会打印task7
    32 // 然后执⾏event queue中的任务,打印task1,然后调⽤() => null,打印task6 
    33 // 然后task2--task3--task5 
    34 // 然后microtask queue中⼜有了新的任务,会被执⾏task4 
    35 // 然后执⾏event queue中的 task8--task9--task10 
    36 } Stream默认是处于单订阅模式。
    所以同⼀个stream上的listen和其它⼤多数⽅法只能调⽤⼀次,调⽤ 第⼆次就会报错 但Stream可以通过transform⽅法返回⼀个Stream进⾏连续调⽤。 
    调⽤Stream.asBroadcastStream可以将⼀个单订阅模式的Stream转换成⼀个多订阅模式的Stream。 

2.8 await for 与 stream流

1 Stream stream = new Stream.fromIterable(['1', '2', '3', '4']); 
2 main() async{ 
3 print('start'); 
4 await for(String s in stream){ 
5 print(s); 
6 } 
7 print('end..'); 
8 } 
9 ------输出------- 10 start 11 1 12 2 13 3 14 4 15 end.. await for⼀般⽤在直到Stream什么时候完成,并且必须等待传递完成之后才能使⽤。不然就会⼀直阻 塞。

2.9 Stream与Future是什么关系 stream和Future都是⽤来处理⼀步的⼯具。

  1. Future表示稍后获得的⼀个数据,所有异步的操作的返回值都⽤Future来表示
  2. Future只能表示⼀次异步获得的数据。
  3. Stream表示多次异步获得的数据,⽐如界⾯上的按钮可能会被⽤户点击多次,按钮上的点击事件 就是⼀个Stream。
  4. Future将返回⼀个值,⽽Stream将返回多次值
  5. Dart中统⼀使⽤Stream处理异步事件流。

2.10 Stream有哪两种订阅模式?分别是怎么调⽤的?

  • Stream有两种订阅模式,分别是单订阅(single)和多订阅(broadcast)。
  • 单订阅就是只能有⼀个订阅者,⽽⼴播可以有多个订阅者。

2.11 Flutter中Widget,State,Context的核⼼概念?是为了解决什么问题?

  • 主要是为了解决多个部件之间的交互和部件⾃身状态的维护
  • Widget:在Flutter中,⼏乎所有东⻄都是Widget,Widget以树形结构进⾏组织,称为Widget树
  • Context:Context⽤来表示某个widget在widget树的位置引⽤,⼀个Context只从属于⼀个widget State:定义StatefulWidget实例的⾏为。
  • 创建⼀个StatefulWidget实例,会创建StatefulWidget类和 State类。 它包含了⽤于交互/⼲预Widget信息的⾏为和布局。

2.12 Dart异步编程中Future关键字?

  • Dart中执⾏⼀个异步任务使⽤Future来处理。
  • Dart是单线程的,它是通过维护⼀个消息事件循环来实 现异步操作的。

2.13 Flutter中Widget的⽣命周期

2.14 Widget的key

  • key本身是⼀个抽象类,它的直接⼦类主要有LocalKey和GlobalKey
  • LocalKey:它应⽤于具有相同⽗Element的widget进⾏⽐较,也就是diff算法的核⼼所在。
  • LocalKey 有三个⼦类:ValueKey,ObjectKey,UniqueKey.
  • GlobalKey:全局唯⼀的,通常我们会使⽤GlobalKey获取某个widget对应的wiget或者state或者 Element LocalKey有三个⼦类
    • ValueKey: ValueKey是当我们以特定的值作为key时使⽤,⽐如⼀个字符串、数字等等
    • ObjectKey: 如果两个学⽣,他们的名字⼀样,使⽤name作为他们的key就不合适了 我们可以创建出⼀个学⽣对象,使⽤对象来作为key
    • UniqueKey 如果我们要确保key的唯⼀性,可以使⽤UniqueKey; ⽐如我们之前使⽤随机数来保证key的不同,这⾥我们就可以换成UniqueKey;

2.15 Flutter是怎么完成组件渲染的?

  • 在计算机系统中,图像的显示需要CPU、GPU和显示器⼀起配合完成CPU负责图像数据计算,GPU负责图像数据渲染,⽽显示器则负责最终图像显示。
  • CPU把计算好的、需要显示的内容交给GPU,由GPU完成渲染后放⼊帧缓冲区,随后视图控制器根据垂直同步信号以每秒60次的速度,从帧缓冲区读取帧数据交由显示器完成图像显示。
  • 操作系统在呈现图像时遵循了这种机制。
  • ⽽Flutter作为跨平台开发框架也采⽤了这种底层⽅案,UI线程使⽤Dart语⾔来构建视图结构数据,这 些数据会在GPU线程进⾏图层合成,随后交给图像渲染引擎Skia加⼯成GPU数据,⽽这些数据会通过 OpenGL最终提供给GPU渲染。
  • 可以看到Flutter⽤了计算机最基本的图像渲染技术,摒弃其他⼀些通道和过程,⽤最直接的⽅式完成了图形显示,⾃然性能也就得到了保障。

2.16 PlatformView以及其原理

  • Flutter中通过PlatformView可以嵌套原⽣View到Flutter UI中。这⾥其实是使⽤了 Presentation+virtualDisplay+surface等实现的
  • ⼤致原理:使⽤了类似副屏显示技术,VirtualDisplay类代表⼀个虚拟显示器,调⽤DisplayManager的 createVirtualDisplay⽅法 将虚拟显示器的内容渲染到⼀个surface控件上,然后将surface的id通知给dart。
  • 让引擎绘制时,在内存中找到对应的surface画⾯内存数据,然后绘制处理。

2.17 Flutter状态管理

  • Flutter的状态可以分为全局状态和局部状态。
  • 状态管理基本都是基于InheritedWidget封装的⽤于Widget树的数据传递与共享的⼀套框架
  • 官⽅提供的状态管理⽅案有两种,Provider和InheritedWidget。
  • Provider本质上就是基于InheritedWidget来实现widget树的状态共享的。

2.18 isolate是怎么进⾏通信和实例化的?

  • isolate通过spawn⽅法实例化,通过port来通信。
  • isolate有⾃⼰的内存和单线程控制的事件循环。
  • Dart程序的并发都是运⾏多个isolate的结果,dart没有共享内存的并发。

2.19 Future还是isolate场景分析

  1. 如果⼀段代码不会被中断,那么就直接使⽤正常的同步执⾏就⾏
  2. 如果代码段可以独⽴运⾏⽽不会影响应⽤程序的流程性,建议使⽤future
  3. 如果繁重的处理可能要花⼀些时间才能完成,⽽且会影响应⽤程序的流畅性,建议使⽤isolate。 例如加解密,裁剪图⽚,从⽹络加载⼤图。

2.20 Flutter是如何与原⽣进⾏通信的

  • Flutter通过PlatformChannel与原⽣进⾏交互,其中PlatformChannel分为三种
  • BasicMessageChannel:⽤于传递字符串和半结构化的信息
  • MethodChannel:⽤于传递⽅法调⽤
  • EventChannel:⽤于数据流的通信。

2.21 Flutter绘制流程

  • Flutter只关⼼GPU提供视图数据,GPU的VSync信号同步到UI线程,UI线程使⽤Dart来构建抽象的 视图结构。
  • 这份数据在GPU线程进⾏图层合成,视图数据提供给Skia引擎渲染为GPU数据,这些数据通过OpenGL或者Vulkan提供给GPU

2.22 Flutter的热重载

  • Flutter的热重载是基于JIT编译模式的代码增量同步。
  • 热重载的流程可分为5步:扫描⼯程改动,增量编译,推送更新,代码合并,widget重建。
  • Flutter在接收到代码变更后,并不会让APP重新启动执⾏,⽽只会触发widget树的重新绘制,因此可 以保持改动前的状态,⼤⼤缩短了从代码修改到看到修改产⽣的变化之间所需要的时间。
  • hot reload⽐hot restart快,hot reload会编译我们⽂件⾥新增的代码并发送给dart虚拟机。dart会更新widget来改变UI。
  • ⽽Hot restart会让dart虚拟机重新编译应⽤。
  • 另⼀⽅⾯也是因为这样,hot reload会保留之前的state,⽽hot restart会重置所有的state回到初始值。

2.23 Flutter热更新

  • 利⽤原⽣框架更新。
  • 苹果不允许热更新!

2.24 Flutter动态化⽅案

  • 通过定义统⼀的描述语⾔(⽐如使⽤json来表示结构,样式和⾏为),然后通过可视化平台拖拽出json模块,最后将json模版下发到Flutter app。
  • Flutter app内置了js模版引起已经DSL解析引擎,由它们将DSL解析映射为Flutter widget或者渲染对象。

2.25 简单说⼀下flutter⾥asyncawait

  • await会把之前和之后 的代码分为两部分,await不会阻塞线程,⽽是会⽴即返回⼀个future。 await和async搭配使⽤的

2.26 在什么场景下使⽤profile model?

  • profile model是⽤来评估app性能的,它和release model相似,
  • 只保留了⼀些评估app性能的debug功能。
  • 在模拟器上是不能使⽤profile model的。

2.27 怎么做到只在debug

  • mode运⾏代码 foundation有⼀个静态的变量kReleasMode来表示是否是release mode。

2.28 怎么理解isolate

  • isolate是Dart对并发模式的实现。isolate有⾃⼰的内存和单线程控制的运⾏实体。isolate之间的内存在逻辑上是隔离的。
  • 任何Dart程序的并发都是通过isolate来完成的。

2.29 请简单介绍下flutter框架,以及它的优缺点

  • Flutter是google推出的⼀套开源跨平台UI框架。Flutter采⽤现代响应式框架构建,其中⼼思想是⽤组件来构建应⽤的UI。
  • 当组件的状态发⽣改变时,组件会重构它的描述,Flutter会对⽐之前的描述,以确定底层渲染树从当前 状态切换到下⼀个状态所需的最⼩更改。
  • 优点:热重载,采⽤声明式编程,这是⼤前端的趋势。
  • 缺点:使⽤dart语⾔,增加学习难度。

2.30 Flutter在Debug和Release下分别使⽤什么编译模式,有什么区别?

  • Debug模式下使⽤JIT编译模式,即Just in time(即时编译),Release下使⽤AOT模式,即Ahead of time(提前编译)。
  • JIT模式因为需要边运⾏边编译,所以会占⽤运⾏时内存,导致卡顿现象,但是有动态编译效果对于开发者来说⾮常⽅便调试。
  • AOT模式提前编译不会占⽤运⾏时内存,相对来说运⾏流畅,但是会导致编译时间增加。

3. Flutter开源项目及参考文章

发文不易, 喜欢点赞的人更有好运气👍 :), 定期更新+关注不迷路~

ps:欢迎加入笔者18年建立的研究iOS审核及前沿技术的三千人扣群:662339934,坑位有限,备注“掘金网友”可被群管通过~