Flutter 面试问题
Flutter 的初始化
1.Flutter App的入口就是函数runApp()
2.点击runApp()函数可以看到widgets/binding.dart中
void runApp(Widget app) { WidgetsFlutterBinding.ensureInitialized() ..attachRootWidget(app) ..scheduleWarmUpFrame(); }
1. 点进去WidgetsFlutterBinding (看名称是将Widget与Flutter 绑在一起的意思)可以看到ensureInitialized函数是返回一个WidgetsBinding.instance
单例
总体上来讲是把window
提供的API分别封装到不同的Binding里。我们需要重点关注的是SchedulerBinding:调度绑定
RendererBinding:渲染绑定,
通过pipelineOwner
间接持有render tree的根节点RenderView
。
WidgetsBinding:组件绑定,
持有element tree的根节点RenderObjectToWidgetElement
。
这3个是渲染流水线的重要存在。
2...attachRootWidget(app):
void attachRootWidget(Widget rootWidget) { _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>( container: renderView, debugShortDescription: '[root]', child: rootWidget ).attachToRenderTree(buildOwner, renderViewElement); }
在RenderBinding初始化的时候,我们得到了RendView的实例,render tree的根节点,RenderView是继承自RenderObject的,而RenderObject需要对应的Widget 与Element。上述代码中的RenderObjectToWidgetAdapter
就是这个Widget
。而对应的Element
就是RenderObjectToWidgetElement
了,既然是要关联到render tree的根节点,那它自然也就是element tree的根节点了。
3.scheduleWarmUpFrame
void scheduleWarmUpFrame() { ... Timer.run(() { ... handleBeginFrame(null); ... }); Timer.run(() { ... handleDrawFrame(); ... }); }
进入布局(Layout)阶段和绘制(Paint)阶段了,
这里其实onBeginFrame
和onDrawFrame
是在具体执行这两个回调。最后渲染出来首帧场景送入engine显示到屏幕。这里使用Timer.run()
来异步运行两个回调,是为了在它们被调用之前有机会处理完微任务队列(microtask queue)
stream跟changeNotifer区别
1.stream 代表着时间了,通过stream 可以快速的实现事件流驱动业务逻辑,界面通过订阅事件,针对事件转换最后通过响应事件完成页面布局,而在整个stream 流过程中,离不开以下角色
streamController:管理调度整个事件流的流程,并保存整个事件流中所需要的对象,便于管理和使用
StreamSink:事件的开始入口,所有的同步异步事件都是从这里开始的,提供了add 和addStream等方法
Steam:事件本身,可以被转换和监听,订阅后返回StreamSubscription对象
StreamSubscription:订阅steam后得到的对象,可以管理订阅过的各种操作,如cancle()、 pause()
总结:stream 的事件流就是:先创建steam,创建回调方法并订阅,在通过streamSink 添加事件源,在订阅的过程中可以使用StreamSubscription管理这个订阅,最后回调steam订阅的回调方法
ChangeNotifier:单向变更通知。
Flutter 中async 与await 异步变成原理?
flutter是单线程,遇到有延迟的运算,比如IO操作时,线程中按循序执行的运算就会阻塞,用户就会感到卡顿,于是用异步来解决这个问题。当遇到有延迟的运算async时,将其放入到延迟运算的队列(await)中去,吧不需要的延迟运算的先执行掉,
最后再来处理延迟计算的部分。有await标记的运算,其结果值都是一个Future对象,使用await,必须在有async标记的函数中运行,否则await 会报错。
除了isoloate 实现异步,你还听过哪个?=====>>> comptuer
dart 是单线程怎么请求数据?除了isoloate 实现异步,你还听过哪个?
Dart是单线程 但是也有自己的进程或者线程机制,isolate。App的启动入口函数就是一个isolate。在dart中islolate无法直接共享内存,不同的isllate直接只能通过isolate Api 进行通讯。
在dart 中,一个isolate 对象其实就是一个islolate执行环节的引用,一般来说,我们都是通过isolate去控制其他的isolate完成彼此之间的交互,而当我们想要创建一个新的isolate可以使用isolate。spawn方法获取返回的一个新的isolate对象,两个isolate之间使用Sendport相互发送消息,而isolate中也存在了一个与之对应的ReceiverPort接受消息用来处理,但是我们需要注意的是,ReceivePort 和SendPort在每个isolate都有一堆,只有同一个isolate中的ReceivePort才能接受当前类的SendPort发送的消息并且处理
Flutter四棵树
Flutter的四棵树:Widget、Element、RenderObject、Layer四棵树,Widget与Element是一对多的关系
Element中持有Widget和Renderobject,而Element与Renderobject是一一对应的关系,当 RenderObject
的 isRepaintBoundary
为 true
时,那么个区域形成一个 Layer
,所以不是每个 RenderObject
都具有Layer
的,因为这受 isRepaintBoundary
的影响
Flutter中Widget不可变,每次都保持在一帧,如果发生改变是通过state实现跨帧状态保存,而真是完成布局和绘制数组的是RenderObject,Element充当两者的桥梁,State就是保存在Element中的
调用setState其实是调用了markNeedsBuild,该方法内部标记此element为dirty,然后在下一帧WidgetsBinding.drawFrame
才会被绘制,这可以看出 setState
并不是立即生效的。
Flutter 中 RenderObject
在 attch
/layout
之后会通过 markNeedsPaint();
使得页面重绘
dart是单线程是如何运行的?
Dart线程是以消息循环机制eventLoop和2个队列。Event looper 中包含2个队列:
(1)MicorTask Queue(微任务队列) 只在当前isolate的任务队列中排队,优先级高于Event Queue ,好比高铁,Vip通道只有Vip用户先登记了菜开放公共排队口
(2) Event Queue (事件队列)包含所有外来的时间,I/O,timer,mouse events等,任意新增的 MicroTask 的优先级是大于Event Queue的,只有所有的MicroTask Queue中的任务都完成以后才会去执行Event Queue中的内容。
入口函数main()执行完后,消息循环机制便启动了,首先会按照先进先出的顺序逐个执行微任务队列中的任务,当所有的微任务队列执行完后便开始执行事件队列中的任务,事件任务执行完毕后再去执行微任务,如此循环往复,生生不息
一个复杂的widget树上挂载一个Stream,其中一个根节点的视图需要变更数据即刷新,如果控制最小化性能消耗,如何做?
.. 与.的区别
..联级操作符,为了方便配置而使用,调用..返回的是this,而.返回的则是该方法返回的值
flutter 如何做大文件传输
答:之前上传使用的是uploadFileStream来上传文件,它会把文件分块读出来,添加分块签名,然后利用request.sink.add(xxx)加入缓存区,最后调用request.send()来完成发送。
这样会吧整个文件外加签名信息都放到缓冲区,意味着文件越大,也就占用更多的内存,最终导致崩溃的发生。
需要对文件进行签名处理,不能直接使用dio插件