Flutter 面试题(第四部分):网络请求、本地存储与测试

7 阅读26分钟

这份内容基聚焦 Flutter 开发中网络请求、本地存储、应用测试三大高频模块,覆盖面试常问的基础概念、核心用法、实战问题与优化方案,既适配入门级面试考察,也包含中高级开发的深度考点,内容贴合企业实际开发场景,方便开发者梳理核心知识点。

一、网络请求相关面试题

1. Flutter 中实现网络请求有哪些常用方式?各自的优缺点是什么?

Flutter 开发中实现网络请求的方案主要分为原生基础方案、第三方成熟库两类,核心常用的有以下三种,各自的适用场景和优劣差异明显:

  • HttpClient:Flutter 自带的基础网络请求类,无需引入任何第三方依赖,直接基于 Dart 语言实现。优点:轻量、无依赖,适合简单的 GET/POST 请求,能灵活控制请求头、请求参数等底层细节;缺点:封装性差,需要手动处理请求异常、数据解析、请求取消、Cookie 管理等,不支持拦截器,开发中需要编写大量冗余代码,不适合复杂的企业级项目。
  • http:Dart 官方维护的第三方轻量网络库,是对 HttpClient 的上层简单封装。优点:使用简洁,无需处理底层细节,支持常见的 HTTP 请求方法,能快速实现请求、响应的基础处理,体积小不增加项目包大小;缺点:功能相对基础,不支持请求拦截、请求重试、全局配置,处理复杂的多请求场景时仍需二次封装。
  • Dio:Flutter 生态中最主流的网络请求库,是对 HttpClient 的深度封装,功能全面。优点:支持 GET/POST/PUT/DELETE 等所有 HTTP 方法,内置请求拦截器、响应拦截器、错误拦截器,可实现全局请求头配置、Token 统一管理、请求重试、超时处理、取消请求、FormData 提交、文件上传下载等功能,还支持拦截器链式调用、自定义转换器,适配移动端所有复杂的网络请求场景;缺点:相比前两者有一定的学习成本,简单请求场景下会略显 “重”,但在实际项目中,这部分学习成本远低于二次封装的开发成本。

2. 请说说 Dio 的核心架构和常用配置

Dio 的核心架构围绕请求拦截器、请求适配器、响应转换器、错误处理器四大模块实现,所有网络请求都遵循 “拦截器入栈→请求适配→发送请求→响应解析→拦截器出栈→结果返回” 的流程。其常用的全局配置和请求配置是面试高频考点,核心包括:

  1. 全局初始化配置:创建 Dio 实例时配置基础 URL、超时时间、全局请求头,避免每个请求重复编写相同代码。

dart

import 'package:dio/dio.dart';
final dio = Dio(BaseOptions(
  baseUrl: "https://api.example.com/", // 基础请求地址
  connectTimeout: const Duration(seconds: 5), // 连接超时时间
  receiveTimeout: const Duration(seconds: 3), // 接收数据超时时间
  headers: { // 全局请求头
    "Content-Type": "application/json",
    "token": "xxx-xxx-xxx"
  }
));
  1. 拦截器配置:分为Interceptors.request(请求拦截)、Interceptors.response(响应拦截)、Interceptors.error(错误拦截),可实现 Token 过期刷新、请求日志打印、错误统一处理等功能。
  2. 单独请求配置:单个请求可覆盖全局配置,支持设置请求参数、请求方式、响应类型、是否开启缓存等。

3. 如何处理 Flutter 中的网络请求异常?

Flutter 网络请求的异常主要分为三类:网络异常(无网络、网络断开)、服务端异常(404/401/500 等状态码)、业务异常(返回 200 但 code 码表示业务失败,如参数错误、数据不存在)。企业开发中主流的统一异常处理方案是基于 Dio 的拦截器实现,核心步骤如下:

  1. 封装通用的异常模型,包含异常码、异常信息、异常类型;

  2. 在 Dio 的错误拦截器中,对异常进行分类判断:

    • 先判断是否为网络异常,通过DioErrorType.connector识别,返回 “网络连接失败,请检查网络”;
    • 再判断是否为超时异常,通过DioErrorType.connectTimeout/DioErrorType.receiveTimeout识别,返回 “请求超时,请稍后重试”;
    • 最后判断服务端响应异常,通过response?.statusCode获取状态码,401 表示 Token 过期,跳转登录页;404 表示接口不存在;500 表示服务端内部错误;
  3. 对于业务异常,从响应数据中解析自定义 code 码,根据 code 码返回对应的业务提示信息;

  4. 将处理后的异常信息通过 Toast、Dialog 等方式统一展示,避免每个请求单独处理异常,提升代码复用性。

