Flutter技术面试经验合集

321 阅读15分钟

自我介绍:

你好面试官,我叫xx, 做flutter开发大概两年的时间,上一家在浙江xx工作,工作内容主要是开发公司业务相关的Flutter App。

看了贵公司的职位,我有三个优势:

第一是我经历过项目从0到1的过程,具备项目基础搭建的经验,无论是日常的项目迭代、还是技术实现方案,都深入参与,具体的项目我放到了简历中。

第二是我熟悉并深入参与国际化相关的业务。熟悉国际化业务的常见问题和解决方案。

第三是除了正常的业务开发,多次深入参与全面升级优化改版。

我看到了岗位的要求信息,我有信心能满足该岗位的需求,希望这次面试能给你一个满意的答复。

Flutter常问的一些基础问题

Dart语法的常见面试问题

const和final

在 Dart 中,constfinal 都用于定义不可变的变量,但它们的本质区别在于——

  • final 是运行时常量,值在程序运行时被初始化;
  • const 是编译时常量,值必须在编译阶段就能确定。

此外,const 创建的是完全不可变对象,连对象内部的字段也不能变,而 final 修饰的对象虽然引用不能变,但如果是可变对象,它的内容仍然可以改变:

在 Flutter 中,我常用 const 来标记不会变化的 Widget,能减少重建次数,提高性能;而 final 更多用于接收初始化时的配置或外部传参值,比如类的构造参数。

值传递还是引用传递

Dart 是 纯粹的值传递语言(pass-by-value) 。这意味着无论传递什么类型,传递的都是“值的副本”。

对于基本类型(如 intdoubleboolString 等不可变类型),传递的是值的副本,函数内部修改参数不会影响外部变量。

对于对象类型(如 ListMap、自定义类),传递的是“引用的值的副本”。也就是说,函数接收到的是原始对象的引用副本,可以修改对象内部的内容,但不能改变原始变量的引用指向

StatefulWidget和StatelessWidget区别

Statelesswidget是静态的,状态在创建后不可变,一般写一些静态,不需要更新的组件。

Stateful状态可以改变,setState,

 

Widget生命周期

·  createState:创建 State 对象。

·  initState:初始化状态,在 Widget 树构建前调用。

·  didChangeDependencies:当 State 对象的依赖项发生变化时调用(例如,InheritedWidget 的变化)。通常在 initState 后第一次调用。

·  build:构建 Widget 树,渲染 UI。

·  didUpdateWidget:当父 Widget 重建时,传递新的参数给当前 Widget,检查是否需要更新状态。

·  setState:通过调用 setState 来更新 Widget 的状态,触发 UI 重新构建。

·  dispose:销毁 Widget,清理资源

三棵树的关系, Widget 和 element 和 RenderObject

  • Widget:UI的配置单元
  • Element:Element是实例化的 Widget 对象,通过 Widget 的 createElement() 方法,是在特定位置使用 Widget配置数据生成;
  • RenderObject:用于应用界面的布局和绘制,保存了元素的大小,布局等信息;

在 Flutter 中,UI 的构建和渲染涉及三棵核心的树结构:Widget Tree、Element Tree 和 Render Tree(RenderObject Tree) 。它们分别承担了描述、连接、渲染的职责。

  1. Widget Tree — 描述阶段(immutable,声明式)
  • Widget 是UI 的配置描述,是不可变的对象(immutable)。
  • 它描述了界面应该长什么样,但不包含任何实际的渲染逻辑或状态。
  • 每次调用 build(),就会创建新的 Widget 树。
  1. Element Tree — 实例阶段(桥梁,状态保持)
  • Element 是 Widget 的一个“实例”,负责连接 Widget 和 RenderObject。
  • 它是树中真正持久存在的部分,负责维护 Widget 的生命周期、状态缓存和 diff 对比。
  • 每次 Widget 重建时,Flutter 会用新的 Widget 与旧的 Element 比较,决定是否需要更新。

