Flutter基础知识总结

22 阅读9分钟

一、参数

分类修饰符核心使用场景极简示例
变量 / 空安全(核心)final变量仅赋值一次(运行时确定值),不可修改final String name = '张三';(无法再赋值 name = '李四'
const编译时常量(值固定),不可变,适合全局常量static const int maxAge = 100;(编译期确定,内存复用)
late延迟初始化非空变量(空安全),避免声明时赋值late User user = Provider.of(context);(使用时才初始化)
?标记变量为可空类型,允许赋值 nullString? username; username = null;(可空,使用需判空)
!强制解包可空变量(断言非空,慎用)String? name = '张三'; print(name!.length);(确保非空时用)
required构造函数参数必须传值(替代旧版 @required)User({required this.name});(实例化必须传 name)
类 / 继承(高频)static静态成员(属于类,非实例),全局复用class Math { static int add(int a, int b) => a + b; }(调用:Math.add(1,2)
abstract抽象类(不可实例化),定义接口规范abstract class Shape { void draw(); }(子类必须实现 draw 方法)
extends单继承类(Dart 唯一继承方式)class Student extends Person {}(继承 Person 的属性 / 方法)
implements实现接口(多实现),必须重写所有成员class Dog implements Animal { @override void eat() {} }
mixin/with混入复用方法(多混入),替代多继承mixin Flyable { void fly() {} } class Bird with Flyable {}
函数 / 方法(高频)async/await异步函数,处理网络 / IO 等耗时操作Future<String> fetchData() async { return await http.get('url'); }
factory工厂构造函数,控制实例创建(单例 / 缓存)factory Singleton() => _instance ??= Singleton._internal();
@override标记重写父类方法(编译器检查正确性)@override void draw() {}(确保正确重写抽象方法)
访问控制(核心)_(私有)文件级私有成员,跨文件不可访问class User { String _password; }(仅当前文件能访问 _password)
默认(公有)无修饰符,跨库 / 跨类可见class User { String name; }(任意地方可访问 name)

1、访问控制修饰符

修饰方式语法示例可见范围说明
公开 (Public)var name class MyClass全局可见默认情况。只要不以 _ 开头,任何导入该文件的代码都可以访问。
私有 (Private)var _name class _MyClass库内可见标识符前加下划线 _。 仅在当前文件 (.dart) 内可见。 即使是同一个包下的其他文件也无法访问。

2、成员变量修饰符

可变性与常量修饰符含义典型场景
final运行时常量。 变量只能被赋值一次,值在运行时确定。构造函数传入的参数、需要在 initState 中初始化的状态。
const编译时常量。 变量必须在编译时就知道确切值,且对象本身必须是不可变的。配置项、数学常数、纯静态的 Widget 树。
late延迟初始化。 告诉编译器:“我保证在使用前会赋值,请先别报空安全错误”。 类型是非空的,但初始化被推迟了。依赖注入、需要在 initState 异步加载后赋值的变量、循环引用。
作用域与生命周期修饰符含义典型场景
static静态成员。 属于类本身,不属于某个实例。所有实例共享同一份内存。工具方法、单例模式、全局计数器、常量池。

3、dynamic参数

dynamic 并不是一个“修饰符” (像 finalstatic 或 @override 那样放在变量前面改变其行为的关键字),而是一个特殊的类型关键字声明为 dynamic 的变量,编译器会跳过所有的类型检查

什么时候使用 dynamic 参数?
  1. 处理 JSON/反序列化:JSON 数据结构复杂且嵌套,字段类型不固定时。
  2. 通用工具函数:例如一个打印日志的函数,需要接受任何类型的对象。
  3. 反射或元编程:需要动态调用对象方法时。
  4. 遗留代码兼容:迁移旧代码时暂时绕过类型检查。
特性dynamicObject?var (推断)
类型检查无 (关闭检查)有 (严格检查)有 (基于初始值锁定类型)
方法调用允许调用任何方法/属性只能调用 Object 的方法 (如 toString, hashCode)只能调用推断出的类型的方法
赋值灵活性可以随时赋值为任何类型可以赋值为任何对象 (因为是 Object?)一旦推断完成,不能赋值为其他类型
安全性低 (运行时才报错)高 (编译时报错)高 (编译时报错)
典型用途黑盒数据、反射、JSON接收任意对象但只进行通用操作局部变量,类型明确

二、任务队列

特性微任务队列 (Microtask)事件队列 (Event Queue)主队列 (同步代码)
优先级⭐⭐⭐ (最高)⭐⭐ (中)⭐⭐⭐ (正在执行)
典型 APIscheduleMicrotask(), async/await (部分), Future 内部回调Future.delayed(), Timer, HttpClient, GestureDetector, vsync普通函数调用, print, 变量赋值
执行时机当前同步代码结束后,立即执行,且在下一个事件任务之前。微任务清空后,按顺序逐个执行。立即执行,阻塞后续所有任务。
主要用途- 状态管理的微小更新 - 需要在当前逻辑结束后立即触发的内部回调 - 避免竞态条件的临时锁- I/O 操作 (网络/文件) - 定时器 - 用户交互事件 - 延时任务- 业务逻辑计算 - UI 构建 - 数据转换
潜在风险阻塞事件队列:如果微任务太多或太慢,会导致 UI 事件(点击、滚动)和网络回调延迟处理。堆积延迟:如果单个事件任务耗时过长,会推迟后续事件和渲染。直接卡死:长时间运行会直接导致掉帧、ANR。
类比"插队":办完手头的事,马上处理这批紧急内部文件,处理不完不让别人进来。"排队":按顺序办理业务,办完一个再办下一个。"正在办理":柜台正在服务的客户。
  1. 默认不用管:90% 的场景下,你只需要写 async/await 或使用 Future,Dart 会自动帮你安排到合适的队列(通常是微任务处理回调,事件任务处理 I/O)。
  2. 避免手动 scheduleMicrotask:除非你是框架开发者或处理极其特殊的竞态问题,否则不要手动将任务放入微任务队列。
  3. 警惕微任务爆炸:不要在微任务中进行耗时计算或递归调用,这会直接饿死事件队列,导致 App 无响应。
  4. 耗时操作去 Isolate:如果任务真的很重(无论是微任务还是事件任务),请将其移动到单独的 Isolate 中运行,不要阻塞主队列。

三、InheritedWidget

InheritedWidget 是 Flutter 框架中状态共享和数据传递的基石。

简单来说,它的核心作用是:允许子组件(Subtree)高效地“向上”查找并监听父组件(Ancestor)中的数据,当父组件数据变化时,自动通知依赖该数据的子组件重建。

它是 Flutter 中所有状态管理方案(如 ProviderRiverpodBuildContext.dependOnInheritedWidgetOfExactType 等)的底层实现原理。

特性InheritedWidgetRiverpod
定位底层原语 (Primitive) Flutter SDK 的一部分。高层解决方案 (Solution) 第三方库 (pub.dev)。
依赖关系无依赖,是框架基石。不再依赖 InheritedWidget (v2.0+)。 它自己维护了一个独立于 Widget 树的“提供者容器 (ProviderContainer)”。
主要职责仅负责:数据传递 + 依赖通知。 不负责状态的创建、销毁或复杂逻辑。负责:状态创建、依赖注入、生命周期管理、缓存、自动 dispose、测试支持。

Riverpod 解决了 InheritedWidget 所有的痛点(Context 依赖、测试难、组合难、生命周期管理繁琐),是现代 Flutter 开发的首选状态管理方案之一。

维度ProviderRiverpod优势分析
依赖上下文强依赖 BuildContext 必须在 Widget 树内使用。零依赖 BuildContext 通过 ref 访问,可在任何地方使用。Riverpod 胜:解决了在路由、服务层、定时器中无法访问状态的痛点。
安全性运行时安全 缺少 Provider 会编译通过,运行时报错崩溃。编译时安全 如果 Provider 未定义或类型错误,编译直接报错。Riverpod 胜:大幅减少线上崩溃,重构更安全。
代码样板较多 需定义类、继承 ChangeNotifier、手动 notifyListeners较少 全局变量定义,自动通知,无需手动管理监听器。Riverpod 胜:代码更简洁,逻辑更清晰。
状态组合困难 一个 Provider 依赖另一个需要嵌套或复杂逻辑。原生支持 ref.watch(otherProvider) 轻松组合,自动处理依赖图。Riverpod 胜:适合复杂业务逻辑。
生命周期手动/被动 需配合 Disposable 等模式手动清理。自动管理 支持 autoDispose,无监听时自动销毁,防止内存泄漏。Riverpod 胜:资源管理更智能。
测试难度较难 需构建 Widget 树并包裹 Provider。极易 可创建独立的 ProviderContainer 进行纯单元测试,无需 UI。Riverpod 胜:测试驱动开发 (TDD) 友好。
学习曲线低 概念简单,官方推荐,文档丰富。中 概念较多 (Provider, Ref, Notifier, Family 等),但一旦掌握效率极高。Provider 胜 (入门阶段)。

四、Isolate、compute、async/await

特性原生 Isolate (手动创建)compute 函数 (Flutter 封装)Dart 异步 (async/await)
本质Dart 轻量级隔离线程,独立内存 / 事件循环Isolate 的单层封装(简化版)单 Isolate 内的事件循环调度
内存模型完全隔离,不共享内存完全隔离(底层仍是 Isolate)共享 Main Isolate 内存
通信方式SendPort/ReceivePort 消息传递自动封装消息传递(仅单次返回)无跨 Isolate 通信
生命周期手动创建 / 销毁 / 管理自动创建→执行任务→销毁无独立生命周期
任务类型支持单次 / 持续 / 双向通信任务仅支持单次、无状态任务非耗时 IO 任务(如网络请求)
返回值支持多次返回(如进度)仅单次返回(任务结束后)单次返回
资源消耗可复用(手动控制),轻量每次调用创建新 Isolate,用完销毁无额外资源消耗
使用复杂度高(需手动管理通信 / 生命周期)低(一行代码调用)极低

总结:

  • Isolate是 Dart/Flutter 的并发机制,核心特点是内存隔离、消息通信,避免了传统线程的锁问题;

  • 轻量级使用推荐compute函数,复杂场景手动创建Isolate并管理SendPort/ReceivePort

  • 选择原则:简单单次耗时任务用compute,需进度 / 持续通信用原生Isolate,非 CPU 密集型异步任务用async/await;async/await仅适用于 “等待型” 任务(IO、延迟),CPU 密集型任务(如循环计算)仍会阻塞 UI;本质是 Main Isolate 内的事件调度,不是真正的并发。

  • 核心差异Isolate/compute是跨隔离区并发(内存隔离),async/await是单隔离区异步(内存共享);

  • 性能优化:高频耗时任务优先复用原生Isolate,避免频繁创建compute