如何有效定位Flutter内存问题?

内存水位升高导致的稳定性问题严重影响app用户体验,所以开发者们非常关注Flutter的内存表现。随着Flutter业务越来越多,闲鱼也面临着oom导致的crash率提升的问题,下面我们结合项目中实际遇到的内存问题和解决思路跟大家分享下flutter内存优化的经验。

本文分为三个部分:

  1. 了解Dart VM内存分配及销毁原理
  2. 通过Observatory工具分析内存泄漏,减少不必要的内存占用
  3. Flutter中常见内存泄漏场景有哪些,如何在业务应用中避免踩坑

Dart VM内存分配及销毁原理

DartVM的垃圾回收机制分两个阶段,新生代(New Generation)和老年代(Old Generation)。

新生代用来存储生命周期较短的对象,由两个内存空间组成,Active内存空间用来分配新对象,inActive内存空间用来作为备用空间,DartVM的内存分配策略非常简单,创建对象时只需要在现有堆上移动指针,内存增长始终是线形的,省去了查找可用内存段的过程。每个Isolate有自己独立的Heap,相互之间无法共享内存,这样可以实现无锁的快速分配。

一旦Active的内存空间被填满,垃圾回收器会从根对象开始遍历检查检查所有对象的引用状态,没有被引用的对象标记为dead状态,非dead状态的对象在下次内存回收事件中会被复制到inActive内存空间,清除Active内存空间,最后Active和inActive内存空间状态调换。

当对象达到一定的生命周期,会被移到老年代内存空间管理,这种垃圾收集策略有两个阶段

  1. 首先遍历对象图,并标记仍在使用的对象。
  2. 扫描整个内存,并回收任何未标记的对象,然后清除所有标志。

这种内存清理的频率较低,并且在扫描回收阶段需要暂停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 ws://127.0.0.1:64673/hXsWR_ZOsGk=/ws, 表示当前连接的VM地址,输入到浏览器就可以看到Observatory主界面,显示了dart vm一些基础信息,具体使用方法可以参考 官方文档,这里不再详细描述,我们重点关注右下角的allocation profile选项。

  • 点击右下角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.
return
3000
;
};

下面我们总结了几种常见内存泄漏的场景,在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 != null&& listenerList != null&& listenerList.contains(listener)) {
      listenerList.remove(listener);
}
}
  • 资源未关闭或释放导致内存泄露,例如ImageStream的图片资源有没有被正常关闭导致的内存泄漏。

问题代码:

void _resolveImage() {
finalImageStream newStream =
    widget.image.resolve(createLocalImageConfiguration(context));
assert(newStream != null);
    _updateSourceStream(newStream);
}

修改方法:在图片组件被销毁时正确释放资源