StatelessElementStatefulElement 就是分别对应 StatelessWidgetStatefulWidget 的 Element 类型。

  1. Render Tree — 渲染阶段(真正绘制 UI)
  • RenderObject 是用来布局、测量、绘制、命中测试的。
  • 是 Flutter 的底层渲染单位,比如 RenderBox
  • RenderObject 由 Element 创建和管理,一般由 RenderObjectWidget(如 RenderBoxWidget)间接生成。

Widget 是 UI 配置;
Element 是 Widget 的实例和状态管理者;
RenderObject 是底层真正的绘制实体。
三者形成了声明式 UI 到高性能渲染之间的完整链条,是 Flutter 高性能的核心。

那么,flutter为什么要设计成这样呢?为什么要弄成复杂的三层结构?

答案是性能优化。如果每一点细微的操作就去完全重绘一遍UI,将带来极大的性能开销。flutter的三棵树型模式设计可以有效地带来性能提升。

widget的重建开销非常小,所以可以随意的重建,因为它不一会导致页面重绘,并且它也不一定会常常变化。 而renderObject如果频繁创建和销毁成本就很高了,对性能的影响比较大,因此它会缓存所有页面元素,只是当这些元素有变化时才去重绘页面。

而判断页面有无变化就依靠element了,每次widget变化时element会比较前后两个widget,只有当某一个位置的Widget和新Widget不一致,才会重新创建Element和widget;其他时候则只会修改renderObject的配置而不会进行耗费性能的RenderObject的实例化工作了。

flutter状态管理

单个页面内:直接setState;

InheritedWidget(Flutter 内建):

  • 是 Flutter 原生提供的跨组件通信机制。
  • 高效,但写法复杂、维护性差,通常用于封装底层依赖注入。

Provider(官方推荐):

  • 封装了 InheritedWidget,API 简洁,性能好。
  • 支持状态响应、依赖注入、懒加载等功能。
  • 更新的颗粒度不够、范围太大,consumer包住的东西都要状态更新。不能像changeNotifer那样更新某个数据。

Riverpod(推荐):

  • Provider 的升级版,支持纯函数、类型安全、无上下文依赖。
  • 状态管理 + 依赖注入二合一,逻辑清晰,测试友好。

Bloc / Cubit:

  • 强约束的状态管理框架,适合大型、复杂逻辑应用。
  • 基于事件驱动(Event → State),适合企业级项目。

Get:

  • 一体化框架(状态管理、路由、DI),轻量快速。
  • 写法简单,适合个人项目,但可维护性相对较弱。
  • 缺点, github 有1k+ issues

一个页面要想刷新一个页面,这个可以通过路由回调,超过两个页面,直接eventBus。

Flutter异步操作

Dart 是单线程模型,通过 事件队列(Event Queue)+ 微任务队列(Microtask Queue) 实现异步任务调度。

Dart 的事件循环由两个主要队列组成:

  1. 微任务队列(Microtask Queue)

    • 优先级高
    • scheduleMicrotask()Future((){}) 产生
  2. 事件队列(Event Queue / Message Queue)

    • 优先级低
    • 包含 Future.delayed()Timer、I/O、UI 事件等

事件循环机制会:

  • 不断先清空微任务队列
  • 然后执行事件队列中的第一个任务
  • 然后再清空新加入的微任务……

Stream 与 Future是什么关系?

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

Flutter和原生通信

Flutter通过Platform Channels机制实现与iOS(Swift/OC)和Android(Kotlin/Java)原生代码通信。

基本原理是基于异步消息传递:Flutter端发送消息到原生端,原生端收到后执行对应操作,再异步返回结果。

支持多种消息编解码格式,默认是StandardMessageCodec。

Flutter和原生以Method Channel、Event Channel和Basic Message Channel三种方式通信:

MethodChannel:Flutter调用原生方法,等待返回结果,适合请求-响应模式。

