定位Flutter内存问题很难么?
liuian 2025-05-21 14:58 40 浏览
内存水位升高导致的稳定性问题严重影响app用户体验,所以开发者们非常关注Flutter的内存表现。随着Flutter业务越来越多,闲鱼也面临着oom导致的crash率提升的问题,下面我们结合项目中实际遇到的内存问题和解决思路跟大家分享下flutter内存优化的经验。
本文分为三个部分:
了解Dart VM内存分配及销毁原理
通过Observatory工具分析内存泄漏,减少不必要的内存占用
Flutter中常见内存泄漏场景有哪些,如何在业务应用中避免踩坑
Dart VM内存分配及销毁原理
DartVM的垃圾回收机制分两个阶段,新生代(New Generation)和老年代(Old Generation)。
新生代用来存储生命周期较短的对象,由两个内存空间组成,Active内存空间用来分配新对象,inActive内存空间用来作为备用空间,DartVM的内存分配策略非常简单,创建对象时只需要在现有堆上移动指针,内存增长始终是线形的,省去了查找可用内存段的过程。每个Isolate有自己独立的Heap,相互之间无法共享内存,这样可以实现无锁的快速分配。
一旦Active的内存空间被填满,垃圾回收器会从根对象开始遍历检查检查所有对象的引用状态,没有被引用的对象标记为dead状态,非dead状态的对象在下次内存回收事件中会被复制到inActive内存空间,清除Active内存空间,最后Active和inActive内存空间状态调换。
当对象达到一定的生命周期,会被移到老年代内存空间管理,这种垃圾收集策略有两个阶段
首先遍历对象图,并标记仍在使用的对象。
扫描整个内存,并回收任何未标记的对象,然后清除所有标志。
这种内存清理的频率较低,并且在扫描回收阶段需要暂停Dart runtime,回收成本较高,比较适合Flutter中大量StatelessWidget的模式(大部分都存放在新生代)。
另外,当engine检测到应用是idle状态并且没有用户交互的时候会发送通知垃圾收集器开始清理内存,最小化对性能的影响。这些策略让Dart的内存分配和回收都非常高效。
Android和IOS中都存在强引用弱引用的概念,区别在于一个对象具有强/弱引用,系统会不会释放该对象占用的内存空间,Dart并没有弱引用的概念,但是有个特例Expando ,它会以弱引用的方式持有 key,相当于一个弱应用的map,感兴趣的可以了解下。
Dart VM借鉴了很多JVM的思路,Dart中产生内存泄露的方式也和Java类似,Java中很多排查内存泄露的思路和防止内存泄露的方法应该也可以借鉴过来。Android可以通过Profile和LeakMemory等工具检测app中的内存泄漏,Flutter如何检测呢?可以使用Observatory或者DevTools。
通过Observatory分析内存泄漏
Observatory是官方提供的调试工具,通过dart vm获取运行时信息,通过它我们可以分析一系列性能相关数据,例如app耗时统计,代码覆盖率等,这里我们重点介绍内存相关的调试工具。(DevTools也可以用来调试分析性能数据,它是在Observatory层做了一层封装,但是目前还是beta版本)。
下面我们用闲鱼中的实际例子介绍下如何使用Observatory检查看Dart VM内存使用情况,注意所有关于性能的分析要在Profile模式下进行。
打开Observatory URL的Web页面。运行app,在控制台中查找类似输出日志
listening on
, 表示当前连接的VM地址,输入到浏览器就可以看到Observatory主界面,显示了dart vm一些基础信息,具体使用方法可以参考 官方文档,这里不再详细描述,我们重点关注右下角的allocation profile选项。
ws://127.0.0.1:64673/hXsWR_ZOsGk=/ws
点击右下角allocation profile选项后,操作app进入想要分析的Flutter页操作,退出该页面,点击页面右上角 GC按钮触发手动GC,查看Class,发现有部分DX Class内存占用,这类class本应该只有在目标分析页会出现,退出目标分析也后手动GC会被完全释放,但是这里任然能看到相关内存占用,说明产生了内存泄漏。
点击对应class查看具体应用实例,点击对应实例进入查看引用路径,就能找到没有导致释放的引用变量,结合业务代码具体分析,就能发现泄漏的源头。
这里有一点需要注意,Observatory显示的Dart VM占用的内存信息要远远小于Android Profile/Xcode检测出的内存大小,因为存在部分只有系统工具能检测出的内存区块,例如一些完全不依赖于DartVM的skia对象,并且layer在engine中创建时并不能明确知道大小,所以采用虚拟近似值代替。
//engine/lib/ui/painting/engine_layer.cc
...
size_tEngineLayer::GetAllocationSize {
// Provide an approximation of the total memory impact of this object to the
// Dart GC. The ContainerLayer may hold references to a tree of other layers,
// which in turn may contain Skia objects.
return3000;
};
下面我们总结了几种常见内存泄漏的场景,在Java中都可以一一对应找到类似的场景,大家在业务开发中注意避免。
常见内存问题
未取消注册或回调导致内存泄露
示例代码:
classDownloadManagerextendsObject{
......
abstractclassDownloadListener{
void completed(DXTemplateItem item);
void failed(DXTemplateItem item, String errorInfo);
}
staticList<DownloadListener> listenerList = List;
staticvoid downloadSingleTemplate(DXTemplateItemtemplate, DownloadListener listener) async{
listenerList.add(listener);
...
}
...
修改方法:手动取消注册或回调
// 移除
staticvoid removeDownloadListener(DownloadListener listener) {
if(listener != && listenerList != && listenerList.contains(listener)) {
listenerList.remove(listener);
}
}
资源未关闭或释放导致内存泄露,例如ImageStream的图片资源有没有被正常关闭导致的内存泄漏。
问题代码:
void _resolveImage {
finalImageStream newStream =
widget.image.resolve(createLocalImageConfiguration(context));
assert(newStream != );
_updateSourceStream(newStream);
}
修改方法:在图片组件被销毁时正确释放资源
@override
void dispose {
...
_imageInfo.image.dispose;
_imageInfo = ;
super.dispose;
}
PlatformAssetBundle.loadString通过asset读取String内容会一直缓存读取的内容,造成内存无法释放
问题代码:
/// 通过asset读取Json
Future<Map<String, dynamic>> loadJsonAsset(String assetPath) async{
_rootBundle ??= PlatformAssetBundle;
finalString jsonStr = await _rootBundle.loadString(assetPath);
return json.decode(jsonStr);
}
修改方法:
/// 通过asset读取Json
Future<Map<String, dynamic>> loadJsonAsset(String assetPath) async{
_rootBundle ??= PlatformAssetBundle;
finalString jsonStr = await _rootBundle.loadString(assetPath, cache: false);
return json.decode(jsonStr);
}
PlatformAssetBundle继承于CachingAssetBundle,会在app整个生命周期中缓存读取的数据,如果不是需要频繁访问的话建议cache参数设置为false
另外很多同学有反馈过Flutter带图片的长列表滑动会造成内存一直上涨,flutter在1.17版本对这个问题做了优化,具体改动可以参考pr 14265。
总结
以上内容介绍了闲鱼在实践中遇到的Flutter内存问题解决思路,给出了内存泄漏定位方法。优化后能在一定程度上减小内存压力,避免不必要的内存占用。闲鱼在内存优化的方向上还有很多需要继续探索的地方,正在做的包括对图片库的缓存改造,layer层内存检测工具等等,朝着不断优化flutter性能体验努力。
相关推荐
- 总结下SpringData JPA 的常用语法
-
SpringDataJPA常用有两种写法,一个是用Jpa自带方法进行CRUD,适合简单查询场景、例如查询全部数据、根据某个字段查询,根据某字段排序等等。另一种是使用注解方式,@Query、@Modi...
- 解决JPA在多线程中事务无法生效的问题
-
在使用SpringBoot2.x和JPA的过程中,如果在多线程环境下发现查询方法(如@Query或findAll)以及事务(如@Transactional)无法生效,通常是由于S...
- PostgreSQL系列(一):数据类型和基本类型转换
-
自从厂子里出来后,数据库的主力就从Oracle变成MySQL了。有一说一哈,贵确实是有贵的道理,不是开源能比的。后面的工作里面基本上就是主MySQL,辅MongoDB、ES等NoSQL。最近想写一点跟...
- 基于MCP实现text2sql
-
目的:基于MCP实现text2sql能力参考:https://blog.csdn.net/hacker_Lees/article/details/146426392服务端#选用开源的MySQLMCP...
- ORACLE 错误代码及解决办法
-
ORA-00001:违反唯一约束条件(.)错误说明:当在唯一索引所对应的列上键入重复值时,会触发此异常。ORA-00017:请求会话以设置跟踪事件ORA-00018:超出最大会话数ORA-00...
- 从 SQLite 到 DuckDB:查询快 5 倍,存储减少 80%
-
作者丨Trace译者丨明知山策划丨李冬梅Trace从一开始就使用SQLite将所有数据存储在用户设备上。这是一个非常不错的选择——SQLite高度可靠,并且多种编程语言都提供了广泛支持...
- 010:通过 MCP PostgreSQL 安全访问数据
-
项目简介提供对PostgreSQL数据库的只读访问功能。该服务器允许大型语言模型(LLMs)检查数据库的模式结构,并执行只读查询操作。核心功能提供对PostgreSQL数据库的只读访问允许L...
- 发现了一个好用且免费的SQL数据库工具(DBeaver)
-
缘起最近Ai不是大火么,想着自己也弄一些开源的框架来捣腾一下。手上用着Mac,但Mac都没有显卡的,对于学习Ai训练模型不方便,所以最近新购入了一台4090的拯救者,打算用来好好学习一下Ai(呸,以上...
- 微软发布.NET 10首个预览版:JIT编译器再进化、跨平台开发更流畅
-
IT之家2月26日消息,微软.NET团队昨日(2月25日)发布博文,宣布推出.NET10首个预览版更新,重点改进.NETRuntime、SDK、libraries、C#、AS...
- 数据库管理工具Navicat Premium最新版发布啦
-
管理多个数据库要么需要使用多个客户端应用程序,要么找到一个可以容纳你使用的所有数据库的应用程序。其中一个工具是NavicatPremium。它不仅支持大多数主要的数据库管理系统(DBMS),而且它...
- 50+AI新品齐发,微软Build放大招:拥抱Agent胜算几何?
-
北京时间5月20日凌晨,如果你打开微软Build2025开发者大会的直播,最先吸引你的可能不是一场原本属于AI和开发者的技术盛会,而是开场不久后的尴尬一幕:一边是几位微软员工在台下大...
- 揭秘:一条SQL语句的执行过程是怎么样的?
-
数据库系统能够接受SQL语句,并返回数据查询的结果,或者对数据库中的数据进行修改,可以说几乎每个程序员都使用过它。而MySQL又是目前使用最广泛的数据库。所以,解析一下MySQL编译并执行...
- 各家sql工具,都闹过哪些乐子?
-
相信这些sql工具,大家都不陌生吧,它们在业内绝对算得上第一梯队的产品了,但是你知道,他们都闹过什么乐子吗?首先登场的是Navicat,这款强大的数据库管理工具,曾经让一位程序员朋友“火”了一把。Na...
- 详解PG数据库管理工具--pgadmin工具、安装部署及相关功能
-
概述今天主要介绍一下PG数据库管理工具--pgadmin,一起来看看吧~一、介绍pgAdmin4是一款为PostgreSQL设计的可靠和全面的数据库设计和管理软件,它允许连接到特定的数据库,创建表和...
- Enpass for Mac(跨平台密码管理软件)
-
还在寻找密码管理软件吗?密码管理软件有很多,但是综合素质相当优秀且完全免费的密码管理软件却并不常见,EnpassMac版是一款免费跨平台密码管理软件,可以通过这款软件高效安全的保护密码文件,而且可以...
- 一周热门
-
-
Python实现人事自动打卡,再也不会被批评
-
【验证码逆向专栏】vaptcha 手势验证码逆向分析
-
Psutil + Flask + Pyecharts + Bootstrap 开发动态可视化系统监控
-
一个解决支持HTML/CSS/JS网页转PDF(高质量)的终极解决方案
-
再见Swagger UI 国人开源了一款超好用的 API 文档生成框架,真香
-
网页转成pdf文件的经验分享 网页转成pdf文件的经验分享怎么弄
-
C++ std::vector 简介
-
飞牛OS入门安装遇到问题,如何解决?
-
系统C盘清理:微信PC端文件清理,扩大C盘可用空间步骤
-
10款高性能NAS丨双十一必看,轻松搞定虚拟机、Docker、软路由
-
- 最近发表
- 标签列表
-
- python判断字典是否为空 (50)
- crontab每周一执行 (48)
- aes和des区别 (43)
- bash脚本和shell脚本的区别 (35)
- canvas库 (33)
- dataframe筛选满足条件的行 (35)
- gitlab日志 (33)
- lua xpcall (36)
- blob转json (33)
- python判断是否在列表中 (34)
- python html转pdf (36)
- 安装指定版本npm (37)
- idea搜索jar包内容 (33)
- css鼠标悬停出现隐藏的文字 (34)
- linux nacos启动命令 (33)
- gitlab 日志 (36)
- adb pull (37)
- python判断元素在不在列表里 (34)
- python 字典删除元素 (34)
- vscode切换git分支 (35)
- python bytes转16进制 (35)
- grep前后几行 (34)
- hashmap转list (35)
- c++ 字符串查找 (35)
- mysql刷新权限 (34)