@override
void dispose() {
...
    _imageInfo.image.dispose();
    _imageInfo = null;

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性能体验努力。


https://mp.weixin.qq.com/s/l58xQlumF3t7vV6FfjgyPQ

借助 Flutter 顺畅地开发多平台应用

发布于:9天以前  |  55次阅读  |  详细内容 »

基于 Flutter 的 Web 渲染引擎「北海」正式开源

发布于:22天以前  |  122次阅读  |  详细内容 »

如何快速提升 Flutter App 中的动画性能

发布于:26天以前  |  111次阅读  |  详细内容 »

Flutter 使用 Riverpod+Retrofit 构建 MVVM 开发模式

发布于:26天以前  |  109次阅读  |  详细内容 »

使用 Flutter 加速应用开发

发布于:1月以前  |  174次阅读  |  详细内容 »

深入分析 Flutter 渲染性能

发布于:1月以前  |  161次阅读  |  详细内容 »

深入分析 Flutter 渲染性能

发布于:1月以前  |  172次阅读  |  详细内容 »

Flutter 不是真正的跨平台框架

发布于:1月以前  |  289次阅读  |  详细内容 »

Flutter 2 来了!

发布于:2月以前  |  291次阅读  |  详细内容 »

Flutter Worker —— 闲鱼这样实现“逻辑跨平台”

发布于:2月以前  |  271次阅读  |  详细内容 »

针对 Web,移动端和桌面端构建的下一代 Flutter

发布于:2月以前  |  233次阅读  |  详细内容 »

使用 flutter_driver 进行集成测试

单元测试可以确保应用的各个部分正常工作,但如何确保整个应用正常工作呢?这就需要用到集成测试了。

发布于:6月以前  |  719次阅读  |  详细内容 »

详解:Flutter内存泄漏解决方案

众所周知,内存的高低是评判一款app的性能优劣的重要的指标之一。如何更简单的帮助开发者分析、暴露且解决内存泄漏问题,几乎是每一个平台或框架、开发者亟需的一个的"标配"的feature。但是对于flutter社区,缺少一款用得顺手的内存泄漏工具。 对于使用flutter而言,因使用dart语言,通过形成渲染树提交到c++的skia进行渲染,从dart层到c++层拥有很长的渲染链路,使用者必须对整个渲染链路有通盘深刻的理解,才能深刻此时此刻的内存使用情况。本文提出一种基于渲染树个数的方式寻找内存泄漏的解决方案。

发布于:6月以前  |  795次阅读  |  详细内容 »

开始使用 Flutter 构建 Windows 桌面应用吧!

我们的使命是为开发者提供一个开源的高效框架,帮助他们在任何平台上构建美观的原生应用。截至目前,我们已经为 Android 和 iOS 带来了生产级别的支持,发布了 8 个稳定版本,仅在 Google Play 商店中就有超过 10 万款应用使用 Flutter。我们在继续努力,将支持范围扩展至 web、macOS 和 Linux 等其他平台。如今,Flutter 的另一个目标,即支持 Windows 的 Flutter Alpha 版本已经发布。

发布于:6月以前  |  827次阅读  |  详细内容 »

如何有效定位Flutter内存问题?

内存水位升高导致的稳定性问题严重影响app用户体验,所以开发者们非常关注Flutter的内存表现。随着Flutter业务越来越多,闲鱼也面临着oom导致的crash率提升的问题,下面我们结合项目中实际遇到的内存问题和解决思路跟大家分享下flutter内存优化的经验。

发布于:7月以前  |  721次阅读  |  详细内容 »

推荐5个Flutter重磅开源项目!

大家好,我是hub哥,近年来,随着移动智能设备的快速普及,移动多端统一开发框架已成为一个热门话题。这里为大家整理了5个Flutter优质的开源项目,希望对大家有帮助

发布于:9月以前  |  1596次阅读  |  详细内容 »

Flutter在PLUS业务中的探索和实践

2016年京东为向核心客户提供更优质的购物体验,特别推出京东PLUS会员,旨在全方位提升和丰富用户网购体验,目前京东PLUS会员已成为电商行业付费人数最多的会员体系。作为PLUS的前端开发,我们思考最多的就是如何让页面更快更好的呈现在用户面前,如何用技术为用户提供最好的购物体验。

发布于:10月以前  |  1184次阅读  |  详细内容 »

京东技术中台Flutter实践之路(二)

发布于:10月以前  |  1453次阅读  |  详细内容 »

首发 | 闲鱼公开多年 Flutter 实践经验

Flutter从诞生到现在,已经成为了跨端开发的领跑者。闲鱼应用在flutter能够以模块形式存在前,进行了很长时间的混合app架构的探索,对原生工程进行较多改动,在官方推出flutter模块模式后,我们进行了大量调研,最终推出了一套开箱即用的混合工程脚手架flutter-boot,帮助大家快速搭建混合工程。

发布于:10月以前  |  1195次阅读  |  详细内容 »

打破重重阻碍,Flutter 和 Web 生态如何对接?

Flutter 设计之初是不考虑 Web 生态的,原因很简单:两种技术设计理念不同,强行融合很可能让彼此都丧失了优势。但是业界又有很多团队在做这种尝试,说明需求是存在的。今天,阿里无线开发专家门柳就来手把手教如何实现 Flutter 和 Web 生态的对接?

发布于:10月以前  |  1177次阅读  |  详细内容 »

最多阅读

Flutter的手势GestureDetector分析详解 1年以前  |  4356次阅读
Flutter插件详解及其发布插件 1年以前  |  3758次阅读
在Flutter中添加资源和图片 2年以前  |  3541次阅读
发布Flutter开发的iOS程序 2年以前  |  2782次阅读
Flutter 状态管理指南之 Provider 1年以前  |  2500次阅读
在Flutter中发起HTTP网络请求 2年以前  |  2451次阅读
使用Inspector检查用户界面 2年以前  |  2367次阅读
Flutter for Web详细介绍 1年以前  |  2362次阅读
Flutter Widget框架概述 2年以前  |  2203次阅读
Flutter框架概览 2年以前  |  2043次阅读
JSON和序列化 2年以前  |  2035次阅读
为Flutter应用程序添加交互 2年以前  |  2032次阅读
Flutter路由详解 1年以前  |  1977次阅读
使用自定义字体 2年以前  |  1889次阅读
处理文本输入 2年以前  |  1886次阅读
编写国际化Flutter App 2年以前  |  1829次阅读
发布Flutter开发的Android程序 2年以前  |  1814次阅读
使用包来开发Flutter应用 2年以前  |  1805次阅读