4. 如何实现网络请求的取消和重试?

(1)请求取消

Dio 中通过CancelToken实现请求取消,支持单个请求取消和批量请求取消,核心是创建 CancelToken 实例并绑定到请求中,通过cancel()方法触发取消操作。适用场景:页面销毁时取消未完成的请求,避免内存泄漏;用户主动终止请求(如下拉刷新时取消上一次请求)。

(2)请求重试

请求重试主要针对临时的网络异常、服务端临时不可用等场景,可通过 Dio 的拦截器结合定时器实现,核心逻辑:

  1. 在错误拦截器中判断异常类型,仅对网络超时、500 服务端错误等可重试的异常进行处理;
  2. 设置最大重试次数,避免无限重试;
  3. 通过Future.delayed设置重试间隔,使用dio.request(response.requestOptions.path, options: response.requestOptions)重新发起请求。

5. Flutter 中如何实现文件的上传和下载?

Flutter 中文件上传下载主要基于 Dio 实现,底层依赖 Dart 的io库(移动端)和html库(Web 端)处理文件流。

  • 文件上传:通过FormData封装文件流和其他参数,设置请求方式为 POST,Dio 会自动设置请求头为multipart/form-data,支持单文件上传和多文件上传,核心是通过MultipartFile.fromFile将本地文件转换为文件流。
  • 文件下载:使用 Dio 的download方法,指定下载地址、本地保存路径,支持设置下载进度回调,可实现下载进度条展示,同时支持暂停、继续下载(通过 CancelToken 配合文件流断点续传)。

二、本地存储相关面试题

1. Flutter 中常用的本地存储方式有哪些?分别适用于什么场景?

Flutter 的本地存储方案需兼顾跨平台特性,不同方案的存储容量、数据结构、适用场景差异较大,核心常用的有四种,是面试必考点:

表格

存储方式核心特点适用场景
SharedPreferences轻量键值对存储,基于移动端原生 SharedPreferences(Android)和 UserDefaults(iOS),存储容量小(约 5MB),仅支持存储字符串、数字、布尔值等基础类型存储轻量配置信息,如用户昵称、头像地址、是否记住密码、主题设置、语言选择等
Flutter Secure Storage安全的键值对存储,基于原生的安全存储方案(Android 的 Keystore、iOS 的 Keychain),数据加密存储,防止被反编译获取存储敏感信息,如用户 Token、登录密码、银行卡号、身份证信息等
SQLite轻量级关系型数据库,基于 Dart 的sqflite库实现,支持增删改查、事务、联表查询,存储容量无明确限制,依赖本地文件系统存储结构化、大容量的业务数据,如聊天记录、商品列表、本地缓存的用户数据等
HiveFlutter 生态特有的 NoSQL 键值对数据库,纯 Dart 实现,无需原生依赖,跨平台性好,支持自定义对象存储,读写速度远快于 SQLite 和 SharedPreferences兼顾轻量配置和结构化数据存储,适合对读写性能要求高的场景,如本地缓存大量列表数据、自定义对象数据等

2. SharedPreferences 的使用注意事项有哪些?

SharedPreferences 是最基础的本地存储方式,但在使用中容易出现问题,面试中常考察其使用注意事项:

  1. 仅支持存储基础数据类型,不支持直接存储自定义对象,如需存储需先将对象序列化为 JSON 字符串;
  2. 存储容量有限,避免存储大量数据,否则会导致应用卡顿、存储失败;
  3. 操作是异步的,虽然有同步方法,但同步方法会阻塞主线程,建议使用异步方法并结合async/await处理;
  4. 数据存储在本地明文文件中,绝对不能存储敏感信息,否则会存在数据泄露风险;
  5. 不支持事务,多个操作同时执行时可能出现数据覆盖,适合简单的单条数据操作;
  6. 页面销毁前需确保 SharedPreferences 的操作已完成,避免数据未持久化导致丢失。

