Flutter3.0 性能优化系列|一文教你完全掌握内存优化和dark虚拟机 (内存泄露)
Flutter内存优化是一个非常复杂的问题,其中涉及多个方面的优化策略。下面将从以下几个方面对Flutter的内存优化进行具体实现的总结。内存泄露ListView优化图片优化,图片外接纹理优化页面不可见,内存释放优化!
Flutter3.0 性能优化系列 | 一文教你完全掌握内存优化和Dart虚拟机
1. 内存和虚拟机的概念
Flutter从使用的语言上,可以分成3大部分:
- Framework层 由Dart编写,开发者接触到顶层,用于应用层开发
- Engine 层,由C/C++编写,主要进行图形渲染
- Embedder层,由植入层语言编写,如iOS使用Objective-C/swift,Android使用java
当我们从进程角度谈论Flutter应用的内存时,指的是这三者所有的内存的总和。
Dart虚拟机的理解
在Flutter中,内存管理是由Dart虚拟机负责的。Dart虚拟机使用垃圾回收器来管理内存,这意味着开发者不需要手动分配和释放内存。垃圾回收器会自动识别不再使用的对象并将其回收,从而释放内存。
Flutter应用程序中的内存分为两种类型:堆内存和栈内存。堆内存用于存储对象,栈内存用于存储临时变量。在Flutter中,大多数对象都是在堆内存中创建的,而临时变量则是在栈内存中创建的。当一个对象不再被引用时,垃圾回收器会自动回收它所占用的堆内存。
Dart虚拟机
Dart虚拟机是一个支持JIT编译和AOT编译的强类型脚本语言虚拟机,它可以运行在多种平台上,包括PC、移动设备和嵌入式设备上。Dart虚拟机内置了大量的标准库和工具,包括Dart SDK、Dart Analyzer、Dartfmt等,方便开发者进行开发和维护。
Dart虚拟机的设计架构具有良好的可扩展性和可定制性,支持诸如Dart的Native扩展等机制,可以在运行时加载和卸载模块进行动态扩展。Dart虚拟机还支持一些高级语言特性,如异步编程和生成器,使得开发者可以编写更加高效的、带有处理异步逻辑的程序。同时,Dart虚拟机还支持代码优化和预编译等特性,以提高程序的性能和运行效率。
重点:
Dart虚拟机在初始化时,会将C++声明的某个类或者函数和Dart中的某个类或者绑定起来,依次注入Dart运行时的全局遍历中,当Dart代码执行某一个函数时,便是指向具体的C++对象或者函数。
2. 内存监测方法
2.1 Flutter编译模式
Flutter支持Release、Profile、Debug编译3种模式:
- Release模式:使用AOT预编译模式,预编译为机器码,通过编译生成对应架构的代码,在用户设备上直接运行对应的机器码,运行速度快,执行性能好;此模式关闭了所有调试工具,只支持真机。
- Profile模式:和Release模式类似,使用AOT预编译模式,此模式最重要的作用是可以用DevTools来检测应用的性能,做性能调试分析。
- Debug模式:使用JIT(Just in time)即时编译技术,支持常用的开发调试功能hot reload,在开发调试时使用,包括支持的调试信息、服务扩展、Observatory、DevTools等调试工具,支持模拟器和真机。
2.2 开启Profile模式
按照Flutter官方的性能优化指南,Flutter-profile模式下,使用DevTools查看Flutter内存占用。
检测消耗多余内存的图片
Flutter Inspector:点击"Highlight Oversized Images",它会识别出那些解码大小超过展示大小的图片,并且系统会将其倒置,这些你就能更容易在App页面中找到它。
3. 优化方向
3.1 内存泄露原理
从渲染原理出发探究Flutter内存泄漏
案例1:
例如,存在两个A,B界面,A界面通过Navigator.push的方式添加B界面,B界面通过Navigator.pop回退到A。如果B界面因为某些写法的缘故导致B的渲染树虽然被从主渲染树解开后依然无法被释放,这会导致整个原来B的子树都无法释放。
3.1.2 内存泄工具设计原理
Flutter内存泄漏检测工具的设计思路是:借鉴Android LeakCanary,用WeakPersitentHandle对象,通过检测渲染树节点来检测内存泄漏。
对比界面进入前后的对象,寻找出未被释放的对象,进而查看未释放的引用关系(Retaining path或Inbound references),再结合源码进行分析,最后找到错误代码。
内存泄露检测原理总结:
- dart代码都会映射c++中对应的方法
- 对象存放在WeakPersitentHandle中
- 根据渲染原理:通过检测渲染树节点数量来检测内存泄漏,对比当前帧使用的EngineLayer个数,如果内存中的EngineLayer个数长时间大于使用的个数
- 通过WeakPersitentHandle找到调用链
3.2 内存泄漏的常见场景
案例:
目前发现异步执行的代码的场景(Feature, async/await,methodChan)长期持有传入的BuildContext,导致element被移除后,依然长期存在,最终导致以及关联的widget,state发生泄漏。
3.2.1. 监听反注册缺失
排查内存泄漏的过程中,我们发现图片内存大幅度超出了图片缓存自身size限制的增长,并且不会被GC回收,经过排查发现我们封装的一个底层图片处理类,注册了图片事件流监听后,并没有在适当的时机做反注册处理。
3.2.2. 长列表直接构建列表项
通过对列表数据遍历的方式,一次生成所有数据对应的widget列表,直接塞进Column里展示给用户,当加载了几页数据之后,数据量稍大就会轻易导致OOM或导致严重卡顿。
3.2.3. 延时、持续执行的闭包引用
Flutter提供的延时和持续执行的对象有Animation、Timer、Future等,在结束执行之前,回调函数引用到的相关对象都会被强引用保留在内存中。
3.3 内存泄漏的操作步骤和实战
通过Observatory分析内存泄漏
Android Studio或VS Code插件帮我们包装了相关内存工具,这些工具都基于debug模式下Dart VM service暴露的接口开发的,Dart VM service自身也带有协助排查内存问题的工具 - Dart VM Observatory。
工具有了,如何排查定位导致内存泄漏的问题代码呢?
对同一个功能或页面进行反复相同的进入、退出操作;
然后执行强制GC,查看不同操作后的内存快照;
对比该功能关联的对象实例增加情况;
如果强制GC后实例只增不减或该回收的对象没有被回收,没有特殊的延时处理一般就可以判断相关代码有问题。
3.3 图片和ListView优化
重绘情况,酌情考虑优化方案(控制setState粒度、引入全局状态管理-redux/Provider、拆分ViewModel、减少build()操作等)。
针对这些图片,你可以指定cacheWidth和cacheHeight为展示大小,这样可以让Flutter引擎以指定大小解析图片,减少内存消耗。
针对ListView item中有image的情况来优化内存
ListView不会销毁那些在屏幕可视范围之外的那些item,如果item使用了高分辨率的图片,那么它将会消耗非常多的内存。
3.4 多页面内存优化
这个优化策略是真真被逼出来的。在对线上数据分析以后,我们发现Flutter页面栈有一个非常有意思的特点:多页面栈情况下,底层的页面不会被释放。即便是在内存非常紧张的情况下,也不会执行回收。这样就会导致一个问题:随着页面的增多,内存消耗会线性增长。这里占比最高的就是图片资源的占比了。
我们发现Flutter的layer层可以稳定感知到页面栈的变化。
4. 写代码时候,内存总结
Flutter内存优化是一个非常复杂的问题,其中涉及多个方面的优化策略。下面将从以下几个方面对Flutter的内存优化进行具体实现的总结。
一、减少Widget的创建和销毁
Widget的创建和销毁是Flutter中内存占用最大和最频繁的操作之一,在开发过程中,应该尽量减少Widget的创建和销毁。
1.1 重用现有的Widget
在同一个页面内,如果多个Widget具有相同的样式和行为,则可以共用同一个widget,而不是每个Widget都创建自己的。
1.2 使用Key避免重复构建
Flutter中相同类型的Widget,如果没有设置Key属性,则每次更新时都会创建新的Widget,即使两个Widget的props完全一致。相反,如果为Widget设置Key属性,则当Widget更新时,会尝试在它们之间保持一个一致的关系,从而减少重新构建它们所需要的树的大小。
1.3 避免状态管理器泄露
在Flutter中,每个控件对应一个状态,如果状态管理不当,可能会导致内存泄漏。因此,在组件销毁时必须释放所占用的内存。在实现中可以使用StatefulWidget和StatelessWidget等控件,这些控件会自动处理状态管理的问题。
二、优化图片资源
在Flutter中,图片资源可能会占用大量的内存,如果没有得到合适的管理和优化,很容易导致内存溢出的问题。
2.1 去掉不必要的图片
去掉那些不必要的图片,或者压缩那些需要的图片,以减少内存占用。
2.2 避免图片重复加载
重复加载的图片会占用更多的内存,因此应该尽量避免这种情况的发生。在Flutter中可以使用MemoryImage对图片进行缓存,提高图片的复用率。
三、避免资源泄漏
资源泄漏是内存占用过多的常见问题之一,在Flutter中也是如此。应该在开发过程中注意避免资源泄漏。
3.1 及时释放内存
当不再使用某个已分配的内存时,应该及时释放它,避免内存泄漏等问题。
3.2 避免"闭包"泄漏
在Flutter中,如果一个Widget包含了另一个Widget,则可能会发生"闭包",即某个Widget的生命周期不正确地延长。为了避免这种情况的发生,可以使用StatelessWidget等控件,不包含状态变量,避免"闭包"泄漏。
四、合理管理内存
在Flutter中,应用程序使用的内存受到操作系统、硬件性能等多个因素的影响。为了合理地管理内存,需要进行特定的优化,以确保应用程序不会消耗过多的内存。
4.1 使用Navigator管理页面历史
在Flutter中,能够使用Navigator管理页面历史,并对页面进行统一的管理。使用Navigator可以对页面进行释放和回收,避免页面占用过多内存。
4.2 使用异步方式加载数据
采用异步方式优化数据加载,可以避免在程序运行时出现阻塞,使得应用程序可响应。异步方式可以优化数据请求、数据转换等操作,减少应用程序的响应时间。
内存性能优化总结:
- 内存泄露
- ListView优化
- 图片优化,图片外接纹理优化
- 页面不可见,内存释放优化!
5. 开发工具推荐
在进行Flutter应用开发时,合理使用工具可以极大提升开发效率。对于iOS开发者来说,AppUploader是一款不错的辅助工具,它可以帮助开发者快速完成应用打包、上传到App Store等流程,特别适合需要频繁打包测试的团队使用。
AppUploader的主要特点包括:
- 简化iOS应用打包流程
- 支持批量处理多个应用
- 提供直观的界面操作
- 自动处理证书和描述文件
对于Flutter开发者来说,结合AppUploader可以更高效地管理iOS端的发布流程,特别是在进行性能优化后的版本验证时,能够快速打包测试。
6. 学习资源推荐
为了帮助大家更好地掌握Flutter开发,这里推荐一些学习资源:
《Flutter Dart 语言编程入门到精通》
第一章 Dart语言基础
- 环境准备
- 基础语法
第二章 Dart 异步编程
- Dart的事件循环
- 调度任务
- 延时任务
- Future详解
- async和await
- Isolate
第三章 异步之 Stream 详解
- 什么是Stream
- 单订阅流
- 广播流
- Stream Transformer
- 总结
第四章 Dart标准输入输出流
- 文件操作
第五章 Dart 网络编程
- TCP服务端
- TCP客户端
- UDP服务端
- UDP客户端
- HTTP服务器与请求
- WebSocket
《Flutter实战:第二版》
第一章:起步
- 1.1 移动开发技术简介
- 1.2 初始Flutter
- 1.3 搭建Flutter开发环境
- 1.4 Dart语言简介
第二章:第一个Flutter应用
- 2.1 计数器应用实例
- 2.2 Widget简介
- 2.3 状态管理
- 2.4路由管理
- 2.5包管理
- 2.6 资源管理
- 2.7 调试Flutter应用
- 2.8 Flutter异常捕获
第三章:基础组件
- 3.1 文本及样式
- 3.2 按钮
- 3.3 图片及ICON
- 3.4 单选开关和复选框
- 3.5 输入框及表单
- 3.6 进度指示器
通过系统学习这些内容,开发者可以全面掌握Flutter开发技能,并能够有效地进行应用性能优化。
更多推荐


所有评论(0)