EventChannel:原生主动发送事件流给Flutter,适合传输设备状态或传感器数据等持续推送。

BasicMessageChannel: 双向数据流,传递自定义消息,适合复杂的交互。

MethodChannel:用于传递方法调用(method invocation)通常用来调用 native 中某个方法

BasicMessageChannel:用于传递字符串和半结构化的信息,这个用的比较少

EventChannel:用于数据流(event streams)的通信。有监听功能,比如电量变化之后直接推送数据给flutter端

三种 Channel 之间互相独立,各有用途,但它们在设计上却非常相近。每种 Channel 均有三个重要成员变量:   name: String类型,代表 Channel 的名字,也是其唯一标识符

messager:BinaryMessenger 类型,代表消息信使,是消息的发送与接收的工具

codec: MessageCodec 类型或 MethodCodec 类型,代表消息的编解码器  

wifi通信

  • Wi-Fi:基于 TCP/IP,通信范围大、速度快,适合局域网通信、大数据传输。

  • 蓝牙(BLE) :低功耗、短距离、基于 GATT 协议,适合 IoT 设备配对、控制和状态获取。

之前我做过的项目里,光伏储能行业里,有通信设备和采集器,通过wifi协议和设备交互, AR眼镜项目,是通过蓝牙和AR眼镜交互的。

  • 通常是通过 TCP/UDP 进行局域网通信;

  • 可使用 network_info_plus 获取当前 Wi-Fi 信息;

  • 使用 dart:io.Socket 实现 TCP 客户端连接设备;

  • 可通过 udp 插件广播设备发现。

Wifi通信流程

  1. 获取当前 Wi-Fi 信息
  • 使用插件:network_info_plus

    • 获取 IP、SSID、BSSID(设备唯一标识)
    • 判断是否连接特定热点(用于设备绑定)
  1. 控制 Wi-Fi 连接(连接某个热点)
  • Flutter 无法直接切换 Wi-Fi 热点,但可以通过原生插件或平台通道实现:

    • Android 可使用 wifi_iot
    • iOS 受系统限制(只能跳转设置页)
  1. Wi-Fi 通信(重点)
  • 使用 dart:io.Socket 实现 TCP 连接设备
  • 使用 udp 插件广播或监听局域网数据

Protobuf 协议

flutter中有Protobuf 协议,高效、轻量级、跨平台、跨语言的序列化协议。
它将结构化数据编码成紧凑的二进制格式,用于网络通信、数据存储等场景,比 JSON 更小更快。 之前采用这种数据格式和通信棒和采集器交互。

蓝牙通信

Flutter_blue_plus 三方库。

蓝牙本来有经典蓝牙和低功耗蓝牙,游戏手柄、耳机这种一般是经典蓝牙,

权限申请(分析不同平台差异,如Android需动态申请定位/蓝牙权限,iOS需在Info.plist声明)

设备扫描
过滤设备(如根据name或service UUID),降低误连概率

连接与断线重连
异步连接,结合超时/自动重连逻辑

服务&特征值发现
Discover并缓存对应特征,避免重复发现损耗

读写通信/监听通知
写命令、读数据、接收notify(如传感器推送数据)

1.国际化问题

语言国际化实现方案

目前flutter实现国际化的三方框架有intl和get,都可以实现国际化,在实际的项目中,我其实都有用到,最初是用intl实现国际化,后来因为get是一套工具框架,为了接入get生态,把intl换成了get。

Intl本身是配置arb文件,arb文件和json差不多。实际项目中用到的时候,发现一个缺点是,没法像json一样一个键值对嵌套着一个键值对声明。

我们项目里对每个小语种,包括英语、西班牙语、法语、、意大利语、日本语等小语种国际化文本用json配置,然后用脚本转换成get的控制器类。

这里涉及到一个国际化文本的翻译问题,首先项目中有接入三方框架deepl翻译来自动机翻,这个翻译有时没有那么专业化,有些词汇表需要和产品部的同事沟通确认,同时还要拉通其他端比如web、后端来确保翻译一致。