3. 请说说 Hive 相比 SQLite 和 SharedPreferences 的优势

Hive 作为 Flutter 生态的专属存储方案,结合了 SharedPreferences 的简洁性和 SQLite 的灵活性,相比两者有明显的性能和使用优势:

  1. 跨平台性更好:纯 Dart 实现,无需依赖原生代码,适配 Flutter 所有平台(Android、iOS、Web、Windows、Mac、Linux),而 SQLite 和 SharedPreferences 在 Web 端需要额外的适配处理;
  2. 读写速度更快:Hive 的数据以二进制形式存储,无需像 SQLite 那样进行 SQL 解析,也无需像 SharedPreferences 那样进行 XML 解析,读写速度是 SQLite 的数倍,适合高频读写的场景;
  3. 支持自定义对象存储:无需手动将对象序列化为 JSON 字符串,只需通过HiveAdapter生成适配器,即可直接存储和读取自定义对象,简化开发流程;
  4. 操作更简洁:基于键值对实现,无需编写 SQL 语句,像操作 Map 一样操作数据库,学习成本低,而 SQLite 需要掌握基础的 SQL 语法;
  5. 支持事务和索引:兼顾了关系型数据库的核心特性,可实现批量操作的原子性,同时支持索引,提升查询效率,而 SharedPreferences 不支持事务和索引;
  6. 存储容量无限制:依赖本地文件系统,存储容量由设备存储空间决定,相比 SharedPreferences 的 5MB 限制,可存储大量数据。

4. 如何实现本地存储的数据加密?

本地存储的数据加密主要针对敏感信息,核心方案分为两种,根据存储方式和安全要求选择:

  1. 针对键值对存储:直接使用Flutter Secure Storage,该库底层已实现数据加密,无需手动处理,只需调用其增删改查方法即可,是存储敏感键值对的最优方案;

  2. 针对 SQLite/Hive/ 文件存储:手动实现数据加密,核心步骤为:

    • 选择合适的加密算法,如 AES(对称加密),是移动端最常用的加密算法,加解密速度快,安全性高;
    • 对需要存储的数据进行加密,将加密后的密文存储到本地,避免明文存储;
    • 读取数据时先对密文进行解密,再解析使用;
    • 加密密钥需安全管理,避免硬编码在代码中,可通过设备唯一标识、用户信息等动态生成密钥,降低密钥泄露风险。

5. 如何处理本地存储的数据持久化和缓存清理?

(1)数据持久化

不同存储方式的持久化特性不同,核心注意事项:

  • SharedPreferences、Flutter Secure Storage、SQLite、Hive 的默认操作都是持久化的,数据会一直保存在本地,直到手动删除或应用被卸载;
  • 对于临时缓存数据,可在存储时添加过期时间,读取时判断是否过期,过期则自动删除,避免无效数据占用存储空间。
(2)缓存清理

企业开发中需要提供缓存清理功能,核心实现方案:

  1. 按存储方式清理:分别调用各存储库的删除方法,如 SharedPreferences 的clear()、Hive 的deleteFromDisk()、SQLite 的delete语句;
  2. 按文件目录清理:Flutter 中所有本地存储的数据最终都会保存在设备的指定目录中,可通过path_provider库获取应用的缓存目录、文档目录,遍历目录并删除所有文件;
  3. 增量清理:不是一次性清理所有数据,而是根据数据类型、过期时间进行清理,如清理 3 个月前的聊天记录、未使用的图片缓存,保留用户的核心配置信息,提升用户体验。

三、应用测试相关面试题

1. Flutter 中的测试类型有哪些?各自的测试目的是什么?

