定位Flutter内存问题很难么?
liuian 2025-05-21 14:58 15 浏览
内存水位升高导致的稳定性问题严重影响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);}
修改方法:在图片组件被销毁时正确释放资源
@overridevoid dispose {..._imageInfo.image.dispose;_imageInfo = ;super.dispose;}
PlatformAssetBundle.loadString通过asset读取String内容会一直缓存读取的内容,造成内存无法释放
问题代码:
/// 通过asset读取JsonFuture<Map<String, dynamic>> loadJsonAsset(String assetPath) async{_rootBundle ??= PlatformAssetBundle;finalString jsonStr = await _rootBundle.loadString(assetPath);return json.decode(jsonStr);}
修改方法:
/// 通过asset读取JsonFuture<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性能体验努力。
相关推荐
- Python处理文件系统路径,pathlib库使用
-
pathlib是Python3.4及以后版本中的一个内置类库,它提供了一种简单而直观的方式来处理文件系统路径,同时也能够轻松地处理各种不同类型的路径。在本教程中,我们将介绍如何使用pathlib类库来...
- Python目录与文件操作教程(python文件目录结构)
-
大家好,我是ICodeWR。今天要记录的是如何使用Python进行常见的目录和文件操作。Python提供了强大的内置模块来处理文件和目录操作。1.基本模块介绍Python中主要使用以下模块进行文件...
- Python文件操作(python文件操作菜鸟教程)
-
文件编码编码技术即:翻译的规则,记录了如何将内容翻译成二进制,以及如何将二进制翻译回可识别内容。计算机中有许多可用编码:UTF-8、GBK、Big5等。不同的编码,将内容翻译成二进制也是不同的。编码有...
- python中文件读写操作最佳实践——使用 os.path 进行路径操作
-
在Python中处理文件路径时,使用os.path模块比直接使用字符串拼接更加安全、可靠且跨平台。下面我将详细解释为什么以及如何使用os.path进行路径操作。为什么不应该使用字符串拼接?#不推荐的...
- Python中列出目录下所有文件的方法
-
技术背景在Python编程中,经常需要列出目录下的所有文件,根据不同的需求,可能只需要列出当前目录下的文件,也可能需要递归列出子目录下的所有文件。Python提供了多种方法来实现这一功能,下面将详细介...
- Python文件及目录处理方法(python列出目录下文件)
-
Python可以用于处理文本文件和二进制文件,比如创建文件、读写文件等操作。本文介绍Python处理目录以及文件的相关方法。下面先来介绍python目录处理相关方法。目录操作1.获取当前代码路径te...
- Python路径操作的一些基础方法(python 程序路径)
-
带你走进@机器人时代Discover点击上面蓝色文字,关注我们Python自动化操作文件避开不了路径操作方法,今天我们来学习一下路径操作的一些基础。Pathlib库模块提供的路径操作包括路径的...
- Python如何获取当前文件所在目录的完整路径
-
在编程的过程中,我们常常会遇到需要获取当前文件所在目录完整路径的需求。那具体该怎么做呢?这是在众多开发者群体中备受关注的一个问题,就像在问答平台上“/questions/3430372/how-d...
- AI超助:打造第一个Python应用(6)(python ai软件)
-
即然已定确定时间节点,倒计时。期间还有个中秋节,便得排好了日程,这段时间得紧着点。打开pycharm就好似在用点金术。调试python,就好似用各种食材准备一道大餐。照例每天碰到疑难,照例每天遇到欣...
- 我在iPad上装了个IDEA,撸了个SpringBoot项目
-
刚开发的哥们儿都知道,系统上线后,如果有bug,领导会第一时间打电话过来,半夜也得起来改bug,随时随地改bug就是程序员的日常,这就导致很多程序下班后必须背着电脑挤地铁,随时候着,拖着疲惫的身体再扛...
- 用户说 | 通义灵码2.0,跨语言编码+自动生成单元测试+集成DeepSeek
-
作者:小鱼引言通义灵码是我一直使用的编码协助工具,我也愿称之为国内程序员的”饭搭子”。通义灵码,作为国内首个AI程序员,从最开始的内测到公测,再到通义灵码正式发布第一时间使用,再到后来使用企业定...
- 初识C语言:简介、环境搭建、第一个HelloWorld
-
一、本系列文章简介什么是C语言C语言之父C语言的由来C语言的发展为什么要学C语言/能做什么为什么要学习这套C语言本套课程适用人群本套笔记介绍二、C语言标准2.1标准简史1.1972年C语言在尔实验...
- 5款工具,让你轻松创建并分享优美的项目源码
-
作为一名开发人员,经常会遇到一些分享和展示代码的需求。例如,在博客分享知识时、请教别人编码方面问题时,都需要用到代码的展示与分享。对于我来说感受最为强烈的就是分享知识时,作为一名IT技术方面的分享者,...
- 风变编程-python基础语法-第17关用python发邮件
-
复习一下上一关的内容,模块的相关知识,以及学习模块的方法(2图)得了,我们可以开始今天的项目实操了1.明确项目目标这一关的主题,其实最早是来源于往期学员的一个问题:学员小贾是一名外贸人员,每到了节...
- 如何在Eclipse中配置Python开发环境?
-
Eclipse是著名的跨平台集成开发环境(IDE),最初主要用来Java语言开发。但是我们通过安装不同的插件Eclipse可以支持不同的计算机语言。比如说,我们可以通过安装PyDev插件,使Eclip...
- 一周热门
-
-
Python实现人事自动打卡,再也不会被批评
-
Psutil + Flask + Pyecharts + Bootstrap 开发动态可视化系统监控
-
【验证码逆向专栏】vaptcha 手势验证码逆向分析
-
一个解决支持HTML/CSS/JS网页转PDF(高质量)的终极解决方案
-
再见Swagger UI 国人开源了一款超好用的 API 文档生成框架,真香
-
网页转成pdf文件的经验分享 网页转成pdf文件的经验分享怎么弄
-
C++ std::vector 简介
-
python使用fitz模块提取pdf中的图片
-
《人人译客》如何规划你的移动电商网站(2)
-
Jupyterhub安装教程 jupyter怎么安装包
-
- 最近发表
- 标签列表
-
- 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)
- table.render (33)
- uniapp textarea (33)
- python判断元素在不在列表里 (34)
- python 字典删除元素 (34)
- vscode切换git分支 (35)
- python bytes转16进制 (35)
- grep前后几行 (34)
- hashmap转list (35)