内存泄露是Android开发中很常见的性能优化点,如果有内存泄露存在,反复重复内存泄露的操作最终会导致内存溢出,App Crash。 内存泄露总结起来主要就是一些static或者生命周期比较长的引用持有一些内存比较大的对象,比如说将一个很大HashMap传递给static的方法作为参数,或者某个static方法持有Activity的引用。解决的思路就是尝试将持有的对象释放,比如广播的取消注册、EventBus的反注册等,或者将持有的强引用变为弱引用。这篇博客主要从几个常见的案例入手,介绍内存泄露的排查到原因分析到解决泄露的过程。
一、new Handler().post()
1、内存泄露代码
1 | runnable = new Runnable() { |
2、排查过程
- 1、首先进到目标页面进行目标操作然后退出页面
- 2、点击『Android Monitor』的触发GC操作,然后dump内存
<img src=”https://ws1.sinaimg.cn/large/006tNc79gy1fjvx7g388uj30w40d6aas.jpg"/ width=”720” height=”295.2249134948097”>
3、点击右侧『Analyzer Task』自动分析有内存泄露的Activity
<img src=”https://ws1.sinaimg.cn/large/006tNc79gy1fjvx8kj3ivj30ie0gkdg9.jpg"/ width=”60%”>4、在Android Studio中查找对应的Activity的GC root
<img src=”https://ws2.sinaimg.cn/large/006tNc79gy1fjvx9fphzuj31kw0yrtg7.jpg"/ width=”720” height=”439.8046875”>5、如果在Android Studio中不能找出对应的GC root,可以导出标准的hprof文件在MAT中查找
6、导入hprof文件到MAT中,按package查看,找到对应的Activity,右击『Merge shortest path to GC roots』
<img src=”https://ws4.sinaimg.cn/large/006tNc79gy1fjvxvsajvwj30r80u8tbh.jpg"/ width=”540.4411764705883” height=”600”>7、可以看到是Message持有Handler引用,Handler又是内部类,持有Activity引用,导致不能回收
<img src=”https://ws1.sinaimg.cn/large/006tNc79gy1fjvxgdamo3j30qa0c0abf.jpg"/ width=”720” height=”328.79492600422833”>
另外,一个强大的工具Leakcanary可以简单的找出内存泄露的地方。(这玩意虽然功能强大,但是有一定的概率不会检测到,而且需要隔一段时间)
<img src=”https://ws1.sinaimg.cn/large/006tNc79gy1fjvwpl7rvrj30u01hcdix.jpg"/ width=”337.5” height=”600”>
3、泄露原因
Handler内部类持有外部类Activity的引用,而Handler之前Post了一个延时Message,Message又持有Handler的引用,而这个Message在Activity退出之前又没有从消息队列中移除。所以导致泄露。
4、修改写法
1 |
|
二、View.postDelay()
1 | runnable = new Runnable() { |
<img src=”https://ws4.sinaimg.cn/large/006tNc79gy1fjvwpwk26nj30u01hcgnv.jpg"/ width=”337.5” height=”600”>
修改写法
1 |
|
上面的案例代码:https://github.com/JayFang1993/MemoryLeak
三、真实案例分析
这部分涉及公司项目代码,对外不公开
四、总结
总的来说上面的几个案例都可以归结为『内部类持有外部类的引用』导致的泄露。总的解决办法有两个角度,一个是将内部类变为非内部类的实现,另一个是要能保证内部类能够正常释放。
五、工具总结
- Android Monitor(Android Studio自带)
- Memory Analyzer Tools
- Leakcanary