腾讯MOO音乐关于Flutter的内存泄漏的排查实战(中)转载
导言
MOO 音乐是 TME 旗下的新锐音乐服务,其团队是公司内最早实践 Flutter 的先行者之一。本系列文章将提炼 MOO APP 开发中遇到的情况,就 Flutter 内存占用治理方面,分享日常开发的一些基本认知、注意要点、排查方法和优化方案。内存治理篇文章共分上、中、下三篇,本篇为中篇。
上篇已经介绍了关于Flutter的内存问题腾讯MOO音乐关于Flutter的内存治理(上),今天我们进入实战,来了解一下内存泄漏应该怎样解决!
四、内存泄漏的排查实战
为了便于我们定位具体问题代码,Android Studio 或 VS Code 插件帮我们包装了相关内存工具,这些工具都基于 debug 模式下 Dart VM service 暴露的接口开发的,Dart VM service 自身也带有协助排查内存问题的工具 - Dart VM Observatory,attach 之后访问 service 提供的 http 链接即可使用该工具,内存排查功能路径为:Isolate (main) --> Allocation Profile。
工具有了,如何排查定位导致内存泄漏的问题代码呢?
- 对同一个功能或页面进行反复相同的进入、退出操作;
- 然后执行强制 GC,查看不同操作后的内存快照;
- 对比该功能关联的对象实例增加情况;
- 如果强制 GC 后实例只增不减或该回收的对象没有被回收,没有特殊的延时处理一般就可以判断相关代码有问题。
下面以 Image 内存泄漏排查为例,展示具体的问题代码定位过程,目标是排查列表项内存泄漏,功能进出动作对应着列表项的滑窗动态创建和销毁。
1. 我们打开 APP 其中一个页面,如某个列表页,通过 Xcode 观察应用整体初始内存 470MB,如图所示。
往下翻了几页,内存持续上涨,并且表现为一直翻页一直涨,这种情况大概可以确定页面存在内存泄漏问题,如图所示。
2. 打开自带工具 Allocation Profile 界面后,点击右上角 GC 按钮,强制回收所有可回收对象,如图所示。
回收了可回收对象后,剩余的便是当前 Isolate 状态下的内存快照,都是强引用内存对象。
3. 根据初始内存快照和当前快照的对比,可明显看到图片实例有明显的增长,并且随着不断翻页持续增长,更明显超过了图片缓存的大小的限制。
从这里我们可以确定 Image 对象实例存在引用没被释放的问题,下一步是找出 Image 引用的持有对象。
4. 点击 Image 链接,如图所示。
进入新页面后找到 strongly reachable,展开可看到内存快照中具体的 Image 对象列表,如图七所示。
5. 进入 Image 实例详情界面,如图八所示。
6. 查找对象引用未释放的节点,通常要翻查外部对象引用链,从上图可以看出该 Image 实例最终被 _CacheImage 引用,是图片缓存正常持有的引用。
根据图片缓存的 LRU 机制,可以断定会存在图片缓存释放掉但被其他对象持有的 Image 实例对象,我们可以继续尝试别的 Image 实例排查其引用情况。
7. 经过随机翻查了一些 Image 实例,发现了一些线索,如图九所示。
这个 Image 实例引用已经被 ImageCache 对象释放,但没有被正常回收。
8. 从引用链可以看出,DisplayDecorationImagePainter 对象是我们封装的类,ImageStreamListener 对象持有了相关引用,正是因为这个引用链导致了 Image 对象实例没被回收。
9. 翻查了一下代码,发现是由于 DisplayDecorationImagePainter 注销时没有给 ImageStream 反注册监听。
10. 补充了反注册代码之后,重复前面的操作,持续滚动翻页,图片实例数量维持在 122MB 左右,内存也没有持续增长,如图十所示。
到此,图片内存问题已经得到解决。
排查过程就是在对象实例的引用链上寻找代码线索的过程,针对图片泄漏的排查,可以设置一个较小的图片缓存阈值,减少图片对象缓存,干扰对象少了排查效率就大大更高。
排查泄漏是个频率较高的重复操作,通常排查特定功能都会关注特定的相关对象,自带的排查工具对关注的对象没有较好的过滤功能,而且操作路径长,引用链显示也不够直观。可以考虑自研排查工具来解决这个效率问题。
更多思考:
本次向大家介绍的是Flutter内存泄漏的优化方案,其实Flutter的各种优化都很多,大家可以通过阅读以下文章系统的学习Flutter!