背景:oom他又来了,这次是flutter项目,说是flutter,其实是android和flutter混合项目,因为设备是语音控制的,所以和语音相关的全部写在了android层,ui层使用flutter。开始时app崩溃没有任何可用日志以及复现方式。
解决思路:
1、翻日志,第一步肯定是翻日志,定位问题,但是这次没有任何的可用信息,定位不到报错的信息(使用过crashHandle也一样捕获不到任何信息)。
2、观察崩溃的步骤,很遗憾没有任何固定步骤,放着不动会崩溃,操作一会也会崩溃,同时代码和之前不崩溃的时候没有什么变化。根据经验,这类跑着跑着突然崩了没有固定复现操作的,往往就是oom了,所以一开始就怀疑是这个报错。一开始怀疑内存泄漏,但是一来这不是传统android项目,甚至不是传统flutter项目,内存泄漏的排查不太容易,只能先过一遍代码看看有没有可疑的地方(比如dispose中有没有释放controller,比如耗时操作有没有关闭)
3、解决日志问题,adb logcat -v time >D:\log.txt. 打印日志到文本文件,防止idea日志太多刷新掉有用信息。
4、一边运行,一边查看日志,一旦崩溃,立马锁定日志时间,开始慢慢排查(1S大概能打印几百条),没有找到能定位错误的日志,但是有几行很可疑:
1、process XXXX(app包名)has died
2、process XXXX2(设备中另外一个app包名)has died
3、XXXActivity(该app的activity) schedule to restart
4、XXXXService(我们app的service)schedule to restart
分析这几行日志,我有一个大体的猜测,首先不止我们一个app崩溃了,设备中其它的app都崩溃了,这不应该,两个完全独立的软件同时崩溃,我猜测是因为整个系统都崩溃了。其次activity和service都开始重启,但是我没有做任何特殊的重启操作,可以理解为系统自己给他重启的,是不是可以认为,是系统崩了重启,然后开始重启崩溃前的每一个app。
5、看一下设备信息,总内存800M,可用内存220M。有了一个初步的确定,应该是整个系统的内存使用完了导致整个系统进程oom重启了
6、使用profile查看app运行时内存的状态,崩溃的页面是一个轮播图,每一次加载一张图片,内存上涨10M左右(图片本身没有这么大),total到250M的时候app崩溃。和第5点中的猜测非常符合。确定原因:由于轮播图片太多太大导致系统的崩溃
7、开始修复问题,首先轮播图压缩是必须的,1080p的屏幕,图片从1、2M压缩到100k,同时从12张减少到6张,崩溃数量减少,从必崩改为轮播页面跳转到视频音频页面时偶尔崩溃。
8、继续排查问题,偶尔崩溃,十有八九还是oom,继续看profile,轮播图数量的减少确实使得必然崩溃变成了偶尔崩溃,但是6张图片,整个app的内存始终在崩溃的边缘徘徊,一旦打开视频播放页面,视频也要缓存,这时候内存抖动一旦大一点就会出现崩溃。继续排查profile,发现图片的内存始终没有释放,这才导致内存抖动过大
9、一开始以为图片内存无法释放是因为内存泄漏,后来发现过一段时候内存会自己释放,同时为发现加载一张图竟然会导致内存上涨10m,翻查flutter内存的的问题,发现,在dart vm中,会在c/c++层备份一份缓存,使用一个WeakPersistentHandle去根据vm中的gc判断是否回收垃圾,于是,整个问题变成了 图片加载以后,由于缓存的关系,在dart层和在c/c++都有了图片的备份,而主要的大头在c/c++层。使用flutter的内存分析cmd:flutetr run --profile 使用性能分析模式,使用Observatory查看内存泄漏情况(打开一个idea中给的url会自动生成报告)里面发现有6张图确实使用了60M的内存
10、再次确认,确实是图片占用了太多的内存资源,同时,也不存在内存泄漏的问题,根本原因,在于图片的缓存本身会存在一定时间,而这段时间内如果打开视频开始缓存,容易出现内存抖动进而导致oom,于是,解决的方法就是如何释放掉图片的缓存
11、不使netCacheImage的widget,方法不管用,flutter图片加载本身就带有缓存,(期间还想出一个歪点子,提前把那几张图片放到全局的静态资源中,由代码手动控制回收释放,结果该oom还是oom,因为根本的原因在于c/c++层的内存过多,图片加载为了加载性能很多时候都会把缓存放在c/c++层)最终,找到了一篇文章,原来fluuter的图片是由全局缓存的,大概是50M,只要修改这个全局缓存的大小,同时控制释放时机,就能彻底解决这个问题,代码如下:
1.获取ImageCache 缓存对象
ImageCache get imageCache => PaintingBinding.instance.imageCache;
2.设置缓存图片的个数(根据情况自己设置,default = 1000)
imageCache.maximumSize = 1000;
3.获取缓存图片个数
int num = imageCache.currentSize;
4.设置缓存大小(根据情况自己设置,default = 50M)
imageCache.maximumSizeBytes=50<<20;
5.获取图片缓存大小(单位是byte,需自行转换到 M)
int byte=imageCache.currentSizeBytes
6.清除图片缓存
imageCache.clear();
文章地址:flutter 清除图片缓存 - 简书 (jianshu.com)
附上一篇咸鱼出的flutter内存泄漏检测说明:(17条消息) 详解:Flutter内存泄漏解决方案_淘系技术-CSDN博客
同时我对里面他们用的泄漏检测的软件很感兴趣,但是似乎搜不到,希望有了解的朋友可以告诉我