Flutter高级面试题&答案

1、Dart是值传递还是引用传递?

dart是值传递。

2、描述Flutter的核心渲染模块三棵树

WidgetTree:存放渲染内容、它只是一个配置数据结构,创建是非常轻量的,在页面刷新的过程中随时会重建

Element 是分离 WidgetTree 和真正的渲染对象的中间层, WidgetTree 用来描述对应的Element 属性,同时持有Widget和RenderObject,存放上下文信息,通过它来遍历视图树,支撑UI结构。

RenderObject (渲染树)用于应用界面的布局和绘制,负责真正的渲染,保存了元素的大小,布局等信息,实例化一个 RenderObject 是非常耗能的

当应用启动时 Flutter 会遍历并创建所有的 Widget 形成 Widget Tree,通过调用 Widget 上的 createElement() 方法创建每个 Element 对象,形成 Element Tree。最后调用 Element 的 createRenderObject() 方法创建每个渲染对象,形成一个 Render Tree。

3、flutter 中Widget的分类

1、组合类:StatelessWidget和StatefulWidget

2、代理类:inheritedwidget、ParentDataWidget
inheritedwidget一般用于状态共享,如Theme 、Localizations 、 MediaQuery 等,都是通过它实现共享状态,这样我们可以通过 context 去获取共享的状态,比如 ThemeData theme = Theme.of(context);

3、绘制类:RenderObjectWidget
RenderObject 的布局相关方法调用顺序是 : layout -> performResize -> performLayout -> markNeedsPaint

4、mixin extends implement 之间的关系?

继承(关键字 extends)、混入 mixins (关键字 with)、接口实现(关键字 implements)。

这三者可以同时存在,前后顺序是extends -> mixins -> implements。

Flutter中的继承是单继承,子类重写超类的方法要用@Override,子类调用超类的方法要用super。

在Flutter中,Mixins是一种在多个类层次结构中复用类代码的方法。mixins的对象是类,mixins绝不是继承,也不是接口,而是一种全新的特性,可以mixins多个类,mixins的使用需要满足一定条件。

使用mixins的条件:

mixins类只能继承自object
mixins类不能有构造函数
一个类可以mixins多个mixins类
可以mixins多个类,不破坏Flutter的单继承

5、简述Dart语音特性

在Dart中,一切都是对象,所有的对象都是继承自Object

Dart是强类型语言,但可以用var或 dynamic来声明一个变量,Dart会自动推断其数据类型,dynamic类似c#

没有赋初值的变量都会有默认值null

Dart支持顶层方法,如main方法,可以在方法内部创建方法

Dart支持顶层变量,也支持类变量或对象变量

Dart没有public protected private等关键字,如果某个变量以下划线(_)开头,代表这个变量在库中是私有的

6、Dart 中的级联操作符

Dart 当中的 「..」意思是 「级联操作符」,为了方便配置而使用。「..」和「.」不同的是 调用「..」后返回的相当于是 this,而「.」返回的则是该方法返回的值 。

7、Dart 的单线程模型是如何运行的?

Dart 在单线程中是以消息循环机制来运行的,包含两个任务队列,一个是“微任务队列” microtask queue,另一个叫做“事件队列” event queue。

当Flutter应用启动后,消息循环机制便启动了。
按照先进先出的顺序逐个执行 微任务队列 中的任务,当所有 微任务队列 执行完后便开始执行 事件队列 中的任务,事件任务执行完毕后再去执行微任务,如此循环往复;

8、await for 与 stream流

Stream<String> stream = new Stream<String>.fromIterable(['1', '2', '3', '4']);
main() async{
 print('start');
 await for(String s in stream){
   print(s);
 }
 print('end..');
}

输出

start
1
2
3
4
end..

await for一般用在直到Stream什么时候完成,并且必须等待传递完成之后才能使用,不然就会一直阻塞。

9、Stream 与 Future是什么关系?

1、Future 表示稍后获得的一个数据,所有异步的操作的返回值都用 Future 来表示。
2、Future 只能表示一次异步获得的数据。
3、Stream 表示多次异步获得的数据。比如界面上的按钮可能会被用户点击多次,按钮上的点击事件(onClick)就是一个 Stream 。
4、Future将返回一个值,而Stream将返回多次值。
5、Dart 中统一使用 Stream 处理异步事件流。

10、Stream 有哪两种订阅模式?分别是怎么调用的?

Stream有两种订阅模式:单订阅(single) 和 多订阅(broadcast)。单订阅就是只能有一个订阅者,而广播是可以有多个订阅者。这就有点类似于消息服务(Message Service)的处理模式。单订阅类似于点对点,在订阅者出现之前会持有数据,在订阅者出现之后就才转交给它。而广播类似于发布订阅模式,可以同时有多个订阅者,当有数据时就会传递给所有的订阅者,而不管当前是否已有订阅者存在。

