内存泄露是Android开发中很常见的性能优化点,如果有内存泄露存在,反复重复内存泄露的操作最终会导致内存溢出,App Crash。 内存泄露总结起来主要就是一些static或者生命周期比较长的引用持有一些内存比较大的对象,比如说将一个很大HashMap传递给static的方法作为参数,或者某个static方法持有Activity的引用。解决的思路就是尝试将持有的对象释放,比如广播的取消注册、EventBus的反注册等,或者将持有的强引用变为弱引用。这篇博客主要从几个常见的案例入手,介绍内存泄露的排查到原因分析到解决泄露的过程。

一、new Handler().post()

1、内存泄露代码

1
2
3
4
5
6
7
8
runnable = new Runnable() {
@Override
public void run() {
Toast.makeText(HandlerDelayActivity.this, "小主,你出去吧,现在不会『泄露了』", Toast.LENGTH_LONG).show();
}
};
handler=new Handler();
handler.postDelayed(runnable,10000);

2、排查过程

  • 1、首先进到目标页面进行目标操作然后退出页面
  • 2、点击『Android Monitor』的触发GC操作,然后dump内存

<img src=”https://ws1.sinaimg.cn/large/006tNc79gy1fjvx7g388uj30w40d6aas.jpg"/ width=”720” height=”295.2249134948097”>

另外,一个强大的工具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
2
3
4
5
6
@Override
protected void onDestroy() {
super.onDestroy();
if (handler != null)
handler.removeCallbacks(runnable);
}

二、View.postDelay()

1
2
3
4
5
6
7
runnable = new Runnable() {
@Override
public void run() {
Toast.makeText(ViewPostActivity.this, "小主,你出去吧,现在不会『泄露了』", Toast.LENGTH_LONG).show();
}
};
textView.postDelayed(runnable, 10000);

<img src=”https://ws4.sinaimg.cn/large/006tNc79gy1fjvwpwk26nj30u01hcgnv.jpg"/ width=”337.5” height=”600”>

修改写法

1
2
3
4
5
6
@Override
protected void onDestroy() {
super.onDestroy();
if (textView != null)
textView.removeCallbacks(runnable);
}

上面的案例代码:https://github.com/JayFang1993/MemoryLeak

三、真实案例分析

这部分涉及公司项目代码,对外不公开

四、总结

总的来说上面的几个案例都可以归结为『内部类持有外部类的引用』导致的泄露。总的解决办法有两个角度,一个是将内部类变为非内部类的实现,另一个是要能保证内部类能够正常释放。

五、工具总结

  • Android Monitor(Android Studio自带)
  • Memory Analyzer Tools
  • Leakcanary

六、参考链接