还涉及到一个问题是国际化界面布局,小语种一般都比较长,有些布局拓展性不好会显示异常,UI进行页面设计、开发页面布局的编码时需要注意小语种太长的情况,确保界面能够兼容中文和其他超长的小语种文本。

常见的业务问题

国际化业务

首先需要确定App是通过发不同的app包为不同国家地区提供服务,还是一个app用户通过进入app后选择国家地区站点的方式。

如果是抖音app这种大陆发一个、香港发一个app包这种,如果很多业务使用同一套代码,可以用flutter的flavor来弄。

Flutter 中的 flavor 本质上是通过配置不同的构建设置来创建应用的多种版本。比如开发环境的包、测试环境的包、国内生产环境的包、国外生产环境的包。

如果是一个app用户通过进入app后选择国家地区站点的方式。需要在具体的业务代码里,面对不同国家不同地区的做具体的判断。

  2.扫一扫怎么做的****

扫一扫可以分成两个部分,扫描二维码和生成二维码

扫描二维码分成多种情况,扫描光伏设备上的二维码和扫描云平台生成的电站二维码、个人信息二维码。

扫一扫的组件是基于mobile_scanner 三方库 二次封装的,业务中如果是扫描设备的二维码,设备的二维码的内容是设备的SN,扫描出结果后可以进行添加光伏逆变器设备的业务操作。

如果是平台生成的电站信息二维码或者个人信息二维码,扫出二维码的结果后,走解密流程得到二维码的信息,去走对应的电站添加设备业务。

 

3.三方库二次封装搭建****

网络请求库:DIO、

地图组件:location、geolocator三方库用来获取当前位置,国内使用百度地图、国外使用谷歌地图、商用地图授权。

json_serializable: 简化 JSON 数据与 Dart 对象之间的转换过程。自动生成序列化/反序列化代码

get是一套工具的的,包括路由、页面的脚手架,国际化语言、状态管理,都是用这个弄的

eventBus:用来跨页面刷新。

Context是什么****

在flutter中,BuildContext代表widget树中的位置,通过这个东西能找到widget的父控件和祖先控件

 

key是什么****

Key 是一个用于标识 Widget、Element 和 RenderObject 的对象。它的主要作用是帮助 Flutter 框架在 Widget 树更新时,正确地定位和匹配 Widget 的状态。  

常见的设计模式****

单例模式,工厂模式。

 

还有什么要问的?

(1) 这个岗位的工作重心,工作指责是什么

(2) 公司对这个岗位的期待是什么

(3) 目前团队人员结构和分工是怎样的

(4) 后续面试流程是怎样的

(5) 您能否分享一下您的工作体验是怎样的呢 ,当前您所在的团队或者业务有没有遇到什么问题、挑战、工作重心等,如果我加入需要优先解决什么工作或者问题。

(6) 您希望这个岗位在入职试用期内,半年内、一年分别达成什么样的目标

(7) 您对我这种经验大概一年半的程序员有什么建议,是持续加深打磨技术?更深入业务?  

Boss终面反问:****

您的团队风格是什么样的?什么样的候选人可以更好地融入您的团队?

您对这个岗位的预期是什么样的?更看重什么样的人才?

您对我这种年轻人有没有什么建议?

 

周围人对你的评价是什么?****

领导对我的评价是:可能经验上比不上那种十年开发经验的,但工作的态度是比较积极的,完成工作方面也是比较有责任心的。

同事的评价:平时同事之间主要是友好和善的沟通协作,不会进行互相评价哈。

有没有其他Offer****

我现在有几个公司还在最终面试中,但这次我看机会会慎重一些,还是期望去到一个合适的平台长期发展,但几轮面试聊下来,对贵公司的业务、所处行业已经了解的比较多了,在前几轮的面试过程中,和几位面试官聊的也还是比较好的,还是很期待拿到贵公司的机会。