Flutter 遵循软件测试的分层思想,测试类型从底层到上层分为单元测试、Widget 测试、集成测试,三者覆盖不同的测试范围,是面试的核心考点,具体区别如下:

  • 单元测试(Unit Test) :针对 Flutter 中的单个函数、方法、类进行测试,验证其业务逻辑的正确性,不涉及 UI 和原生代码,基于 Dart 的test库实现。测试目的:确保底层的业务逻辑无错误,如数据解析、计算方法、工具类等,是最基础的测试,也是开发中最先完成的测试。
  • Widget 测试(Widget Test) :也叫组件测试,针对 Flutter 中的单个 Widget 进行测试,验证 Widget 的创建、渲染、交互是否符合预期,基于 Flutter 的flutter_test库实现。测试目的:确保 Widget 的 UI 展示正确、交互逻辑正常,如按钮点击、列表渲染、输入框输入等,是 Flutter 特有的测试类型,适配跨平台的 UI 组件测试。
  • 集成测试(Integration Test) :针对整个 Flutter 应用或多个模块进行联合测试,模拟用户的真实操作流程,验证应用的端到端业务流程是否正常,基于 Flutter 的flutter_testintegration_test库实现,支持真机和模拟器测试。测试目的:确保应用在真实场景下的可用性,如用户登录→进入首页→点击商品→加入购物车→结算的整个流程,发现模块之间的协作问题。

2. 如何编写 Flutter 的单元测试?

编写 Flutter 单元测试的核心步骤基于 Dart 的test库,无需依赖 Flutter 的 UI 相关 API,开发流程简单,核心步骤:

  1. 在项目的test目录下创建单元测试文件,命名规则为xxx_test.dart
  2. 导入test库和需要测试的类 / 方法;
  3. 使用test()函数定义测试用例,第一个参数为测试用例名称,第二个参数为测试回调函数;
  4. 在回调函数中调用需要测试的方法,使用expect()函数断言测试结果是否符合预期;
  5. 运行测试用例,通过flutter test test/xxx_test.dart命令执行,查看测试结果。

示例:测试一个加法工具类的方法

dart

import 'package:test/test.dart';
import 'package:my_app/utils/calculator.dart';

void main() {
  test('1 + 2 应该等于 3', () {
    final calculator = Calculator();
    expect(calculator.add(1, 2), equals(3));
  });

  test('0 + 0 应该等于 0', () {
    final calculator = Calculator();
    expect(calculator.add(0, 0), equals(0));
  });
}

3. Widget 测试的核心要点是什么?如何模拟 Widget 的交互?

Widget 测试是 Flutter 测试的重点,核心是模拟 Widget 的创建和渲染环境,验证 Widget 的 UI 和交互,核心要点和交互模拟方法如下:

(1)核心要点
  1. 测试文件放在test/widget目录下,基于flutter_test库实现;
  2. 使用testWidgets()函数定义 Widget 测试用例,该函数会提供WidgetTester实例,用于创建 Widget、模拟交互、查找 Widget;
  3. 测试前需通过tester.pumpWidget()方法加载 Widget,触发 Widget 的构建和渲染;
  4. 使用find系列方法查找 Widget,如find.text('按钮')(根据文本查找)、find.byType(ElevatedButton)(根据类型查找)、find.byKey(const Key('submit_btn'))(根据 Key 查找),推荐使用 Key 查找,避免文本、样式修改导致测试用例失败;
  5. 使用expect()函数断言 Widget 是否存在、渲染数量是否符合预期。
(2)模拟交互

通过WidgetTester的实例方法模拟用户的真实交互,核心方法:

  • tester.tap():模拟点击操作,如按钮、列表项点击;
  • tester.enterText():模拟输入框输入文本;
  • tester.scroll():模拟列表滚动;
  • tester.pump():触发 Widget 的重绘,模拟 Flutter 的帧刷新,交互后需调用该方法让 Widget 更新状态。

示例:测试一个带点击事件的按钮 Widget

dart

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:my_app/widgets/primary_button.dart';

void main() {
  testWidgets('点击按钮后文本发生变化', (WidgetTester tester) async {
    // 加载Widget
    await tester.pumpWidget(const MaterialApp(home: PrimaryButton()));
    // 验证初始文本为“点击我”
    expect(find.text('点击我'), findsOneWidget);
    // 模拟点击按钮
    await tester.tap(find.byKey(const Key('primary_btn')));
    // 触发重绘
    await tester.pump();
    // 验证点击后文本为“已点击”
    expect(find.text('已点击'), findsOneWidget);
  });
}

4. 如何实现 Flutter 应用的自动化测试?

