面试
flutter基础
flutter中的key
widget是否复用
stf 和 stl 组件
flutter渲染
渲染流程
渲染原理
简单来说,Flutter 的渲染原理可以概括为:通过组合(Composition)而非继承(Inheritance)的方式构建视图,并使用自建的渲染引擎(Skia)直接在画布(Canvas)上进行绘制,从而实现对每一像素的精准控制,达到高效的渲染性能。
下面我们分步骤拆解这个过程。
核心三棵树:Widget -> Element -> RenderObject
Flutter 的渲染流程围绕着三棵核心的树形结构展开,它们协同工作,构成了响应式UI框架的基石。
Widget 树:
- 是什么:Widget 是您用代码编写的UI描述,是不可变的(Immutable)。它们就像蓝图,配置了Element和RenderObject应该如何被创建和更新。
- 特点:轻量级、不可变。当状态改变时,整个 Widget 树会重建,但这不是性能问题,因为重建的是“蓝图”本身,而不是底层的渲染实体。
- 例子:
Container,Text,Row,Column等。
Element 树:
- 是什么:Element 是 Widget 在 UI 树中的实例化对象。它负责管理 Widget 的生命周期,并将 Widget 的配置信息与底层的 RenderObject 连接起来。它是 Widget 和 RenderObject 之间的“粘合剂”。
- 特点:可变的、稳定的。当 Widget 树重建时,Flutter 会使用
diff算法对比新旧 Widget,然后尽可能地复用已有的 Element,只更新发生变化的 Element。这是 Flutter 高效的关键之一。 - 工作方式:
Element会检查新的 Widget 是否与旧的 Widget 是同一类型(runtimeType和key相同)。如果是,则更新现有的 Element 和 RenderObject;如果不是,则销毁旧的并创建新的。
RenderObject 树:
- 是什么:RenderObject 是真正负责布局(Layout)和绘制(Painting) 的对象。它保存了元素的几何信息(如大小、位置)。
- 特点:重量级、持久化。布局和计算是非常昂贵的操作,因此 RenderObject 树会尽量避免重建和重新计算。
- 核心方法:
layout():执行布局约束(由父节点传递下来的Constraints),计算自身大小(Size),并递归地对子节点进行布局。paint():在给定的画布(Canvas)上,根据布局阶段计算出的位置和大小,将自己绘制出来。
三棵树的关系图解:
// 你的代码
Widget build(BuildContext context) {
return Container( // Widget
color: Colors.blue,
child: Text('Hello'),
);
}
// 底层对应
Widget Tree: Container Widget -> Text Widget
| |
Element Tree: Container Element -> Text Element
| |
RenderObject树: RenderFlex -> RenderParagraph当你改变 Text 的字符串时,Text Widget 会重建,对应的 Element 会被复用,RenderParagraph 会被标记为“脏”(dirty),需要重新绘制。而 Container 的 Element 和 RenderObject 可能完全不受影响。
完整的渲染管线(Rendering Pipeline)
一个完整的 UI 更新(例如,因为 setState 触发了 Widget 树重建)会经历以下阶段:
动画(Animate):
- 渲染管线首先处理所有的动画(
Ticker)。动画的每一帧都会触发一次新的构建和渲染。
- 渲染管线首先处理所有的动画(
构建(Build):
setState()被调用,标记了某个 State 为“脏”。- Flutter 会重新执行
build方法,生成新的 Widget 树。 - Flutter 会将新的 Widget 树与旧的 Widget 树进行对比(Diff),并相应地更新 Element 树(复用、更新、创建或销毁 Element)。
- 注意:这个阶段只更新 Element 和 RenderObject 的配置,不进行布局和绘制。
布局(Layout):
- 布局过程是一个从根
RenderObject开始的递归过程。 - 约束向下传递(Constraints go down):父节点向子节点传递布局约束(例如,最大/最小宽度/高度)。
- 尺寸向上传递(Sizes go up):子节点根据约束决定自己的尺寸,然后告诉父节点。
- 如果一个 RenderObject 在构建阶段被标记为“需要布局”(dirty),或者它的约束发生了变化,它就会重新执行
layout方法。 - 布局完成后,每个 RenderObject 都拥有了一个确定的位置和大小。
- 布局过程是一个从根
绘制(Paint):
- 绘制过程也是一个递归过程,但顺序通常是自上而下。
- 如果一个 RenderObject 在布局阶段被标记为“需要绘制”(dirty),它就会重新执行
paint方法。 paint方法会接收一个Canvas对象,RenderObject 在这个画布上调用绘图指令(例如,画矩形、写文字、显示图片)来绘制自己。- 绘制过程会生成一个或多个
Layer(图层)。这些图层最终会被合成(Composited)到屏幕上。
合成(Compositing):
- 这是渲染管线的最后一步。Flutter 的渲染引擎(Skia)会将所有由
paint方法生成的图层合成为一张完整的画面。 - 对于需要硬件加速的图形操作(如变换、透明度),Flutter 会使用不同的图层来处理,以提高效率。
- 这是渲染管线的最后一步。Flutter 的渲染引擎(Skia)会将所有由
为什么这种设计是高效的?
声明式UI与响应式编程:你只需要描述“当前状态下的UI应该是什么样子”,而不需要关心如何从状态A更新到状态B。框架(Flutter)会自动、高效地帮你完成更新。
逻辑与渲染分离:轻量级的 Widget 负责业务逻辑和配置,重量级的 RenderObject 负责昂贵的布局和绘制。两者通过稳定的 Element 树连接,实现了高效的更新。
精细的重建:通过 Diff 算法和“脏”标记机制,Flutter 能够将 UI 变化的影响范围降到最低。改变一个文本,通常不会导致整个页面重新布局和绘制。
绕过原生控件,直接渲染:与 React Native 等框架不同,Flutter 不依赖平台的原生控件。它自己管理 RenderObject 树,并通过 Skia 引擎直接向画布(Canvas)绘制。这消除了桥接(Bridge)带来的性能开销,并保证了 UI 在不同平台上的一致性和高性能。
Flutter 的渲染原理可以概括为:
开发者编写不可变的 Widget -> Flutter 通过 Diff 算法更新可复用的 Element -> Element 驱动持有布局和绘制信息的 RenderObject -> RenderObject 通过 layout 和 paint 方法,利用 Skia 引擎直接在画布上绘制出最终界面。
这套机制确保了 Flutter 能够以每秒 60 帧(甚至 120 帧)的流畅度运行,并提供极其灵活和强大的 UI 构建能力。
setState流程
buildContext作用
Widget、Element、RenderObject
热重载实现原理
skia渲染逻辑
flutter状态管理
getx
provider
bloc
flutter框架进阶
flutter中的engine层、framework层
flutter原生通信
dart语法基础
dart进阶
dart异步实现机制
总结:异步IO+事件循环
事件队列 微任务队列 垃圾回收
1. Flutter 的消息队列与事件队列
Flutter 作为一个单线程模型的应用,其异步能力依赖于 事件循环(Event Loop) 机制。这个机制中包含了两个核心队列:
- 微任务队列(Microtask Queue)
- 事件队列(Event Queue)
事件循环(Event Loop)模型
Dart(以及 Flutter)的运行模型是一个单线程的、带有事件循环的模型。这个线程通常被称为 UI 线程 或 主线程。它的工作流程可以用下图表示:
启动App
|
v
执行main函数
|
v
启动事件循环
|
v
[ 事件循环开始 ]
|
|-----> 检查微任务队列 -----> 有任务? -----> 执行微任务(直到队列为空)
| ^
| | 无
| |
| v
|-----> 检查事件队列 -----> 有任务? -----> 取出第一个事件并执行
| ^
| | 无
| |
| |(应用可以退出)
|------------------------------a 微任务队列
- 优先级:最高。在当前事件处理和其他任何事件之前执行。
- 用途:用于非常紧急的、需要在这个事件循环周期内完成的短小任务。通常用于库内部的清理工作或状态同步,在业务代码中应谨慎使用。
- 调度方法:
scheduleMicrotask(() { ... })或Future.microtask(() { ... })。
b 事件队列
- 优先级:低于微任务队列。
- 用途:处理绝大多数异步操作,如:
- I/O 操作(文件读写、网络请求)
- 用户输入(点击、滑动)
- 定时器(
Future.delayed,Timer) - 绘制与布局事件
- 调度方法:通过
Future创建的任务,默认都会被加入到事件队列。
代码示例与执行顺序
void main() {
print('1. main start');
// 事件队列
Future(() => print('6. Event Queue Task'));
// 微任务队列
scheduleMicrotask(() => print('4. Microtask 1'));
// 通过 Future 创建的微任务
Future.microtask(() => print('5. Microtask 2'));
// 立即执行的 Future(仍然会在当前事件之后)
Future(() => print('3. Immediate Event Queue Task'))
.then((_) => print('7. Then callback (also microtask)'));
print('2. main end');
// 事件循环启动,开始处理队列中的任务
}输出顺序:
1. main start
2. main end
4. Microtask 1
5. Microtask 2
3. Immediate Event Queue Task
7. Then callback (also microtask)
6. Event Queue Task关键点:
then和await之后的代码,其回调会被包装成微任务,因此执行优先级高于普通事件。- 永远不要在主线程执行耗时操作(如大量计算、同步网络请求)。这会阻塞事件循环,导致页面卡顿甚至应用无响应(ANR)。耗时任务应使用 Isolate 在后台执行。
Dart 使用一种分代垃圾回收(Generational Garbage Collection) 机制,这与 Java, C# 等语言类似。它的设计目标是实现低延迟,避免因GC导致UI卡顿。
分代假说
GC 基于两个“假说”:
- 弱分代假说:绝大多数对象都是“朝生夕死”的(例如,在
build方法中创建的临时对象)。 - 强分代假说:存活得越久的对象,越不可能被回收。
基于此,Dart VM 将内存分为两块:
- 新生代(Young Generation)
- 老年代(Old Generation)
a 新生代
- 存储对象:新创建的、存活时间短的对象。
- 回收算法:采用 复制清除(Copying Collector)。
- 新生代空间被分为两块:活动区 和 空闲区。
- 对象首先在活动区分配。
- 当GC触发时,GC会遍历活动区的“活跃对象”,并将它们复制到空闲区。
- 然后,清空原来的活动区,并交换两个区的角色。
- 特点:速度极快。因为只处理“活跃对象”,不活跃的对象直接被丢弃。由于新生代空间小,且大部分对象都很快死亡,所以这个回收过程非常高效。
b 老年代
- 存储对象:在新生代中经历过多次GC后仍然存活的对象(被称为“晋升”)。
- 回收算法:采用 标记-清除-整理(Mark-Sweep-Compact)。
- 标记:从GC根对象(如全局变量、当前执行栈上的变量)开始,遍历所有可达对象,并标记为“活跃”。
- 清除:遍历整个老年代内存,将所有未被标记的对象回收。
- 整理:(可选,非每次进行)为了避免内存碎片,GC会将存活的对象移动到内存的一端。
- 特点:速度较慢。因为需要遍历整个老年代空间。但触发的频率远低于新生代GC。
GC 与 Flutter 性能
- 避免卡顿:Dart 的 GC 是 增量式的(Incremental) 和 并发的(Concurrent)。这意味着 GC 过程可以被分成很多小步骤,并与应用程序的代码执行交错进行,而不是一次性地“停止世界”(Stop-The-World),从而极大地减少了单次GC造成的UI卡顿。
- 开发建议:
- 避免创建大量小对象:虽然在新生代GC很快,但频繁触发GC和对象晋升也会带来开销。在
build方法或动画的builder中尤其要注意。 - 使用
const构造函数:对于不变的 Widget,使用const可以避免在每次重建时创建新的实例,既减少了GC压力,又提高了性能。const Container()优于Container()
- 及时销毁监听器:对于
AnimationController,ScrollController等,在dispose方法中一定要调用dispose(),以便GC可以回收它们。 - 小心闭包:避免在长时间存在的对象中持有对 BuildContext 或其他大型对象的引用,这可能导致它们无法被回收。
- 避免创建大量小对象:虽然在新生代GC很快,但频繁触发GC和对象晋升也会带来开销。在
总结:三者如何协同工作
- 你编写代码:包括同步代码和异步代码(
Future,async/await)。 - 任务入队:你的异步任务被分配到微任务队列或事件队列中。
- 事件循环驱动:事件循环 按优先级(微任务 -> 事件)从队列中取出任务执行。
- 内存分配与回收:任务执行过程中创建的对象在堆内存中分配。Dart VM 的垃圾回收器会默默地在后台工作,自动回收不再使用的内存,确保应用不会因内存泄漏而崩溃。
- 核心目标:这套机制的最终目标是确保 UI 线程的流畅。通过高效的异步模型和低延迟的GC,Flutter 能够实现 60fps/120fps 的丝滑用户体验。
业务
ios开发者账号维护
打包/上架
平常是怎么打包的?如果我一个原生的app里面引入了flutter的话我应该怎么打包?
支付
支付踩坑
购买漏包如何解决?

