ArturoYi
面试

Flutter面试题1

高频面试题 2
事件循环、Future / Stream、Isolate、热重载与 JIT / AOT。

和 高频面试题 2 重叠的概念,这里写 Dart / 发布视角。

事件循环怎么跑

考察事件循环与执行模型。

  • 用什么驱动应用?
    事件循环,调度输入、动画和 UI 更新。由 Engine + Dart VM 组成。
  • 大致流程:
    1. 引擎处理输入、动画
    2. Dart VM 跑业务
    3. 引擎更新界面并上屏
    4. 进入下一轮
  • Dart 单线程还是多线程?
    单线程。一条事件循环 + 两个队列。async 不会开线程。
  • 微任务队列和事件任务队列,微任务队列优先:
    1. 同步代码跑完
    2. 清空微任务队列
    3. 从事件队列取一个任务
    4. 再回到微任务
print('sync');
scheduleMicrotask(() => print('micro'));
Future(() => print('event'));
// sync → micro → event
微任务事件
谁在这scheduleMicrotask、then、await 续体Future(() {})、Timer、IO、点击、帧
规则当前轮先清空一次只取一个
面试点:事件驱动 + 帧循环;「单线程 + 两个队列 + 微任务优先」。

Future 和 Stream

  • 差在哪?
    Future:一次结果。Stream:一串结果。async / await 只是 Future 的写法。
  • Stream 两种形态:
    • 单订阅(Single-subscription)
      只能被订阅一次,第二次 listen 会报错。事件按顺序交给唯一消费者。
      典型:HTTP 响应体、文件读、一次性请求结果流。
    • 广播(Broadcast)
      可以多次订阅,多个订阅者同时收到同一批事件。后订阅的人通常只收到之后的事件。
      典型:全局事件总线、通知广播。默认 StreamController 是单订阅,广播用 StreamController.broadcast() 或 asBroadcastStream()。
  • 会阻塞吗?
    不阻塞事件循环。await / listen / await for 只暂停当前函数,控制权交回循环,UI 还能跑。
    但 Future / Stream 回调里的同步重活仍在主 Isolate,会堵帧。IO 等结果不堵;解析、加密、大循环要丢 Isolate。
  • 容易漏的点:
    Future 忘了处理 error;Stream 忘了 cancel。单订阅流不要当事件总线用。
FutureStream
结果一次一串
典型请求、读文件分页、socket、EventChannel
收尾try/catchdispose 里 cancel
单订阅广播
订阅只能一次可以多次
消费者一个多个同时收
典型请求结果流、读文件事件总线、通知广播
面试点:单订阅 = 单消费者,广播 = 多消费者。一次结果不必上 Stream。async 不阻塞、也不开线程。

Isolate 怎么隔离

  • 是什么?
    Dart 的并发单元:独立堆、独立事件循环,不共享内存,靠 Port 传消息。
  • 什么时候用?
    解析大 JSON、加密、压图这类 CPU 重活。
    async / await 只是「先去做别的,算完再回来」,计算本身还在主 Isolate,UI 照样卡。要另开一个 Isolate 才能真并行。
final data = await Isolate.run(() => parseHugeJson(raw));
Isolateasync
解决不堵 UI 的 CPU 重活等 IO
内存不共享仍在主 Isolate 同一堆
限制不能 setState,结果要传回来不增加核
面试点:主 Isolate 跑 UI,后台 Isolate 跑重任务。不是 async 的升级版,也不是 Java 线程。

热重载、热重启、热更新

  • 冷启动: 从 0 启动,最慢。
  • 热重载: 再执行 build,尽量保状态。
  • 热重启: 清 Dart 状态,重跑 main(),不重装 App。
  • 热更新: 线上发补丁。官方不直接支持,偏安全和合规。
口诀:热重载改 UI、保状态;热重启清状态;热更新是发版,不是 r。

Hot Reload 原理

Debug 用 JIT,走的就是这份可增量更新的 Kernel:

  1. 扫描改动
  2. 只把改过的部分编成新 Kernel
  3. 推到设备上正在跑的 VM
  4. 和新老 Kernel 合并(换方法体,进程不重启)
  5. Framework 重建 Widget,尽量保状态

Release 是 AOT:Kernel 已被编死成本机二进制,不能局部替换,所以没有热重载。

热重载经常无效

  • StatelessWidget → StatefulWidget
  • 全局 / static 初始化变了
  • main()、initState() 变了
  • 枚举 / 泛型破坏性调整
  • 改了原生或插件
热重载热重启热更新
做什么增量换 Dart重跑 main()线上补丁
状态尽量保清空和开发期不是一类
面试点:Stateless 改 Stateful 要热重启。热更新不要讲成热重载的线上版。

JIT 和 AOT

JITAOT
模式DebugRelease / Profile
产物Kernel,可增量替换本机二进制
图什么热重载、调试启动、帧率、上架

看卡顿用 profile,看包体用 release,不要拿 Debug 包说事。

为什么能热重载:Debug 用 JIT → 增量 Kernel 推到 VM → 合并后重建 Widget,进程不重启。
Copyright © 2026