Android常见面试题集锦(七)

232 阅读9分钟

flutter篇

1. Flutter的生命周期

image.png

  • 1.初始化期:createState,initState(state与context产生关联。中可以获取服务器数据)

  • 2.组件创建期:didChangeDependencies和build

  • 3.触发组件build:setState、didUpdateWidget(当widget重新构建时,触发该方法一定会触发build)、didChangeDependencies(当State对象依赖发生变化时会触发,ex国际化和主题改变)

  • 4.销毁期:deactivate和dispose

  • widget树中,若节点的父级结构中的层级 或 父级结构中的任一节点的widget类型有变化,节点会调用didChangeDependencies;若仅仅是父级结构某一节点的widget的某些属性值变化,节点不会调用didChangeDependencies.

  • didUpdateWidget
    widget树中,若节点调用setState方法,节点本身不会触发didUpdateWidget,此节点的子节点会调用didUpdateWidget

2. EventQueue和MicroTask有什么区别

Flutter框架基于 单线程模型的Dart,通过 Event Loop(事件循环) 实现 异步,有Android Handler那味了。不过Dart中有着两个队列:Event Queue(事件队列)Microtask Queue(微任务队列,短时间就能完成的异步任务) ,后者优先级最高,每次的事件循环先查此队列是否有可执行的任务,没有才会去处理事件队列。异步队列很少必须要在事件队列前完成,因此用得不多。I/O、绘制、定时器这些异步事件,都是通过 事件队列 驱动主线程执行的。

EventQueue和MicroTask在Flutter中都是用于处理异步任务和事件的机制,但它们之间有一些区别。

首先,它们的执行优先级不同。在Flutter的事件循环中,MicroTask的优先级高于EventQueue。这意味着,当MicroTask队列中有任务时,它将会一直占用事件循环,直到队列中没有任务为止。而EventQueue中的事件则会排在MicroTask之后执行。

其次,它们的用途也不同。EventQueue主要用于处理耗时操作、定时器、网络请求等异步任务。而MicroTask则主要用于执行一些非常小的任务,例如更新UI或执行某些副作用,这些任务通常需要在当前帧结束之前完成。

总的来说,EventQueue和MicroTask都是Flutter中非常重要的异步任务和事件处理机制,它们各自有其特点和适用场景,开发人员可以根据具体需求选择合适的机制来处理异步任务和事件。

3. Future与isolate的区别?

(1)future是异步编程,调用本身立即返回,并在稍后的某个时候执行完成时再获得返回结果。在普通代码中可以使用await 等待一个异步调用结束。

(2)isolate是并发编程,Dartm有并发时的共享状态,所有Dart代码都在isolate中运行,包括最初的main()。每个isolate都有它自己的堆内存,意味着其中所有内存数据,包括全局数据,都仅对该isolate可见,它们之间的通信只能通过传递消息的机制完成,消息则通过端口(port)收发。isolate只是一个概念,具体取决于如何实现,比如在Dart VM中一个isolate可能会是一个线程,在Web中可能会是一个Web Worker。

4. Flutter与原生通信

  1. MethodChannel:方法传递,Flutter调用原生方法
  2. EventChannel:流传递(应用:eventbus)
  3. BasicMessageChannel:传递字符串和半结构化信息

5. HotReload与HotRestart区别

HotReload会保留State,重新编译你添加的代码从而改变ui。HotRestart重新编译应用。HotReload速度比HotRestart快。

6. Flutter有哪几种key

  1. LocalKey(局部key):ValueKey(默认string)、ObjectKey(比较的是地址)、UniqueKey(每次都不一样)
  2. GlobalKey(全局key):LabeledGlobalKey(默认实现)、GlobalObjectKey

7. Flutter架构

image.png

  1. Framework层:dart基础库
  2. Engine层:绘图引擎层(Skia)
  3. Embedder操作系统层:渲染设置、线程设置,平台插件等相关特性适配

8. 三棵树

  • Widget对视图的结构化描述,存储视图渲染相关的 配置信息:布局、渲染属性、事件响应等信息。

  • ElementWidget的实例化对象,承载视图构建的上下文数据,连接Widget到完成最终 渲染桥梁

  • RenderObject负责实现视图渲染的对象

Widget被设计成不可变的 (immutable)

当视图渲染的配置信息发生变化,Flutter会 重建Widget树 来进行数据更新。因为不涉及实际渲染位图,所以它只是一个 轻量级 的数据结构,重建成本很低。另外,得益于Widget的 不可变性,可以以较低成本进行 渲染结点(Element) 的 复用,因此在真实渲染树中可能存在 不同Widget对应同一个渲染结点的情况 (多对一)。

把Flutter的渲染过程简单分成这三步:

  • 通过 Widget树 生成对应的 Element树
  • 创建相应的 RenderObject 并关联到 Element.renderObject 属性上;
  • 构建成 RenderObject树深度优先遍历,确定树中各对象的 位置和尺寸 (布局) ,把它们 绘制 到不同图层上。SkiaVsync信号同步 时直接从渲染树 合成Bitmap,最后交给 GPU渲染

例子:Row容器的左边放一个Image,有边放一个Column套两个Text:

image.png

