【Flutter】面试记录
本文部分内容参考博文
目录
- Flutter三棵树渲染原理
- 渲染原理
- 三者之间的关系
- 参数
- 位置参数
- mixin、extends和implements
- mixin(混入)
- extends(继承)
- implements(实现)
- Flutter 如何与 Native 通信的?
- 如何从Flutter传递一个dart类到原生?
- 常用的三种状态管理框架
- provider的渲染机制
- 二叉树遍历
- 递归与迭代
- 什么是迭代?
- 什么是递归?(怎么写一个递归函数)
- 怎么在页面加载完成后对页面进行一些逻辑处理?
- Flutter与其他一些跨平台框架(react、uniapp)的区别?
- 渲染方式
- 性能
- 开发语言
- 适用场景
- 生态
- 一句话选型:
- flutter项目怎么提升性能减少卡顿(运行时内存过大怎么优化)?
- 1.优化 Widget 树与渲染:
- 2.优化动画与帧率
- 3.内存与资源管理
- 4.平台相关优化
- 5.监控与持续优化
- 总结
- ListView 包含大量项时,页面滑动时隐藏的项会自动被回收和重新渲染是怎么做的?
Flutter三棵树渲染原理
虽然咱平时没用到,但面试官说理论要懂,好排查问题
- WidgetTree
- 存放渲染内容、它只是一个配置数据结构,创建是非常轻量的,在页面刷新的过程中随时会重建。
- 用于存储渲染所需的信息,例如布局、样式等。
- Element
- 分离 WidgetTree 和真正的渲染对象的中间层, WidgetTree 用来描述对应的Element 属性,同时持有Widget和RenderObject,存放上下文信息,通过它来遍历视图树,支撑UI结构。
- 是实例化的 Widget 对象,通过 Widget 的 createElement()方法生成,是控件树上的实体。
- 维护着 Widget 的状态信息,使得即使 Widget 发生变化,状态也能得以保存。
- RenderObject
- 负责管理布局、绘制等操作,保存了元素的大小、布局等信息。
- (渲染树)用于应用界面的布局和绘制,负责真正的渲染
渲染原理
- 当应用启动时 Flutter 会遍历并创建所有的 Widget 形成 Widget Tree
- 通过调用 Widget 上的 createElement() 方法创建每个 Element 对象,形成 Element Tree。
- 最后调用 Element 的 createRenderObject() 方法创建每个渲染对象,形成一个 Render Tree。
三者之间的关系
- Widget 会被 inflate(填充)到 Element 中,并由 Element 管理底层渲染树。
- Element 是渲染树的节点,它包含了 Widget 的配置信息和对应的 RenderObject
- RenderObject 则根据 Widget 的配置进行布局和绘制。
参数
位置参数
哈哈哈 以为参数很简单 结果一问位置参数 我说我没听说过呀 原来是用过的…
比如说:最常用的DateTime 啊啊啊这不就是吗 只是平时定义方法时很少自己用到这个
DateTime(int year,[int month = 1,int day = 1,int hour = 0,int minute = 0,int second = 0,int millisecond = 0,int microsecond = 0]): this._internal(year, month, day, hour, minute, second, millisecond,microsecond, false)
mixin、extends和implements
mixin(混入)
mixin用于在类中添加额外的功能或行为。它可以将一个类的代码片段“混入”到另一个类中,从而实现代码复用。mixin类通常包含一些通用的方法或属性,可以被多个其他类使用。mixin类不能被实例化,只能被其他类通过with关键字混入使用。
extends(继承)
extends用于创建子类,子类可以继承父类的属性和方法。父类的私有属性和方法不会被继承,子类可以重写父类的方法来实现自己的逻辑。extends是一种单继承关系,一个类只能有一个直接父类。
implements(实现)
implements用于实现接口。接口是一种抽象的定义,它只定义了类需要实现的方法或属性,而不提供具体的实现。类通过implements关键字来声明实现某个接口,并实现接口中定义的方法。
在实际应用中,选择使用哪种关系取决于具体的需求。如果需要在类中添加通用的功能,可以使用mixin;如果需要创建子类并继承父类的功能,可以使用extends;如果需要实现接口的定义,可以使用implements。
Flutter 如何与 Native 通信的?
可恶,不知道native的意思就是原生层!!!
如何从Flutter传递一个dart类到原生?
- 首先,在 Dart 端定义你的类,并将其序列化为一个 Map 或者 JSON 字符串以便于跨平台通道传输。
- 接下来,在 Flutter 端创建一个 MethodChannel,并调用相应的 native 方法,传递序列化后的数据。
- 在 Android 端(假设使用 Kotlin),你需要设置对应的 MethodChannel 来接收来自 Flutter 的消息,并反序列化接收到的数据。
常用的三种状态管理框架
- Provider:通过 InheritedWidget 传递数据,子组件可以通过 context访问数据
- Bloc:使用事件和状态的方式进行数据传递,通过发送事件来更新状态。
- GetX:使用控制器来管理状态,通过依赖注入的方式将状态传递给子组件。
响应式:
- Provider:本身不具备响应式,需要手动通知子组件更新。
- Bloc:基于 Stream 实现响应式,当状态发生变化时,会自动通知订阅者更新。
- GetX:通过 .obs 实现响应式,当状态发生变化时,会自动通知依赖该状态的组件更新。
使用场景:
- Provider:适用于简单的状态管理场景,如全局配置、主题等。
- Bloc:适用于复杂的状态管理场景,如表单验证、异步操作等。
- GetX:适用于快速开发、性能要求较高的场景,提供了丰富的 API 和路由管理功能。
provider的渲染机制
通过监听其提供的数据的变化来决定何时重新构建依赖于这些数据的 widgets。
二叉树遍历
哈哈还考了这个 也是没想到
概述:二叉树遍历是指按照某种顺序访问二叉树中的所有节点。常见的遍历方式包括前序遍历、中序遍历、后序遍历和层序遍历。每种遍历方式都有其特定的应用场景。
- 前序遍历(Preorder Traversal)
- 顺序:根节点 → 左子树 → 右子树。
- 特点:先访问根节点,再递归遍历左子树和右子树。
- 应用场景:用于复制二叉树、序列化二叉树等。
def preorder(root):if not root:return print(root.val) # 访问根节点 preorder(root.left) # 遍历左子树 preorder(root.right) # 遍历右子树
- 中序遍历(Inorder Traversal)
- 顺序:左子树 → 根节点 → 右子树。
- 特点:先递归遍历左子树,再访问根节点,最后遍历右子树。
- 应用场景:用于二叉搜索树(BST)的中序遍历可以得到有序序列。
def inorder(root):if not root:return inorder(root.left) # 遍历左子树 print(root.val) # 访问根节点 inorder(root.right) # 遍历右子树
- 后序遍历(Postorder Traversal)
- 顺序:左子树 → 右子树 → 根节点。
- 特点:先递归遍历左子树和右子树,最后访问根节点。
- 应用场景:用于删除二叉树、计算表达式树等。
def postorder(root):if not root:return postorder(root.left) # 遍历左子树 postorder(root.right) # 遍历右子树 print(root.val) # 访问根节点
- 层序遍历(Level Order Traversal)
- 顺序:从上到下、从左到右逐层访问节点。
- 特点:使用队列实现,按层级顺序访问节点。
- 应用场景:用于查找二叉树的最大宽度、最短路径等。
from collections import deque def level_order(root):if not root:return queue = deque([root])while queue:node = queue.popleft() print(node.val) # 访问当前节点 if node.left: queue.append(node.left) # 加入左子节点 if node.right: queue.append(node.right) # 加入右子节点
总结:
复杂度:时间复杂度为 O(n),空间复杂度取决于树的高度(递归)或队列大小(层序遍历)。
递归与迭代
什么是迭代?
迭代是通过重复执行一段代码来解决问题的方法,通常使用循环结构(如 for、while)来实现。(其实就是循环对吧,平时压根不叫迭代,每次都是名词替换给我整蒙了)
什么是递归?(怎么写一个递归函数)
递归是通过函数调用自身来解决问题的方法,通常需要一个明确的终止条件(基线条件)来避免无限递归。
怎么在页面加载完成后对页面进行一些逻辑处理?
WidgetsBinding.instance?.addPostFrameCallback((_) async {//执行一些逻辑操作});
Flutter与其他一些跨平台框架(react、uniapp)的区别?
渲染方式
Flutter:自绘UI(Skia引擎),不依赖原生组件,性能高。
React Native:通过JS桥接调用原生组件,依赖平台渲染。
Uniapp:WebView渲染(小程序端优化为原生)。
性能
Flutter ≈ 原生 > React Native > Uniapp(H5端)。
开发语言
Flutter:Dart;React Native:JS/TS;Uniapp:Vue。
适用场景
Flutter:复杂应用(如高交互需求);
React Native:中轻量级应用;
Uniapp:小程序/H5快速开发。
生态
React Native生态最成熟,Flutter增长快,Uniapp国内为主。
一句话选型:
要性能/一致性 → Flutter;
要生态/灵活性 → React Native;
做小程序 → Uniapp。
flutter项目怎么提升性能减少卡顿(运行时内存过大怎么优化)?
1.优化 Widget 树与渲染:
- 减少不必要的重建:使用 const 、StatelessWidget构造Widget。
- 高效列表渲染:ListView.builder、ListView.separated、复杂滚动场景使用 CustomScrollView + SliverList。
- 避免过度绘制:使用 RepaintBoundary 隔离高频更新的区域(如动画组件)、检查 Debug Paint(debugPaintSizeEnabled)识别冗余绘制。
2.优化动画与帧率
- 使用原生动画:优先选择 AnimatedContainer、TweenAnimationBuilder 等内置动画,而非手动控制帧;复杂动画使用 Rive 或 Flare(现Lottie)替代逐帧绘制。
- 控制动画粒度:避免同时运行多个高耗能动画(如透明度+缩放+位移),及时释放资源。
- 60FPS 保障:通过 DevTools 的 Frame Chart 检查丢帧,确保每帧处理时间 <16ms。
3.内存与资源管理
- 图片优化:cached_network_image 缓存网络图片,避免重复下载。压缩大图资源,适配分辨率(如 flutter_native_splash 优化启动图)。
- 及时释放资源:在 dispose() 中释放控制器(如 ScrollController、AnimationController);使用 WeakReference 避免内存泄漏(如全局状态监听)。
- 隔离耗时操作:将计算密集型任务(如JSON解析)放到 Isolate 中执行;使用 compute() 简化Isolate调用。
4.平台相关优化
- 减少通道(Channel)通信:批量处理 MethodChannel 调用,避免频繁跨平台通信;使用 Pigeon 生成类型安全的通道代码。
- 编译优化:发布模式启用 AOT编译:flutter build --release;禁用调试工具:确保 App.initState() 中无 debugPrint。
5.监控与持续优化
- 性能分析工具:Flutter DevTools(检查帧率、内存、Widget重建)、Dart Observatory(分析内存泄漏和CPU占用)。
- 集成 firebase_performance 或 Sentry 监控用户端卡顿日志。
总结
- 高频优化点:列表渲染、动画控制、内存释放。
- 关键工具链:DevTools + RepaintBoundary + Isolate。
- 长期策略:定期性能测试,结合用户反馈持续迭代。
ListView 包含大量项时,页面滑动时隐藏的项会自动被回收和重新渲染是怎么做的?
其实就是ListView.builder
,只是平时没注意ListView
和ListView.builder
的区别。
- ListView.builder 是懒加载的,只会构建当前视口内的项,而不是一次性构建所有项。
- Flutter 使用一种称为 “懒加载” 或 “视口回收”
的机制来优化性能,确保只有当前可见的项会被渲染到屏幕上,而隐藏的项会被回收以节省内存。