本文主要涉及Flutter 性能相关的概念,如渲染、包体积管理、懒加载、线程相关、以及并发操作使用的Dart隔离概念。
一、性能优化概览
在Flutter中,性能优化是提升用户体验的关键,它涉及多个方面,包括流畅度、内存管理、应用大小和功耗。以下是对这四个方面的详细分析:
一、流畅度优化
流畅度是指应用程序在用户交互时的响应速度和界面更新的平滑程度。为了提升Flutter应用的流畅度,可以采取以下措施:
- 减少不必要的计算和重绘:使用Opacity和Visibility组件来控制组件的可见性,避免直接设置组件的visible属性导致的重绘。同时,使用ListView.builder和GridView.builder等滚动组件的builder方法来构建长列表或网格,以减少不必要的计算和重绘。
- 优化动画效果:使用AnimatedWidget和AnimatedBuilder等组件来实现动画效果,但需要注意将不需要变化的widget作为child传递给AnimatedBuilder,从而只构建一次。此外,避免在动画中裁剪,尽可能在动画开始之前预先裁剪图像。
- 使用高效的渲染技术:例如,使用IndexedStack来减少不必要的子组件绘制,以及使用RepaintBoundary将具有复杂绘制的子树包装起来,作为单个图层处理。
二、内存管理优化
内存管理是指应用程序在运行过程中如何分配、使用和释放内存。为了优化Flutter应用的内存管理,可以采取以下措施:
- 避免内存泄漏:使用WeakReference和SoftReference等弱引用和软引用来管理内存,以避免内存泄漏。同时,注意在不需要时及时释放内存,例如使用GarbageCollector类来回收不再使用的对象。
- 优化图片加载:使用cached_network_image等第三方库来缓存网络图片,减少重复加载所需的内存。此外,调整图片资源文件的大小或使用cacheWidth和cacheHeight参数来指定解码大小,以减少内存使用。
- 使用高效的组件:例如,使用const和final来声明不可变的对象,减少内存分配和垃圾回收的压力。同时,避免在build方法中执行耗时操作,以减少内存占用和CPU使用。
三、应用大小优化
应用大小是指应用程序安装包的大小,它直接影响到用户的下载和安装体验。为了优化Flutter应用的大小,可以采取以下措施:
- 代码拆分:将应用程序拆分成多个模块或组件,并根据需要动态加载它们。这可以减少初始安装包的大小,并允许用户在需要时下载额外的功能或内容。
- 压缩资源文件:对应用程序中的图片、音频和视频等资源进行压缩处理,以减少它们的大小。同时,使用合适的图片格式和压缩算法来进一步减小资源文件的大小。
- 移除不必要的依赖:检查应用程序的依赖项,并移除那些不再需要或可以替换为更轻量级库的依赖项。这有助于减小应用程序的大小并提高其性能。
四、功耗优化
功耗是指应用程序在运行过程中消耗的电量或能量。为了优化Flutter应用的功耗,可以采取以下措施:
- 减少CPU和GPU的使用:通过优化渲染性能、减少不必要的计算和重绘以及使用高效的动画效果来降低CPU和GPU的使用率。这有助于减少应用程序在运行时的功耗。
- 优化网络请求:使用高效的网络库和协议来发起网络请求,并尽量减少网络请求的次数和数据量。这有助于降低网络传输过程中的功耗,并加快数据的加载速度。
- 使用节能模式:在可能的情况下,将应用程序设置为节能模式或低功耗模式。这可以通过减少后台任务、降低屏幕亮度或关闭不必要的传感器等方式来实现。
二、Impeller 渲染引擎
在Flutter开发中,Impeller 是一个相对较新的渲染引擎后端,旨在替代现有的 Skia 渲染引擎,以提供更高的性能和更好的跨平台一致性。
渲染优化性能的原因
1、更高效的图形处理:Impeller 使用了更现代的图形 API 和技术,如 Vulkan 和 Metal,这些 API 通常比 Skia 使用的 OpenGL 或 Direct3D 提供了更高的性能和更好的硬件加速能力。
2、减少渲染开销:Impeller 通过优化渲染管道和减少不必要的渲染调用,降低了渲染开销。这有助于减少 CPU 和 GPU 的负担,从而提高应用程序的响应速度和流畅度。
3、更好的跨平台一致性:Impeller 旨在提供一个更加一致的渲染体验,无论你是在 Android、iOS 还是其他平台上运行 Flutter 应用程序。这有助于减少因平台差异而导致的渲染问题,并提高应用程序的兼容性和稳定性。
4、自定义渲染管道:Impeller 允许开发者更加灵活地自定义渲染管道,以满足特定的性能需求或视觉效果。这有助于开发者在保持应用程序性能的同时,实现更加独特和吸引人的视觉效果。
5、可预测的性能(Predictable performance)
- Impeller 在构建时离线编译所有着色器(shaders)和反射(reflection),并预先构建所有管道状态对象(pipeline state objects)。这种离线预处理的方式有助于减少运行时的开销,并提供更稳定的性能表现。
- 引擎控制缓存并显式地进行缓存操作,这有助于确保渲染资源的有效利用和减少不必要的重复计算。
6、可仪器化(Instrumentable)
- Impeller 对所有图形资源(如纹理和缓冲区)进行标记和标签处理,这使得开发者能够轻松地跟踪和监控这些资源的使用情况。
- 它能够捕获动画并将其持久化到磁盘上,而不会影响每帧的渲染性能。这对于性能分析和调试非常有用。
7、可移植性(Portable)
- Flutter 没有将 Impeller 绑定到特定的客户端渲染 API 上。这意味着 Impeller 可以灵活地适应不同的平台和图形后端。
- 开发者可以编写一次着色器代码,并根据需要将其转换为特定后端的格式。这有助于确保跨平台的渲染一致性和兼容性。
8、利用现代图形 API(Leverages modern graphics APIs)
- Impeller 使用现代图形 API(如 Metal 和 Vulkan)提供的特性,但这些特性并不是必需的依赖项。这意味着 Impeller 可以在不依赖这些现代特性的情况下运行,同时仍然能够利用它们来提高性能。
- 通过利用这些现代 API,Impeller 能够实现更高效的渲染、更好的硬件加速和更低的功耗。
9、利用并发性(Leverages concurrency)
- Impeller 能够根据需要将单帧的工作量分布在多个线程上执行。这有助于提高渲染的并行度和减少渲染延迟。
- 通过利用多核 CPU 和 GPU 的并行处理能力,Impeller 能够更好地应对高负载场景,并提供更流畅的用户体验。
Impeller 开启/关闭设置
确保你的 Flutter 环境是最新的,并且可能还需要一些额外的配置步骤(这些步骤可能会随着 Flutter 的更新而变化)
1、iOS 平台
默认启用 Impeller:
在 Flutter 的最新版本中,对于 iOS 平台,Impeller 渲染引擎是默认启用的。这意味着,当您在 iOS 设备或模拟器上运行 Flutter 应用程序时,它会自动使用 Impeller 进行渲染。
调试时禁用 Impeller:
如果您在调试过程中希望禁用 Impeller,可以通过向 flutter run 命令传递 --no-enable-impeller 参数来实现。这样做可以让您的应用程序在调试期间使用旧的渲染引擎(通常是 Skia),而不是 Impeller。
flutter run --no-enable-impeller
部署时禁用 Impeller:
当您准备将 Flutter 应用程序部署到生产环境时,如果您希望禁用 Impeller,可以在应用程序的 Info.plist 文件中添加特定的键值对。这样做可以确保您的应用程序在发布版本中使用旧的渲染引擎。
在 Info.plist 文件中,您需要找到或添加顶层的 <dict> 标签,并在其内部添加以下 XML 代码:
<key>FLTEnableImpeller</key>
<false />
2、Android 平台
您提供的信息是关于 Flutter 在 Android 平台上 Impeller 渲染引擎的默认启用行为以及如何禁用它的详细说明。以下是对您所提供信息的整理和解释:
默认启用 Impeller
在 Flutter 的最新版本中,对于 Android 平台,Impeller 渲染引擎同样是默认启用的。这意味着,当您在 Android 设备或模拟器上运行 Flutter 应用程序时,它会自动尝试使用 Impeller 进行渲染。
Vulkan 和 OpenGL 的兼容性
- Vulkan 支持:Impeller 渲染引擎主要依赖于 Vulkan 图形 API,因为它提供了高性能和低延迟的渲染能力。
- OpenGL 回退:对于那些不支持 Vulkan 的设备,Impeller 会自动回退到使用旧的 OpenGL 渲染器。这种回退行为是自动的,您不需要进行任何额外的操作或配置。
调试时禁用 Impeller
如果您在调试过程中希望禁用 Impeller,可以通过向 flutter run 命令传递 --no-enable-impeller 参数来实现。这样做可以让您的应用程序在调试期间使用旧的渲染引擎(如 OpenGL),而不是 Impeller。
flutter run --no-enable-impeller
部署时禁用 Impeller
当您准备将 Flutter 应用程序部署到生产环境时,如果您希望禁用 Impeller,可以在 Android 项目的 AndroidManifest.xml 文件中添加特定的 <meta-data> 标签。
在 <application> 标签内部添加以下 XML 代码:
<meta-data
android:name="io.flutter.embedding.android.EnableImpeller"
android:value="false" />
三、性能优化最佳实践
在 Flutter 开发中,理解帧的构建和渲染时间对于优化应用性能至关重要
1、帧渲染时间
- 构建与渲染时间
- Flutter 的构建(即 UI 元素的创建和布局)和渲染(即将这些元素绘制到屏幕上)是在两个独立的线程上进行的。这意味着它们可以并行处理,从而提高效率。
- 对于 60Hz 的显示器,每一帧的显示时间为 16.67ms(1秒 / 60帧)。然而,由于 Flutter 的构建和渲染是分开的,理想情况下,我们希望构建和渲染各自都能在 16ms 或更短的时间内完成,以确保在下一帧开始之前能够完成当前帧的所有工作。这实际上意味着构建和渲染各自应该努力在 8ms 或更短的时间内完成,以留出一些缓冲时间应对不可预见的情况。
- 性能优化的重要性
- 即使在 profile 构建状态下,每一帧的渲染时间低于 16ms,开发者仍然应该致力于优化性能。这是因为,虽然从视觉上看可能没有明显的卡顿,但减少渲染时间可以延长电池寿命、减少设备发热,并为用户提供更流畅的体验。
- 此外,性能优化还可以确保应用在各种设备上都能保持良好的表现,特别是那些性能较低的设备。
- 未来设备的发展趋势
- 随着高刷新率设备的普及(如 120Hz 或更高),对渲染时间的要求将变得更加严格。为了在这些设备上提供流畅的体验,开发者需要在更短的时间内完成每一帧的渲染。例如,对于 120Hz 的设备,每一帧的显示时间为 8.33ms,因此开发者需要确保构建和渲染各自都能在 8ms 或更短的时间内完成(实际上可能需要更短的时间以留出缓冲)。
- 60fps 的平滑视觉体验
- 60fps(每秒60帧)之所以带来平滑的视觉体验,是因为人眼的视觉暂留效应。当帧率足够高时,人眼无法区分单个帧之间的间隔,从而产生了连续运动的错觉。
- 视频“60fps是啥意思?”可能提供了更详细的解释和示例,帮助开发者理解为什么高帧率对于提供流畅的用户体验至关重要。
2、基础优化实践
-
使用最新版本的Flutter
- Flutter不断更新和改进,加入新功能、修复错误和提高性能。
- 通过保持与最新版本的Flutter同步,可以确保应用性能达到最佳状态。
-
最小化小部件的重建,分拆和封装庞大的widget
- 使用
shouldRebuild方法来确定小部件是否需要重建。 - 通过比较小部件的当前状态和前一个状态,减少不必要的重建。
- 如果
build()方法返回的widget过于庞大或复杂,应该考虑将其分拆成更小的、可复用的widget。 - 通过封装,可以提高代码的可读性和可维护性,同时也有助于减少不必要的重建和性能开销。
- 使用
-
使用无状态小部件,使用
StatelessWidget而不是函数- 无状态小部件没有可变状态,构建速度比有状态小部件快。
- 使用无状态小部件可以减少构建时间,提高应用启动速度和响应性。
- 在构建可复用的UI代码时,应该优先考虑使用
StatelessWidget而不是函数。 StatelessWidget提供了更丰富的生命周期管理和状态管理功能,同时也更容易与Flutter的widget系统集成。- 函数式组件在某些情况下可能更简洁,但
StatelessWidget提供了更好的封装性和可测试性。
-
使用
const关键字const关键字可以创建编译时常量,优化小部件的渲染和布局。- 使用
const可以避免不必要的重绘和内存分配。 - 当widget的构造函数参数在构建过程中不会改变时,应该使用
const构造函数来创建widget实例。 - 这有助于Flutter框架识别出哪些widget实例是恒定的,从而避免不必要的重建。
- 启用
flutter_lints包中的推荐lints可以帮助自动提醒使用const。
-
优化列表渲染
- 使用
ListView.builder小部件来延迟创建和显示列表项。 - 只创建可见的项目,减少内存和CPU使用。
- 使用
-
利用widget树的遍历优化
-
Flutter框架在构建widget树时,会检查每个widget是否与前一帧相同。如果相同(使用
operator==进行比较),则不会遍历其后代widget。 -
这意味着,如果两个widget实例在逻辑上是相同的(即它们的状态和属性没有改变),那么它们的后代widget也不会被重建。
-
因此,应该尽量保持widget实例的稳定性和可比较性,以利用这一优化。
-
3、高级优化实践
- 优化图像加载
- 使用
cached_network_image包来缓存图像,减少网络请求次数。 - 使用淡入图像技术来改善应用的感知性能。
- 使用
- 使用状态管理库
- 状态管理是构建高性能Flutter应用的重要方面。
- 使用如Provider、Redux和BLoC等状态管理库来集中管理应用状态,减少UI重建次数。
- 使用正确的数据结构
- 根据使用场景选择合适的数据结构,如列表、映射、集合等。
- 使用高效的数据结构可以提高应用性能。
- 优化构建过程
- 使用发布模式构建生产环境的应用,生成运行速度更快、内存消耗更少的代码。
- 使用代码拆分来减小应用大小并提高性能。
- 使用延迟加载
- 延迟加载资源,如图像,直到需要它们时才加载。
- 使用Flutter框架提供的延迟加载方法,如可见性类和滚动控制器类。
4、动画与网络性能优化
- 优化动画
- 使用
AnimatedBuilder小部件而不是AnimatedWidget小部件。 - 将动画逻辑与小部件本身分离,减少UI重建次数。
- 使用缓动动画而不是曲线动画,定义动画的开始和结束值。
- 使用
- 优化网络性能
- 使用高效的网络请求库,如
HttpClient、Dio或http。 - 根据网络请求的结果更新UI,减少不必要的网络请求。
- 使用高效的网络请求库,如
5、性能分析工具与调试
- 使用性能分析工具
- Flutter提供了几个内置的性能分析工具,如Dart Observatory和DevTools中的Flutter性能选项卡。
- 使用这些工具可以实时监控应用的性能,并识别性能瓶颈。
- 利用Profile模式进行性能调试
- Profile模式使用AOT预编译模式,支持使用DevTools进行性能检测和分析。
- 通过Profile模式可以获取应用的性能数据,进行针对性的优化。
6、其他优化实践
-
使用
Offstage来隐藏组件- 将不经常显示或只在特定条件下显示的组件从渲染树中移除,减少布局和绘制开销。
-
使用
RepaintBoundary- 将具有复杂绘制的子树包装在
RepaintBoundary中,减少布局和绘制的工作量。
- 将具有复杂绘制的子树包装在
-
使用
LayoutBuilder和ConstrainedBox- 根据父组件的大小动态调整子组件的大小,减少不必要的布局计算。
-
避免在
build方法中执行耗时操作-
在
initState中进行耗时操作,避免在build方法中频繁重绘和重建。 -
build()方法是Flutter中widget构建的核心方法。当widget的状态改变或父widget重建时,build()方法可能会被频繁调用。 -
因此,应避免在
build()方法中执行任何耗时或复杂的计算。这些操作应该放在initState()、didUpdateWidget()等生命周期方法中,或者在单独的函数或类中处理,并通过状态管理传递给widget。
-
7、谨慎使用saveLayer()
在 Flutter 开发中,saveLayer() 是一个强大的功能,它允许你在绘制操作中创建一个离屏缓冲区(off-screen buffer),这个缓冲区可以在后续的绘制操作中被复用或修改。然而,saveLayer() 的使用需要谨慎,因为它可能会影响性能,特别是在频繁调用或在大面积绘制时使用。
7.1、谨慎使用 saveLayer() 的原因
- 性能开销
saveLayer()会创建一个新的图层,这需要在 GPU 上分配额外的内存和可能的渲染开销。- 在复杂或大面积的场景中,频繁使用
saveLayer()可能会导致掉帧或性能瓶颈。
- 内存使用
- 每个离屏缓冲区都会占用内存,如果创建过多的图层,可能会导致内存使用量剧增,进而影响应用的稳定性和响应速度。
7.2、触发使用 saveLayer() 的场景
- 动画和变换
- 当需要对某些元素应用复杂的动画或变换(如旋转、缩放、透明度变化等)时,可能需要使用
saveLayer()来确保这些变换独立于其他绘制操作。
- 当需要对某些元素应用复杂的动画或变换(如旋转、缩放、透明度变化等)时,可能需要使用
- 遮罩和裁剪
- 在需要应用特定形状的遮罩或裁剪区域时,
saveLayer()可以帮助创建一个独立的图层,以便在这些图层上应用遮罩或裁剪效果。
- 在需要应用特定形状的遮罩或裁剪区域时,
- 复杂绘制逻辑
- 在绘制逻辑非常复杂或需要分步骤完成时,使用
saveLayer()可以将绘制过程分解为多个独立的步骤,每个步骤在一个离屏缓冲区上进行,从而简化绘制逻辑。
- 在绘制逻辑非常复杂或需要分步骤完成时,使用
- 透明度
- 能不用 Opacity widget,就尽量不要用。
7.3、优化方案
- 减少使用频率
- 尽可能减少
saveLayer()的调用次数。如果可以通过其他方式(如使用组合变换、遮罩等)实现相同的效果,优先考虑这些方法。
- 尽可能减少
- 优化图层大小
- 精确控制
saveLayer()覆盖的区域大小。只覆盖必要的区域,避免创建过大的离屏缓冲区。
- 精确控制
- 合并图层
- 如果多个
saveLayer()调用覆盖的区域相同或重叠,考虑将它们合并为一个图层,以减少内存使用和渲染开销。
- 如果多个
- 使用
RepaintBoundary- 在某些情况下,可以使用
RepaintBoundary替代saveLayer()。RepaintBoundary会触发一个独立的渲染树节点,但它通常用于更复杂的场景,如捕获图像或处理滚动。
- 在某些情况下,可以使用
- 性能分析
- 使用 Flutter 的性能分析工具(如 DevTools)来监控
saveLayer()对性能的影响。通过分析渲染帧时间和内存使用情况,找到性能瓶颈并进行优化。
- 使用 Flutter 的性能分析工具(如 DevTools)来监控
- 硬件加速
- 确保设备支持硬件加速,并检查 Flutter 引擎的版本,因为新版本的引擎可能包含对
saveLayer()性能的优化。
- 确保设备支持硬件加速,并检查 Flutter 引擎的版本,因为新版本的引擎可能包含对
- 透明度
- 有关将透明度直接应用于图像的示例,请查看 Transparent image,这比使用 Opacity widget 更快。 与其将简单的形状或文本包裹在一个 Opacity widget 中,不如用半透明的颜色来绘制它们会更快。(这仅在要画的形状中没有重叠的部分时有效)。
- 动画
- 要在图像中实现淡入淡出,请考虑使用 FadeInImage widget,该 widget 使用 GPU 的片段着色器应用渐变不透明度。了解更多详情,请查看 Opacity 文档。
- 在动画中避免Opacity透明度使用。可以使用
AnimatedOpacity或FadeInImage代替该操作 - 避免在动画中裁剪,尽可能的在动画开始之前预先裁剪图像
- Clipping
- Clipping 不会调用 saveLayer() (除非明确使用 Clip.antiAliasWithSaveLayer),因此这些操作没有 Opacity 那么耗时,但仍然很耗时,所以请谨慎使用。
- 圆角
- 要创建带圆角的矩形,而不是裁剪矩形来达到圆角的效果,请考虑使用很多 widget 都提供的 borderRadius 属性。
四、包体积
检测应用体积是确保应用优化和用户体验的重要步骤
1、使用 Flutter 构建命令分析体积
Flutter 提供了一些构建命令,可以帮助开发者分析应用的体积。这些命令会生成包含应用体积详细信息的报告文件。
- 全平台包(胖包)大小
- 使用
flutter build apk或flutter build ios命令构建出发布包,该包可以大概评估出用户要下载的包大小。 - 对于 Android 平台,可以使用 Android Size Analyzer 工具来分析包大小,以及各个部分(如 lib、assets、res、dex 等)的大小和比例。
- 对于 iOS 平台,可以使用
flutter build ios --analyze-size命令来查看安装包大小,并生成包含详细信息的报告文件。
- 使用
- App Bundle 分析
- 对于 Android 平台,构建 App Bundle(使用
flutter build appbundle --release命令)可以减小应用体积,因为 App Bundle 会根据用户设备信息动态下发不同的资源包。 - 可以将 App Bundle 上传到 Google Play Console,在应用大小部分可以看到用户最终下载包的大小。
- 对于 Android 平台,构建 App Bundle(使用
2、使用应用体积工具
Flutter 官方提供了应用体积工具,用于分析 Flutter 应用的体积信息。
- 生成体积分析文件
- 使用
flutter build <your target platform> --analyze-size命令构建应用,并生成体积分析文件。 - 该文件包含整个应用的体积信息(本机代码、Dart 代码、资源和字体等)。
- 使用
- 分析体积信息
- 打开应用体积工具,导入生成的体积分析文件。
- 使用 Analysis 标签查看体积信息的单个快照,或使用 Diff 标签比较两个不同快照的体积信息。
- 在 Analysis 标签中,可以查看层次结构的树状图和表格,了解应用体积的构成和各个部分的大小。
3、优化应用体积的方法
在检测应用体积后,开发者可以采取一些方法来优化应用体积,提高用户体验。
- 删除无用资源和代码
- 检查项目中的资源和代码,删除未使用或不必要的部分。
- 使用工具如 dart_code_metrics 或 Flutter 内置的
flutter analyze命令来查找未使用的代码。
- 压缩和混淆代码
- 在构建过程中启用代码压缩和混淆,减小代码体积并提高安全性。
- 在
android/app/build.gradle文件中配置minifyEnabled和shrinkResources为 true,并配置 ProGuard 规则文件。
- 优化图像资源
- 将 PNG 或 JPEG 图片转换为 WebP 格式,使用工具如 Squoosh 进行压缩。
- 使用在线工具或脚本优化图片大小,例如 tinypng 或
flutter_image_compress。
- 使用动态下发和按需加载
- 使用 Flutter 的动态下发功能,根据用户需求动态加载资源。
- 使用
flutter_deferred_components包,按需加载特定模块,而非一次性加载整个应用。
- 优化 Dart 包依赖
- 检查
pubspec.yaml文件中的依赖,删除未使用的第三方库。 - 使用
flutter pub deps --style=compact命令分析依赖包大小,并优化依赖关系。
- 检查
五、懒加载
延迟加载组件(也称为懒加载或按需加载)是一种优化应用性能和资源使用的有效方法。通过延迟加载,应用可以在需要时才加载特定的组件或资源,从而减少初始加载时间和内存占用
1、配置项目以支持延迟加载
-
Android平台:
- 在
android/app/build.gradle文件中添加Play Core依赖:
dependencies { implementation 'com.google.android.play:core:版本号' // 替换为实际版本号 }- 配置
AndroidManifest.xml文件,将application标签的android:name属性设置为io.flutter.embedding.android.FlutterPlayStoreSplitApplication(如果已使用FlutterPlayStoreSplitApplication,则无需更改)。
- 在
-
iOS平台:
- iOS平台通常不需要额外的配置来支持延迟加载,因为Flutter的iOS实现已经内置了相关的支持。
-
Web平台:
- Web平台需要将延迟组件创建为单独的
.js文件,并在需要时动态加载。
- Web平台需要将延迟组件创建为单独的
2、在pubspec.yaml文件中声明延迟组件
在pubspec.yaml文件的flutter部分下添加deferred-components声明,用于指定哪些组件是延迟加载的。例如:
flutter:
# ... 其他配置 ...
deferred-components:
my_deferred_component:
path: lib/my_deferred_component # 延迟组件的路径
3、在Dart代码中实现延迟加载
-
创建延迟组件:
- 在指定的路径下创建延迟组件的Dart文件。例如,在
lib/my_deferred_component目录下创建一个名为my_deferred_component.dart的文件。
- 在指定的路径下创建延迟组件的Dart文件。例如,在
-
在需要时加载延迟组件:
- 使用
deferred as关键字导入延迟组件,并在需要时调用loadLibrary()函数来加载它。例如:
import 'package:flutter/material.dart'; // 延迟加载组件的导入 deferred import 'my_deferred_component.dart' as my_deferred_component; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: Text('Delayed Loading Demo'), ), body: Center( child: ElevatedButton( onPressed: () async { // 加载延迟组件 await my_deferred_component.loadLibrary(); // 创建并使用延迟组件的Widget Navigator.push( context, MaterialPageRoute(builder: (context) => my_deferred_component.MyDeferredComponentWidget()), ); }, child: Text('Load Deferred Component'), ), ), ), ); } } - 使用
4、构建和测试延迟加载组件
- 构建应用:
- 使用
flutter build appbundle(针对Android)或flutter build ios(针对iOS)命令构建应用。对于Web平台,使用flutter build web命令。
- 使用
- 测试延迟加载:
- 在设备上运行应用,并测试延迟加载组件的功能。确保在点击按钮后,延迟组件能够正确加载并显示。
5、注意事项
- 延迟加载组件的限制:
- 延迟加载组件不能访问非延迟加载组件中的全局变量或函数。
- 延迟加载组件的初始化代码(如构造函数或
initState方法)在加载时才会执行。
- 性能考虑:
- 延迟加载可以减少初始加载时间和内存占用,但可能会增加首次使用延迟组件时的加载时间。因此,应根据实际需求权衡利弊。
- 调试和测试:
- 在Debug模式下,所有延迟组件都被视为常规导入,并在启动时立即加载。因此,应在Release或Profile模式下测试延迟加载功能。
六、性能视图
Flutter性能视图(Performance view)是Flutter DevTools的一部分,它对于开发者来说是一个非常重要的工具,因为它可以帮助开发者分析和优化Flutter应用的性能。以下是对Flutter性能视图的详细解释:
1、性能视图的作用
性能视图可以记录并分析Dart应用程序的性能,帮助开发者找到应用程序的性能瓶颈。它主要关注于UI的流畅性,即确保应用在每16毫秒(对于60帧每秒的帧率)内能够渲染一帧,从而避免卡顿现象。此外,性能视图还可以分析Dart代码中的性能问题,以及I/O或网速等其他性能指标(尽管本文主要聚焦于UI流畅性)。
2、使用性能视图的前提
- 设备要求:几乎所有的Flutter应用性能调试都应该在真实的Android或者iOS设备上以分析模式进行。因为调试模式或模拟器上的性能指标与发布模式的表现并不相同。
- 模式要求:为了使用性能视图,Flutter应用需要以profile构建模式运行。这是因为在分析模式下,Flutter应用会提供追踪信息给分析工具,而不会像调试模式那样增加额外的检查(如断言),这些检查可能相当耗费资源。同时,分析模式与发布模式的编译和运行基本相同,除了一些调试性能问题所必须的额外方法。
3、如何打开和使用性能视图
-
打开DevTools:首先,需要在VS Code、Android Studio或IntelliJ等IDE中打开Flutter DevTools。
-
运行应用:确保应用在以profile模式运行。在VS Code中,可以通过修改launch.json文件设置flutterMode属性为profile来运行应用。在Android Studio和IntelliJ中,则可以通过Run菜单选择Flutter Run main.dart in Profile Mode选项。在命令行中,可以使用flutter run --profile参数来运行应用。
-
显示性能图层:一旦应用运行在分析模式下,就可以打开性能图层来分析应用的性能。性能图层可以通过Flutter Inspector或直接在DevTools中打开。
-
分析图表:它用两张图表显示应用的耗时信息,一张显示raster线程的性能情况(在上方),另一张显示UI线程的性能情况(在下方)。
-
图表中的绿色条代表当前帧,而白线则代表16毫秒的增量。如果白线在图表中都没有被超过,说明应用的运行帧率低于60Hz。
-
红图表中色竖条则表示当前帧的渲染和绘制都很耗时。如果红色竖条出现在UI图表中,则表明Dart代码消耗了大量资源;如果红色竖条出现在GPU图表中,则意味着场景太复杂导致无法快速渲染,就要开始对 UI 线程 (Dart VM) 进行诊断了
4、性能视图中的其他功能
- CPU分析器:CPU分析器可以记录并分析CPU的使用情况。它可以显示调用树(Call Tree)、自下而上的视图(Bottom Up)和火焰图(Flame Chart)。通过这些视图,开发者可以分析哪些方法调用了哪些方法,以及每个方法消耗了多少CPU时间。
- 火焰图:火焰图主要用于显示一段持续时间内CPU的样本信息。图表展示的是自上而下的调用堆栈信息,每个堆栈帧的宽度代表CPU执行的时长。通过火焰图,开发者可以快速定位到消耗CPU时间最多的方法。
5、web 性能
在进行Flutter web应用的性能分析时,确实需要Flutter版本3.14或更高版本。这是因为从该版本开始,Flutter框架提供了更强大的性能分析工具和功能。
Flutter Web性能分析概述
Flutter框架在运行时会发出时间线事件,这些事件涵盖了帧的构建、场景的绘制以及垃圾回收等其他活动。这些事件对于调试和优化应用性能至关重要。在Chrome浏览器中,你可以使用Chrome DevTools性能面板来查看这些事件。通过该面板,你可以直观地看到应用的性能瓶颈,进而采取相应的优化措施。
优化Web加载速度
关于如何优化Flutter web应用的加载速度,你可以参考Medium上的免费文章《优化Flutter Web加载速度的最佳实践》。这篇文章提供了许多实用的建议,帮助你提升应用的加载速度和整体性能。
自定义时间线事件
除了Flutter框架自动生成的时间线事件外,你还可以使用dart:developer包中的Timeline和TimelineTask API来自定义时间线事件。这对于深入分析应用的特定部分或功能非常有用。通过自定义事件,你可以更精确地了解应用在不同阶段的行为和性能表现。
七、线程
1、Flutter中的线程类型
- UI线程(Dart VM线程)
- 位置:性能图层的最低栏展示。
- 职责:在Dart虚拟机(VM)中执行Dart代码。这包括开发者编写的代码和Flutter框架根据应用行为自动生成的代码。
- 关键点:UI线程负责创建和更新图层树(layer tree),这是一个包含设备无关的渲染命令的轻量级对象结构。然后,它将图层树发送到GPU线程进行渲染。
- 注意事项:不要阻塞这个线程!阻塞UI线程会导致应用界面卡顿或无响应。
- Raster线程(GPU准备线程)
- 位置:性能图层的最顶栏显示。
- 职责:Raster线程接收来自UI线程的图层树,并使用Skia图形库在CPU上进行栅格化处理,准备渲染数据供GPU使用。
- 关键点:尽管Raster线程本身在CPU上运行,但它为GPU渲染做准备。如果Raster线程变慢,通常是由于Dart代码中的某些操作导致的,比如复杂的布局计算或过多的重绘。
- 注意事项:开发者无法直接与Raster线程或其数据通信,但可以通过优化Dart代码来减少对其的影响。
- GPU线程
- 位置:不在性能图层的直接展示中,但Raster线程的工作最终是为GPU线程服务的。
- 职责:GPU线程负责实际的渲染工作,将Raster线程准备好的渲染数据渲染到屏幕上。
- I/O线程
- 位置:性能图层上不显示。
- 职责:执行耗时的I/O操作,如文件读写、网络请求等,以避免阻塞UI线程或Raster线程。
- 关键点:I/O线程的存在是为了提高应用的响应性和性能,通过异步处理耗时的I/O操作。
- 平台线程(Native线程)
- 描述:每个Flutter应用在原生平台(如Android或iOS)上都有一个对应的线程,称为平台线程。这个线程主要负责与原生代码进行通信,处理原生插件、服务端通知和系统事件等。
- 作用:平台线程是Flutter与原生平台之间交互的桥梁,它使得Flutter应用能够调用原生平台的功能和服务。
- Isolates(隔离区)
- 描述:Isolates是Dart语言中的一种轻量级线程,它们在内存中相互隔离,通过消息传递进行通信。在Flutter中,可以使用Isolates来执行后台任务,如计算密集型任务或长时间运行的任务。
- 用途:通过将任务分发到不同的Isolates,可以避免阻塞UI线程,从而保持应用的响应性。
2、线程管理注意事项
- 避免在UI线程上执行耗时操作:耗时操作(如网络请求、文件读写等)应该使用异步操作或Isolates来处理,以避免阻塞UI线程。
- 优化布局和渲染:减少不必要的重建和复杂的布局计算,以降低UI线程上的负担。
- 使用Flutter性能工具:利用Flutter开发者工具中的性能分析器来检测和解决性能问题。性能分析器可以帮助开发者找到导致UI线程堵塞的原因,并提供优化建议。
- 合理利用Isolates:对于计算密集型任务或长时间运行的任务,可以使用Isolates来执行,以保持UI线程的响应性。
- 注意线程安全:在多线程环境中编程时,需要注意线程安全问题,避免数据竞争和不一致性问题。
3、Flutter inspector工具
Flutter 插件提供的 Flutter inspector,只需单击 Performance Overlay 按钮,即可在正在运行的应用程序上切换图层
使用 P 参数触发性能图层
八、并发与隔离
在Flutter中,并发操作主要通过Dart语言的Isolate机制来实现
1、Isolates (隔离)基本概念
- Isolates是Dart语言中用于实现并发执行的一种机制。
- 每个Isolate都有自己独立的内存空间和事件循环,相互之间不会共享内存。
- Isolates之间通过消息传递进行通信,确保了并发执行的安全性和独立性。
2、使用Isolates的原因
- UI线程保护
- Flutter使用单线程模型来渲染UI,为了保证UI的流畅性,耗时的操作需要在后台线程中执行。
- Isolates提供了在后台执行代码的能力,从而避免了阻塞UI线程。
- 资源利用
- 利用多核CPU资源,通过并行处理来提高应用程序的性能。
3、使用Isolates的方式
- 创建Isolate
- 使用
compute函数可以简便地创建并执行一个Isolate中的函数。 compute函数接受一个函数和一个参数列表,返回一个Future,该Future将在Isolate执行完成后完成。
- 使用
- Isolate间通信
- 通过发送和接收消息来实现Isolate之间的通信。
- 发送消息使用
sendPort.send(message),接收消息则通过监听ReceivePort来实现。
4、Isolate的生命周期管理
- Isolate的创建与销毁
- Isolate在创建时分配独立的内存和资源,在完成任务或被显式销毁时释放这些资源。
- Flutter框架不直接管理Isolate的生命周期,开发者需要负责适时地创建和销毁Isolate。
- 错误处理
- Isolate中的错误不会传播到创建它的Isolate中,而是需要通过消息传递机制进行错误报告和处理。
5、Flutter中Isolate的性能考虑
- 内存使用
- 每个Isolate都有独立的内存空间,因此创建过多的Isolate会增加内存使用。
- 需要合理控制Isolate的数量和生命周期以优化内存使用。
- CPU使用
- Isolate可以充分利用多核CPU资源,但过多的Isolate可能会导致上下文切换开销增加。
- 需要根据实际应用场景和性能需求来合理配置Isolate的数量和工作负载。
6、特定术语解释
- Dart Isolates
- Dart Isolates是Dart语言中用于实现并发执行的一种轻量级线程。
- 它们相互独立,有自己的内存空间和事件循环,通过消息传递进行通信。
- UI线程
- UI线程是负责渲染用户界面和响应用户输入的线程。
- 在Flutter中,UI线程是单线程的,耗时的操作需要在后台线程(如Isolates)中执行以避免阻塞UI。
7、Isolates的常见用例
Isolates在Flutter中主要用于执行需要并发处理的任务,以避免阻塞主线程(UI线程)。常见的用例包括:
- 执行耗时的计算任务,如复杂的数学运算或数据处理。
- 进行网络请求,以异步方式获取数据而不阻塞UI。
- 处理I/O操作,如文件读写或数据库访问。
每个隔离都有自己的内存和自己的事件循环。事件循环按照事件添加到事件队列的顺序处理事件。在主隔离上,这些事件可以是任何事情,从处理用户在UI中的点击,到执行一个函数,再到在屏幕上绘制一个框架。
下图显示了一个示例事件队列,其中有3个事件等待处理:
当你应该使用隔离时,只有一个硬性规则,那就是当大型计算导致你的Flutter应用程序出现UI阻塞时。当有任何计算需要比Flutter的帧间隙更长的时间时,就会出现这种情况。
如下图 Tap handler 操作时间过长导致阻塞:
8、Isolates之间的消息传递
Isolates之间通过消息传递进行通信。每个Isolate都有自己的内存空间和事件循环,它们之间不能直接共享内存。因此,需要使用SendPort和ReceivePort来发送和接收消息。发送方通过SendPort发送消息,接收方通过ReceivePort接收消息。
9、短生命周期的Isolates
短生命周期的Isolates通常用于执行一次性任务,如计算某个值或处理一次网络请求。这些Isolates在完成任务后会被销毁,以释放资源。使用短生命周期的Isolates可以提高应用程序的性能和响应性。
在Flutter中将进程移动到隔离的最简单方法是使用isolate .run方法。此方法生成一个隔离,向生成的隔离传递一个回调以启动某些计算,从计算返回一个值,然后在计算完成时关闭隔离。这一切都与主隔离并发发生,并且不会阻塞它:
10、有状态、长生命周期的Isolates
与短生命周期的Isolates相比,有状态、长生命周期的Isolates通常用于执行需要持续运行的任务,如后台服务或定时任务。这些Isolates在创建后会一直运行,直到被显式销毁。它们可以维护自己的状态,并在需要时与其他Isolates进行通信。
11、ReceivePorts和SendPorts
ReceivePort和SendPort是Dart中用于Isolate间通信的两个关键类。ReceivePort用于接收消息,而SendPort用于发送消息。当创建一个新的Isolate时,会返回一个SendPort对象,该对象可以用于向新Isolate发送消息。同时,新Isolate也会创建一个ReceivePort对象来接收消息。
12、在Isolates中使用平台插件
在Flutter中,平台插件允许应用程序与原生平台(如iOS和Android)进行交互。然而,在Isolates中使用平台插件是有限制的。由于Isolates是独立的执行环境,它们没有直接访问原生平台的能力。因此,通常需要在主Isolate中初始化平台插件,并通过消息传递机制将插件的功能暴露给其他Isolates。
13、Isolates的限制
尽管Isolates提供了强大的并发处理能力,但它们也有一些限制:
- Isolates之间不能直接共享内存,这限制了它们之间的数据交换方式。
- 在某些平台上,如Web平台,Isolate的创建和销毁可能会有额外的性能开销。
- 由于Isolates是独立的执行环境,它们无法直接访问主Isolate中的某些资源,如
rootBundle或dart:ui方法。
14、Web平台和计算
在Web平台上,Flutter使用Web Workers来实现Isolates的功能。Web Workers允许在后台线程中执行脚本,从而不会阻塞主线程。然而,由于Web Workers的限制,一些在Dart VM中可用的Isolate特性在Web平台上可能不可用。
15、无法访问rootBundle或dart:ui方法
在Isolates中,无法直接访问rootBundle或dart:ui方法。rootBundle通常用于加载应用程序的资源文件,而dart:ui提供了与UI相关的功能。由于Isolates是独立的执行环境,它们没有访问这些资源的权限。因此,需要在主Isolate中加载资源或执行UI相关操作,并通过消息传递机制将结果传递给其他Isolates。
16、从主机平台到Flutter的插件消息有限制
在Flutter中,从主机平台(如iOS或Android)到Flutter应用程序的插件消息传递是有限制的。这通常是由于安全原因和平台限制。为了确保应用程序的稳定性和安全性,Flutter对插件消息传递进行了严格的控制。因此,在开发过程中需要注意这些限制,并遵循最佳实践来确保消息传递的可靠性和安全性。