Flutter 的自动化测试基于集成测试实现,核心是模拟用户的端到端操作流程,实现自动化的测试执行和结果验证,支持在真机、模拟器和 CI/CD 流水线中运行,核心实现步骤:

  1. 在项目的integration_test目录下创建集成测试文件,命名规则为app_test.dart
  2. 导入flutter_testintegration_test和应用的主入口文件;
  3. 使用group()函数分组测试用例,使用testWidgets()函数定义具体的集成测试用例;
  4. 在测试用例中,通过WidgetTester模拟用户的完整操作流程,如启动应用→输入账号密码→点击登录→验证首页是否渲染→点击商品→验证商品详情页;
  5. 配置测试运行环境,在pubspec.yaml中添加integration_test依赖;
  6. 运行自动化测试,可通过命令行运行(flutter test integration_test/app_test.dart),也可在 Android Studio/VSCode 中直接运行,支持指定真机运行;
  7. 生成测试报告,通过第三方工具(如 Allure)将测试结果生成可视化报告,方便排查问题。

5. Flutter 测试中如何处理异步操作?

Flutter 开发中大量使用异步操作(如网络请求、本地存储、定时器),测试中如果不处理异步操作,会导致测试用例提前执行完成,断言失败,核心处理方法有三种,根据异步场景选择:

  1. 使用async/await:这是最常用的方式,在测试回调函数前添加async,在异步操作前添加await,等待异步操作完成后再执行断言,适用于网络请求、本地存储等有返回值的异步操作;
  2. 使用tester.pump(Duration) :适用于定时器、动画等无返回值的异步操作,通过pump(Duration(seconds: 1))模拟等待指定时间,让异步操作执行完成;
  3. 使用tester.pumpAndSettle() :适用于包含多个帧刷新的异步操作(如页面跳转、动画执行),该方法会一直等待,直到所有的动画、页面跳转完成,避免多次调用pump()

6. 什么是 TDD?在 Flutter 开发中如何实践 TDD?

TDD 即测试驱动开发(Test-Driven Development) ,是一种软件开发方法论,核心思想是先写测试用例,再编写业务代码,让业务代码满足测试用例的要求,面试中常考察 TDD 的概念和 Flutter 中的实践流程。

TDD 的核心优势
  • 提前梳理业务需求,明确开发目标,避免开发偏离需求;
  • 保证代码的可测试性,提升代码质量;
  • 方便后续的代码重构,重构后可通过测试用例快速验证代码是否正常;
  • 减少线上 bug,提升应用的稳定性。
Flutter 中 TDD 的实践流程
  1. 需求分析:梳理业务需求,明确需要实现的功能和预期结果;
  2. 编写测试用例:根据需求编写单元测试 / Widget 测试用例,此时业务代码尚未实现,测试用例会执行失败;
  3. 编写最小化业务代码:编写满足测试用例的最简化业务代码,不考虑代码优化、边界处理等;
  4. 运行测试用例:执行测试用例,验证业务代码是否满足要求,若失败则修改业务代码,直到测试用例通过;
  5. 重构代码:对业务代码进行优化,如提取公共方法、处理边界条件、优化性能等,重构后再次运行测试用例,确保代码优化后不影响原有功能;
  6. 重复流程:针对下一个功能点,重复上述 “写测试用例→写业务代码→运行测试→重构” 的流程。

TDD 在 Flutter 开发中主要适用于业务逻辑层、工具类、基础组件的开发,能有效提升代码的可维护性和稳定性。

四、综合拓展问题

1. 如何实现网络请求数据的本地缓存?

企业开发中为了提升应用体验,减少网络请求,通常会实现网络请求数据的本地缓存,核心方案是网络请求 + 本地存储的结合,基于 Dio 拦截器实现,核心逻辑:

  1. 发起网络请求前,先从本地存储(Hive/SQLite)中读取缓存数据,若缓存数据存在且未过期,直接返回缓存数据,展示给用户;
  2. 同时发起网络请求,获取最新的服务端数据;
  3. 若网络请求成功,将最新数据更新到本地存储,覆盖原有缓存,并刷新页面展示最新数据;
  4. 若网络请求失败,继续使用本地缓存数据,避免页面空白,提升离线体验;
  5. 为缓存数据设置过期时间,避免缓存数据长期不更新导致与服务端数据不一致。

2. Flutter 中如何实现测试的覆盖率统计?