Stream 默认处于单订阅模式,所以同一个 stream 上的 listen 和其它大多数方法只能调用一次,调用第二次就会报错。但 Stream 可以通过 transform() 方法(返回另一个 Stream)进行连续调用。通过 Stream.asBroadcastStream() 可以将一个单订阅模式的 Stream 转换成一个多订阅模式的 Stream,isBroadcast 属性可以判断当前 Stream 所处的模式。

11、Flutter中的Widget、State、Context 的核心概念?是为了解决什么问题?

主要是为了解决多个部件之间的交互和部件自身状态的维护。

1、Widget: 在Flutter中,几乎所有东西都是Widget。将一个Widget想象为一个可视化的组件(或与应用可视化方面交互的组件),当你需要构建与布局直接或间接相关的任何内容时,你正在使用Widget。

2、Widget树: Widget以树结构进行组织。包含其他Widget的widget被称为父Widget(或widget容器)。包含在父widget中的widget被称为子Widget。

3、Context: 仅仅是已创建的所有Widget树结构中的某个Widget的位置引用。简而言之,将context作为widget树的一部分,其中context所对应的widget被添加到此树中。一个context只从属于一个widget,它和widget一样是链接在一起的,并且会形成一个context树。

4、State: 定义了StatefulWidget实例的行为,它包含了用于”交互/干预“Widget信息的行为和布局。应用于State的任何更改都会强制重建Widget。

12、Dart异步编程中的 Future关键字?

Dart中,执行一个异步任务使用Future来处理。在 Dart 的每一个 Isolate 当中,执行的优先级为 :Main > MicroTask > EventQueue。

13、Flutter 中的生命周期

image.png

14、Widget 唯一标识Key

GlobalKey:确保生成的Key在整个应用中唯一,是很昂贵的,允许element在树周围移动或变更父节点而不会丢失状态;
LocalKey
UniqueKey
ObjectKey

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

在计算机系统中,图像的显示需要CPU、GPU和显示器一起配合完成CPU负责图像数据计算,GPU负责图像数据渲染,而显示器则负责最终图像显示。CPU把计算好的、需要显示的内容交给GPU,由GPU完成渲染后放入帧缓冲区,随后视频控制器根据垂直同步信号以每秒60次的速度,从帧缓冲区读取帧数据交由显示器完成图像显示。操作系统在呈现图像时遵循了这种机制。

而Flutter作为跨平台开发框架也采用了这种底层方案,UI线程使用Dart语言来构建视图结构数据,这些数据会在GPU线程进行图层合成,随后交给图像渲染引擎Skia加工成GPU数据,而这些数据会通过OpenGL最终提供给GPU渲染。

可以看到Flutter用了计算机最基本的图像渲染技术,摒弃其他一些通道和过程,用最直接的方式完成了图形显示,自然性能也就得到了保障。

16、PlatformView 以及其原理

Flutter 中通过 PlatformView 可以嵌套原生 View 到 Flutter UI 中,这里面其实是使用了 Presentation + VirtualDisplay + Surface 等实现的,
大致原理:
使用了类似副屏显示的技术,VirtualDisplay 类代表一个虚拟显示器,调用 DisplayManager 的 createVirtualDisplay() 方法,将虚拟显示器的内容渲染在一个 Surface 控件上,然后将 Surface 的 id 通知给 Dart,让 engine 绘制时,在内存中找到对应的 Surface 画面内存数据,然后绘制出来,实时控件截图渲染显示技术。

17、Flutter 线程管理模型

Flutter Engine层会创建一个Isolate,并且Dart代码默认就运行在这个主Isolate上。必要时可以使用spawnUri和spawn两种方式来创建新的Isolate,在Flutter中,新创建的Isolate由Flutter进行统一的管理。
事实上,Flutter Engine自己不创建和管理线程,Flutter Engine线程的创建和管理是Embeder负责的,Embeder指的是将引擎移植到平台的中间层代码。

Flutter 中存在的四大线程:分别为 UI Runner、GPU Runner、IO Runner, Platform Runner (原生主线程) ,

在 Flutter 中可以通过 isolate 或者 compute 执行真正的跨线程异步操作。

18、Flutter状态管理

Flutter的状态可以分为全局状态和局部状态两种。常用的状态管理有ScopedModel、BLoC、Redux / FishRedux和Provider。

状态管理基本都是基于InheritedWidget封装的用于Widget树的数据传递与共享的的一套框架

Provider是继承于InheritProvider,而InheritProvider本质上是一个InheritWidget,所以Provider本质上是依托于InheritProvider的机制来实现的widget树的状态共享。

Flutter中Provider和Redux状态管理简述

