我正在参加「掘金·启航计划」
1. 什么是Flutter?
1.1 Flutter介绍
1.1.1 官方英文介绍
- Flutter是一个UI SDK(Software Development Kit).
- 可以进行移动端、Web端、桌面应用开发的跨平台解决方案, 目标是一统大前端.
- 目前使用Flutter技术的大厂: Google、阿里闲鱼团队、腾讯、字节...
1.1.2 特点
- 美观
- 使用Flutter内置美观的Material Design和Cupertino Widget、丰富的motion API、平滑自然的滑动效果和平台感知, 为用户带来流畅的操作体验.
- 快速
- Flutter的UI渲染性能很好. 在生产环境下, Flutter将代码编译成机器码执行, 并充分利用GPU的图形加速能力, 因此使用Flutter开发的移动应用即使在低配手机上也能实现流畅的UI渲染速度(16.7ms完成一帧渲染).
- Flutter引擎用C++编写, 包括高效的Skia 2D渲染引擎、Dart运行时和文本渲染库.
- 高效
- 把Hot Reload(热重载)技术由前端带入到移动端(( Ĭ ^ Ĭ )).
- 开放
- 由Google主导, 完全开源的项目.
1.2 跨平台方案对比
1.2.1 基于JavaScript和WebView的跨平台方案
- 代表框架有PhoneGap、Apache Cordova、Ionic...
- 主要是通过H5来构建页面, 再将其显示在各个平台的WebView中.
- 但是它默认是不能调用Native的一些服务的(如相机、蓝牙等硬件), 所以需要通过JavaScript进行桥接调用Native的一些API来完成某些基于硬件的功能.
- 本身的用户体验、性能相对Native控件并不是太流畅, Native更新迭代的过程中容易产生很多适配问题.
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
- Flutter使用Skia渲染引擎, 直接通过CPU、GPU进行图形绘制, 不需要依赖任何Native控件.
- Android操作系统中, 我们编写的Native控件实际上也是依赖于Skia进行绘制渲染, 所以Flutter在某些Android小K做系统上, 甚至还要高于安卓原生(因为安卓原生中的Skia必须随着操作系统进行更新, 二Flutter SDK中总是保持最新的Skia引擎).
- 而类似于React Native框架, 必须通过桥接操作先转成Native控件进行调用, 之后再进行渲染.
1.2.3.1 Flutter图形绘制原理
- GPU将信号同步到UI线程
- UI线程用Dart来构建图层树
- 图层树在GPU线程进行合成
- 合成后的视图数据提供给Skia引擎
- Skia引擎通过OpenGL或者Vulkan将显示内容提供给GPU
- 这也是Flutter相较React Native的本质区别
- React Native之类的解决方案, 只是通过JavaScript VM虚拟机桥接调用Native组件, 由iOS和Android系统进行Native组件的渲染.
- 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都是⽤来处理⼀步的⼯具。
- Future表示稍后获得的⼀个数据,所有异步的操作的返回值都⽤Future来表示
- Future只能表示⼀次异步获得的数据。
- Stream表示多次异步获得的数据,⽐如界⾯上的按钮可能会被⽤户点击多次,按钮上的点击事件 就是⼀个Stream。
- Future将返回⼀个值,⽽Stream将返回多次值
- 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场景分析
- 如果⼀段代码不会被中断,那么就直接使⽤正常的同步执⾏就⾏
- 如果代码段可以独⽴运⾏⽽不会影响应⽤程序的流程性,建议使⽤
future。 - 如果繁重的处理可能要花⼀些时间才能完成,⽽且会影响应⽤程序的流畅性,建议使⽤
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⾥async和await?
- 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,坑位有限,备注“掘金网友”可被群管通过~