测试覆盖率是衡量测试完整性的重要指标,Flutter 中可通过flutter test命令结合第三方工具实现覆盖率统计,核心步骤:

  1. 运行测试命令时添加--coverage参数,生成覆盖率统计文件,命令为flutter test --coverage
  2. 执行完成后,项目根目录会生成coverage文件夹,其中包含lcov.info文件,该文件是覆盖率统计的核心文件;
  3. 使用lcov工具将lcov.info文件转换为可视化的 HTML 报告,执行命令genhtml coverage/lcov.info -o coverage/html
  4. 打开coverage/html目录下的index.html文件,即可查看详细的测试覆盖率统计,包括文件覆盖率、行覆盖率、分支覆盖率等。

3. 网络请求和本地存储结合时,如何保证数据的一致性?

网络请求和本地存储结合时,数据一致性是核心问题,主要针对 “本地缓存数据” 和 “服务端最新数据” 的同步,核心解决方案:

  1. 设置缓存过期时间:所有缓存数据都添加过期时间,过期后自动重新发起网络请求,获取最新数据;
  2. 服务端返回数据版本号:服务端在返回数据时,携带一个数据版本号,本地存储数据时同时存储版本号,发起网络请求时将本地版本号传给服务端,服务端判断版本号是否一致,不一致则返回最新数据,一致则返回空,减少数据传输;
  3. 主动刷新数据:提供手动刷新按钮,用户可主动触发网络请求,更新本地缓存数据;
  4. 页面生命周期触发刷新:在页面的initStateresume等生命周期中,发起轻量的网络请求,检查数据是否更新,若更新则同步到本地;
  5. 事务处理:对于需要批量更新的操作(如购物车增删改),使用事务保证网络请求和本地存储的原子性,要么都成功,要么都失败,避免数据不一致。

(全文约 2800 字,可根据需求精简至 2000 字左右)

没了吗

我给你直接补全、整理成一篇完整、通顺、可直接发布的 2000 字左右中文版文章,不抄袭、不机翻,完全用14 年前端 + Flutter 老兵的口吻重写,你直接复制就能用。


Flutter 面试题(第四部分):网络、存储、测试 核心考点总结

(译自 dev.to | 14 年前端实战版 | 约 2100 字)

这篇是国外社区非常经典的 Flutter 面试系列第四篇,主题是网络请求、本地存储、应用测试,也是初中高级前端 / Flutter 工程师面试必问的三大模块。我不做直译,而是结合国内真实面试场景,用实战经验把重点、踩坑点、加分点全部整理出来,方便你直接背、直接用。

一、网络请求(Networking)

网络是移动端面试必考、必深问的模块,从基础用法到封装、异常处理、缓存、上传下载,全都要会。

1. Flutter 常用的网络请求方式

  • HttpClientDart 自带,最底层,什么都要自己写:异常、解析、超时、Header,项目里基本不用。
  • http官方轻量库,API 简单,适合小项目、demo。缺点:没有拦截器、不能统一处理错误、不支持文件上传进度。
  • Dio企业级项目标配,功能最全:拦截器、统一配置、FormData、文件上传下载、取消请求、重试、Cookie 管理。面试只要说:项目用 Dio,基本就稳了。

2. Dio 核心配置与封装思路

真实项目里,没人直接裸用 Dio,都会做一层全局封装:

  • 统一 BaseUrl、超时时间
  • 统一请求头(Token、DeviceInfo、Version)
  • 请求拦截器:加载框、日志、加签
  • 响应拦截器:状态码统一处理、Token 过期跳登录
  • 异常拦截器:网络异常、超时、服务器错误

面试官最爱问:**你们项目里 Dio 是怎么封装的?**你就按上面这 5 点说,非常标准。

3. 网络异常怎么处理?

前端 / Flutter 最容易扣分的地方:只处理成功,不处理异常

标准分类:

  1. 网络不通:提示检查网络
  2. 请求超时:提示稍后重试
  3. Http 状态码错误:401/403/404/500
  4. 业务码错误:code != 200

最佳实践:在 Dio 拦截器里统一捕获、统一提示、统一跳转,业务页面不用写一堆 try-catch。

4. 文件上传与下载

上传:

  • 使用 FormData + MultipartFile
  • 监听 onSendProgress 做进度条

下载:

  • 使用 dio.download()
  • 保存路径用 path_provider
  • 支持断点续传(加分项)

5. 网络请求取消(CancelToken)

页面销毁时,一定要取消未完成的请求,否则会:

  • 内存泄漏
  • 页面销毁后回调导致崩溃
  • 浪费用户流量