19、isolate是怎么进行通信和实例化的?

1、isolate实际就是一个隔离的Dart执行的上下文环境(或者容器)
2、isolate是有自己的内存和单线程控制的事件循环
3、isolate之间的内存在逻辑上是隔离的,不像Java一样是共享内存的
4、任何Dart程序的并发都是运行多个isolate的结果。Dart没有共享内存的并发;

isolate线程之间的通信主要通过port来进行,这个port消息传递过程是异步的。

//实现newIsolate与rootIsolate互相通信
import 'dart:io';
import 'dart:isolate';
//定义一个newIsolate
late Isolate newIsolate;
//定义一个newIsolateSendPort, 该newIsolateSendPort需要让rootIsolate持有,
//这样在rootIsolate中就能利用newIsolateSendPort向newIsolate发送消息
late SendPort newIsolateSendPort;

void main() {
  establishConn(); //建立连接
}

//特别需要注意:establishConn执行环境是rootIsolate
void establishConn() async {
  //第1步: 默认执行环境下是rootIsolate,所以创建的是一个rootIsolateReceivePort
  ReceivePort rootIsolateReceivePort = ReceivePort();
  //第2步: 获取rootIsolateSendPort
  SendPort rootIsolateSendPort = rootIsolateReceivePort.sendPort;
  //第3步: 创建一个newIsolate实例,并把rootIsolateSendPort作为参数传入到newIsolate中,为的是让newIsolate中持有rootIsolateSendPort, 这样在newIsolate中就能向rootIsolate发送消息了
  newIsolate = await Isolate.spawn(createNewIsolateContext, rootIsolateSendPort); //注意createNewIsolateContext这个函数执行环境就会变为newIsolate, rootIsolateSendPort就是createNewIsolateContext回调函数的参数
  //第7步: 通过rootIsolateReceivePort接收到来自newIsolate的消息,所以可以注意到这里是await 因为是异步消息
  //只不过这个接收到的消息是newIsolateSendPort, 最后赋值给全局newIsolateSendPort,这样rootIsolate就持有newIsolate的SendPort
  newIsolateSendPort = await rootIsolateReceivePort.first;
  //第8步: 建立连接后,在rootIsolate环境下就能向newIsolate发送消息了
  sendMessageToNewIsolate(newIsolateSendPort);
}

//特别需要注意:sendMessageToNewIsolate执行环境是rootIsolate
void sendMessageToNewIsolate(SendPort newIsolateSendPort) async {
  ReceivePort rootIsolateReceivePort = ReceivePort(); //创建专门应答消息rootIsolateReceivePort
  SendPort rootIsolateSendPort = rootIsolateReceivePort.sendPort;
  newIsolateSendPort.send(['this is from root isolate: hello new isolate!', rootIsolateSendPort]);//注意: 为了能接收到newIsolate回复消息需要带上rootIsolateSendPort
  //第11步: 监听来自newIsolate的消息
  print(await rootIsolateReceivePort.first);
}

//特别需要注意:createNewIsolateContext执行环境是newIsolate
void createNewIsolateContext(SendPort rootIsolateSendPort) async {
  //第4步: 注意callback这个函数执行环境就会变为newIsolate, 所以创建的是一个newIsolateReceivePort
  ReceivePort newIsolateReceivePort = ReceivePort();
  //第5步: 获取newIsolateSendPort, 有人可能疑问这里为啥不是直接让全局newIsolateSendPort赋值,注意这里执行环境不是rootIsolate
  SendPort newIsolateSendPort = newIsolateReceivePort.sendPort;
  //第6步: 特别需要注意这里,这里是利用rootIsolateSendPort向rootIsolate发送消息,只不过发送消息是newIsolate的SendPort, 这样rootIsolate就能拿到newIsolate的SendPort
  rootIsolateSendPort.send(newIsolateSendPort);
  //第9步: newIsolateReceivePort监听接收来自rootIsolate的消息
  receiveMsgFromRootIsolate(newIsolateReceivePort);
}

//特别需要注意:receiveMsgFromRootIsolate执行环境是newIsolate
void receiveMsgFromRootIsolate(ReceivePort newIsolateReceivePort) async {
  var messageList = (await newIsolateReceivePort.first) as List;
  print('${messageList[0] as String}');
  final messageSendPort = messageList[1] as SendPort;
  //第10步: 收到消息后,立即向rootIsolate 发送一个回复消息
  messageSendPort.send('this is reply from new isolate: hello root isolate!');
}

代码摘自https://zhuanlan.zhihu.com/p/355315785

20、Future还是isolate场景分析?

1、如果一段代码不会被中断,那么就直接使用正常的同步执行就行。
2、如果代码段可以独立运行而不会影响应用程序的流畅性,建议使用 Future (需要花费几毫秒时间)

