1.问题描述:
一个聊天对话页面,由于对话框形状需要自定义,因此采用了
CustomPainter
来自定义绘制对话框。测试过程中发现在ipad mini
上不停地上下滚动对话框列表竟然出现了crash
,进一步测试发现连天过程中也会频繁出现crash
。
2.问题分析:
1.首先,对于crash我们想到的是会不会是某处空指针引起的。接着我们给可能出现空指针的调用全部添加了容错处理(
dart
语言只需在对象后面添加‘?’),处理完之后发现crash
依然存在(令人崩溃...)。2.排除了空指针因素后,我们继续猜测会不会是某个操作引起主线程阻塞导致
crash
。经过排查,页面中并无此情况。3.无计可施的情况下,我们在安卓手机上安装APP(之前一直都是在ios上测试)并进入聊天页进行测试,果然
crash
如期而遇。仔细分析log后我们发现,每次crash
之前必定有一次以上的system.gc()
。由此我们进一步猜测是否可能因为内存溢出导致的crash
,抓住这一条线索,我们在xcode
上进行了运行时内存观测。果不其然,每次crash
时APP
的内存占用相当高,最高逼近总内存的50%。由此可初步推断crash由于内存泄漏引起。
3.内存使用情况
4.问题探究
通过打log我们终于找出了原因,导致内存泄漏的罪魁祸首便是
CustomPainter
的paint
函数被不停地回调,静止(不做任何手工交互)时次数一度达到上千次,这简直是噩梦。然后通过尝试, 将CustomPaint
的child
注释后再次运行后一切恢复了正常。检查child
我们发现里面除了一个动画(拥有静止态和播放态)和一切普通的Widget
外并无其他,因此尝试着将动画注释后,仍然运行正常。加入动画并播放后再次出香paint
被重复疯狂回调直至动画播放结束才停止回调。由此,我们断定出现内存泄漏的最终原因是动画播放导致
CustomPaint
的paint
函数返回回调,最终致使内存溢出从而APP发生Crash。
5.解决方法
问题既然是由于重复绘制导致内存泄漏引起的,显然最容易想到的解决方法就是通过禁止它重复绘制来保护内存。而重复绘制又是由动画引起,这里动画又是必须存在的组件,所以最终还是得从
paint
上寻找突破口。
查阅 flutter官方文档,有这么一段描述:
The most efficient way to trigger a repaint is to either:
1.Extend this class and supply arepaint
argument to the constructor of the CustomPainter, where that object notifies its listeners when it is time to repaint.
2.Extend Listenable (e.g. via ChangeNotifier) and implement CustomPainter, so that the object itself provides the notifications directly.
显然,我们这里paint
被回调都不属于这两种情况(因为除了播放动画,其他什么也没错,所以不存在改变widget内容的情况)。官方文档也无助于事,怎么办!!!!!!终于功夫不负有心人,我们找到了它:RepaintBoundary
(传送门,通过将动画组件用RepaintBoundary
包裹起来,动画播放时免除了一切多余的绘制操作,内存蹭蹭蹭地降下来了......
6.心得体会
花了一天时间解决这个问题,真让人头大。初学flutter,遇到过不少一时难以解决的问题,不过好在方法总比问题多,只要肯用心去发现,没有解决不了的问题。