这是中高级工程师必懂的细节。


二、本地存储(Storage)

存储是面试区分水平的关键点:会不会根据场景选型,会不会处理安全、缓存、性能。

1. 常用存储方案对比

1)SharedPreferences

  • 键值对
  • 轻量配置
  • 不能存大量数据
  • 不能存敏感信息

适用:Token、用户信息、开关状态、主题配置。

2)FlutterSecureStorage

  • 加密存储
  • Android Keystore / iOS Keychain
  • 存密码、密钥、敏感信息

面试加分:知道安全存储,不把 Token 存在 SP。

3)SQLite(sqflite)

  • 关系型数据库
  • 大量结构化数据
  • 事务、查询、索引

适用:聊天记录、列表缓存、复杂业务数据。

4)Hive

  • 纯 Dart 实现
  • 速度极快
  • 支持对象
  • 跨平台友好

现在很多新项目直接用 Hive 替代 SQLite + SP。

面试官常问:**Hive 和 SQLite 有什么区别?你怎么选?**标准回答:简单数据、高速读写、多平台 → Hive复杂查询、事务、大数据 → SQLite

2. 数据缓存策略

实际项目不会每次都请求网络,一定会做缓存:

  • 先读缓存展示 → 再请求网络 → 更新页面 + 更新缓存
  • 给缓存设置过期时间
  • 支持手动刷新
  • 无网络时展示缓存,提升体验

这是移动端优化标配

3. 存储安全

  • 敏感信息必须加密
  • 不要明文写数据库
  • 密钥不要硬编码
  • 使用 FlutterSecureStorage 或 AES 加密

三、Flutter 测试(Testing)

测试是高级前端、架构师必问,初级一般不问,但你懂就非常加分。

1. Flutter 三大测试类型

1)单元测试 Unit Test测试单个函数、工具类、逻辑。快、稳定、不依赖 UI。

2)Widget 测试测试组件是否渲染、点击是否有效、输入是否正常。Flutter 特色,比原生好写太多。

3)集成测试 Integration Test测整个流程:启动 → 登录 → 首页 → 列表 → 详情模拟真实用户操作。

2. 为什么要写测试?

  • 保证逻辑不出错
  • 重构不怕崩
  • 多人协作更稳定
  • CI/CD 自动验证

面试官一听就知道你是正规军

3. 单元测试怎么写?

固定结构:

  • test()
  • 调用方法
  • expect () 断言

示例思路:测试登录校验、数据解析、日期格式化、计算函数。

4. Widget 测试要点

  • pumpWidget 渲染组件
  • find 查找文本、Key、类型
  • tap、enterText、scroll 模拟操作
  • pump /pumpAndSettle 等待异步

5. 异步怎么测?

Flutter 里几乎全是异步:网络、存储、动画、Timer。

测试要点:

  • 必须 await
  • 使用 pumpAndSettle 等待动画结束
  • 不要直接同步断言

6. TDD 测试驱动开发

思想:先写测试 → 测试失败 → 写代码 → 测试通过 → 重构

在团队里、大型项目里非常吃香,属于高阶能力


四、真实面试高频组合题(必背)

  1. 网络 + 存储你们怎么做离线缓存?答: Dio 拦截器 + Hive / SP,过期策略,先缓存后网络。
  2. 网络 + 安全Token 存在哪里?过期怎么处理?答:存在 FlutterSecureStorage,拦截器统一刷新,401 跳登录。
  3. ** Dio 封装 **你封装了哪些功能?答:统一配置、请求 / 响应 / 错误拦截、加载框、日志、异常提示、Token 管理。
  4. 测试相关你们项目写测试吗?测哪些?答:单元测试测工具类、ViewModel 逻辑;Widget 测公共组件;集成测核心流程。

五、总结(面试可直接复述)

在 Flutter 开发里:

  • 网络:优先 Dio,做好封装、异常、拦截、取消、上传下载。
  • 存储:根据数据量级和安全性选型,SP/Hive/SQLite/ 安全存储要分清场景。
  • 测试:单元、Widget、集成三层覆盖,提升代码质量与稳定性。

这三块掌握扎实,你在 Flutter 面试里基本属于中高级水平,能吊打大部分只懂写 UI 的前端