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(
      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)阶段了,

这里其实onBeginFrameonDrawFrame是在具体执行这两个回调。最后渲染出来首帧场景送入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插件

 

你可能感兴趣的:(Flutter,面试)