3、如果繁重的处理可能要花一些时间才能完成,而且会影响应用程序的流畅性,建议使用 isolate (需要几百毫秒)

下面列出一些使用 isolate 的具体场景:
1、JSON解析: 解码JSON,这是HttpRequest的结果,可能需要一些时间,可以使用封装好的 isolate 的 compute 顶层方法。
2、加解密: 加解密过程比较耗时
3、图片处理: 比如裁剪图片比较耗时
4、从网络中加载大图

21、Flutter 是如何与原生Android、iOS进行通信的?

Flutter 通过 PlatformChannel 与原生进行交互,其中 PlatformChannel 分为三种:

BasicMessageChannel :用于传递字符串和半结构化的信息。
MethodChannel :用于传递方法调用(method invocation)。
EventChannel : 用于数据流(event streams)的通信。

22、Flutter 绘制流程

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

23、Flutter 的热重载

Flutter 的热重载是基于 JIT 编译模式的代码增量同步。由于 JIT 属于动态编译,能够将 Dart 代码编译成生成中间代码,让 Dart VM 在运行时解释执行,因此可以通过动态更新中间代码实现增量同步。
热重载的流程可以分为 5 步,包括:扫描工程改动、增量编译、推送更新、代码合并、Widget 重建。Flutter 在接收到代码变更后,并不会让 App 重新启动执行,而只会触发 Widget 树的重新绘制,因此可以保持改动前的状态,大大缩短了从代码修改到看到修改产生的变化之间所需要的时间。
另一方面,由于涉及到状态的保存与恢复,涉及状态兼容与状态初始化的场景,热重载是无法支持的,如改动前后 Widget 状态无法兼容、全局变量与静态属性的更改、main 方法里的更改、initState 方法里的更改、枚举和泛型的更改等。
可以发现,热重载提高了调试 UI 的效率,非常适合写界面样式这样需要反复查看修改效果的场景。但由于其状态保存的机制所限,热重载本身也有一些无法支持的边界。

24、Flutter 热更新

Android:
利用原生框架更新,实际上就是更新Flutter框架相关的二进制。Flutter应用发布出来的产物主要包括 libflutter.so,libapp.so,flutterAssets,这样,就可以通过Android端原生平台网络请求,动态下发并加载这些产物,从而实现热更新。

iOS:苹果商店不允许动态下发和加载二进制产物,包括动态库之类的;

25、Flutter 动态化方案

基本思路:
通过定义统一的描述语言(JSON来表示结构、样式和行为),然后通过可视化平台来拖拽出JSON模板,最后将JSON模板下发到Flutter App,Flutter App内置了JS模板引擎以及DSL解析引擎,由它们将DSL解析映射为Flutter Widgets或者渲染对象。

Flutter动态化整体架构
总体架构上分为四大部分。
第一部分是可视化搭建平台,负责开发DSL页面和配置数据。
第二部分是低代码服务中台,提供组件保存、页面发布和数据加工能力。
第三部分是面向端的接口服务,包括模板和数据接口。
第四部分是端,这块是核心重点,端上需要支持一整套DSL的解析和渲染映射,并且要做好相应的优化,以保证渲染性能和效率。

1、美团外卖Flutter动态化实践
2、携程App 首页动态化探索
3、Flutter 动态化在最右 App 中的实践
4、Flutter 动态化热更新的思考与实践
5、NOW直播Flutter动态搜索列表页实现
6、Flutter动态化的方案对比及最佳实现-闲鱼
7、基于JavaScript 的MXFlutter
8、Flutter App动态化与可视化搭建方案设计

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,013评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,205评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,370评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,168评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,153评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,954评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,271评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,916评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,382评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,877评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,989评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,624评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,209评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,199评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,418评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,401评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,700评论 2 345

推荐阅读更多精彩内容

  • Flutter是Google推出的一套开源跨平台UI框架,可以快速地在Android、iOS和Web平台上构建高质...
    GoldMask阅读 15,311评论 2 48
  • 1. Dart 当中的 「..」表示什么意思? Dart 当中的 「..」意思是 「级联操作符」,为了方便配置而使...
    马修斯阅读 13,700评论 0 22
  • 1、Dart中var 与 dynamic的区别 2、const和final的区别 3、Dart中 ?? 与 ??=...
    永不放弃_8eef阅读 1,410评论 0 3
  • Dart 当中的 「..」表示什么意思? Dart 当中的 「..」意思是 「级联操作符」,为了方便配置而使用。「...
    kadis阅读 522评论 0 4
  • 概述 Flutter是Google推出的一套开源跨平台UI框架,可以快速地在Android、iOS和Web平台上构...
    flutter开发精选阅读 1,196评论 0 13