Flutter遍历完Widget树,创建子Widget对应的Element的同时,创建与之关联的、负责实际布局和绘制的 RenderObject:

image.png

增加Element中间层的好处 (提高渲染效率)

在这一层 将Widget树的变化(diff)做抽象,只将 真正需要修改的部分同步到RenderObject树 中,最大程度降低对真实渲染视图的修改,提高渲染效率,而不是销毁整个渲染视图树重建。Element是可复用的,Widget触发重建,Flutter会根据重新前后Widget树的 渲染类型及属性变化情况 决定后续的 复用或新建。比如:只是调整了一个渲染样式,Flutter会通知Element复用现有节点,只是 同步属性 到RenderObject出发绘制。如果Widget树中涉及到 Widget类型替换或变更,Flutter 则会将老的Element及RenderObject 摘除,让新Widget 重新走一遍创建 Element 和 RenderObject 的流程,挂载到 Element 树和 RenderObject 树上。

9. 当Widget发生变化时,Element是怎样知道Widget变化了的?

当Widget发生变化时,Element会通过比较新旧Widget的key来判断其是否发生变化。如果key不同,则说明Widget发生了变化。因此,Element可以知道Widget的变化情况。

10. Dart 中 number 类型分为 int 和 double

11. Flutter是如何做到一套Dart代码可以编译运行在Android和iOS平台的?所以说具体的原理。

计算机系统中,CPU、GPU和显示器以一种特定的方式协作:CPU将计算好的显示内容提交给 GPU,GPU渲染后放入帧缓冲区,然后视频控制器按照 VSync信号从帧缓冲区取帧数据传递给显示器显示。

  • Flutter视图数据提供给 Skia引擎渲染为 GPU数据,这些数据通过 OpenGL或者 Vulkan提供给 GPU。

image.png

12. Flutter在不使用WebView和JS方案的情况下。如何做到热更新?说一下大概思路。

  • iOS 目前不支持,不能过审
  • 安卓,可以替换so文件来实现

13. 如何让Flutter编译出来的APP的包大小尽可能的变小?

1. 移除无用代码和无用资源,压缩图片, 安卓里拆 App Bundle,

2. Dart 编译产物做针对性优化

  • 动态下发:剥离 Data 段及一切非必要产物,打包后动态下发。
  • 内置压缩:以二进制形态内置动态下发包。

3. Flutter 引擎编译产物优化

  • 主要优化思路有升级 Bulild Tools 统一双编译参数,
  • 定制化编译裁剪引擎内部部分特定无用功能。

4. 机器码指令优化

精简机器码指令,Google 也回复称未来 Dart 与 OC 基本持平。

14. 不支持热重载的场景

Flutter 提供的亚秒级热重载一直是开发者的调试利器。通过热重载,我们可以快速修改 UI、修复 Bug,无需重启应用即可看到改动效果,从而大大提升了 UI 调试效率。

不过,Flutter 的热重载也有一定的局限性。因为涉及到状态保存与恢复,所以并不是所有的代码改动都可以通过热重载来更新。以下是Flutter开发中几个不支持热重载的典型场景:

  • 代码出现编译错误;
  • Widget 状态无法兼容;
  • 全局变量和静态属性的更改;
  • main 方法里的更改;
  • initState 方法里的更改;
  • 枚举和泛类型更改。

15. 如何监听 Activity 的生命周期

参考答案: (1)WidgetsBindingObserver或者AppLifecycleListener

(2)与原生通信回调,如MethodChannel 方法传递

16. Isolate-多线程

Dart 也提供了多线程机制 → Isolate,每个 Isolate 都有自己的 EventLoopEventQueue,Isolate 间 不共享任何资源,只能依靠 消息机制 (SendPort发送管道) 通信,所以 不存在资源抢占问题。Isolate的创建非常简单,只要给定一个 函数入口,创建时再 传入一个参数 就可以启动了。

执行 I/O任务,如存储访问、网络请求,可以放心使用 async/await,要执行 消耗CPU的计算密集型 工作,可以将其转移到一个 Isolate 上以避免阻塞事件循环。

17. Future和Stream之间有什么区别

(1)Future是表示一个异步操作的单个结果,只返回一次结果。而Stream表示一系列异步操作的序列,可以返回多个结果。

(2)Future通常用于处理一次性的异步操作,而Stream通常用于处理实时流式数据。

(3)Future通过then()和catchError()方法来处理异步操作的结果和异常,而Stream通过listen()方法来监听和处理异步事件。

(4)Future使用await关键字来等待异步操作完成,Stream使用await for关键字来等待异步事件序列。

18. Stream里的事件是串行还是并行的?

串行的,使用await依次等待执行。

19. flutter管理框架 Provider有啥缺点?

(1) 因为是根据类型向上寻找 Provider,无法提供相同类型的参数;

(2) 找不到对应参数的时候可能会发生不可预估的错误;

(3) 依赖 Widget,无法解决 Widget 之外的问题;

(4) 无法简单的组合多个 Provider;

(5) 无法在不使用 state 的时候 dispose state(只能在 dispose widget 的时候 dispose state);

更多参考(网上说框架 Riverpod 解决了这些问题)