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

一、new Handler().post()

1、内存泄露代码

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内存

  • 3、点击右侧『Analyzer Task』自动分析有内存泄露的Activity

  • 4、在Android Studio中查找对应的Activity的GC root

  • 5、如果在Android Studio中不能找出对应的GC root,可以导出标准的hprof文件在MAT中查找

  • 6、导入hprof文件到MAT中,按package查看,找到对应的Activity,右击『Merge shortest path to GC roots』

  • 7、可以看到是Message持有Handler引用,Handler又是内部类,持有Activity引用,导致不能回收

另外,一个强大的工具Leakcanary可以简单的找出内存泄露的地方。(这玩意虽然功能强大,但是有一定的概率不会检测到,而且需要隔一段时间)

3、泄露原因

Handler内部类持有外部类Activity的引用,而Handler之前Post了一个延时Message,Message又持有Handler的引用,而这个Message在Activity退出之前又没有从消息队列中移除。所以导致泄露。

4、修改写法

@Override
protected void onDestroy() {
   super.onDestroy();
   if (handler != null)
       handler.removeCallbacks(runnable);
}

二、View.postDelay()

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

修改写法

@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

六、参考链接