Android 内存泄漏排查实战

Android 内存泄漏排查过程

前言

最近项目内存占用越来越大,在图片管理优化了一波之后,内存还是有点过大。大概率是出现内存泄漏了,不查不知道,一查吓一跳。很多地方出现了内存泄漏。这里记录一下,存当笔记。

内存泄漏

内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。 —— [ 百度百科 ]

Android内存泄漏

众多周知,安卓是每个Apk都有限制内存大小的。如果内存得不到释放,则非常容易爆OOM内存溢出。所以Android的内存溢出的问题会比其他平台更为严重。如果一个Activity内存泄漏了,会导致这个Activity一直不回收。堆内存会一直持有这个Activity的引用。即使Activity destroied的,系统还是认为他还活着。Gc都无法带走它。非常恐怖。

方法一:Android内存泄漏工具-LeakCanary

这里我就不说了,网上比较多的介绍;这里推荐一个文章可以看看 [ LeakCanary 中文使用说明 ]
里面内容说的主要步骤:

//在 build.gradle 中加入引用,不同的编译使用不同的引用:
dependencies {
   debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3'
   releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3'
 }
//在 Application 中:
public class ExampleApplication extends Application {
  @Override public void onCreate() {
    super.onCreate();
    LeakCanary.install(this);
  }
}
//LeakCanary.install() 会返回一个预定义的 RefWatcher,同时也会启用一个 ActivityRefWatcher,用于自动监控调用 Activity.onDestroy() 之后泄露的 activity。中:
public class ExampleApplication extends Application {

  public static RefWatcher getRefWatcher(Context context) {
    ExampleApplication application = (ExampleApplication)  context.getApplicationContext();
    return application.refWatcher;
  }

  private RefWatcher refWatcher;

  @Override public void onCreate() {
    super.onCreate();
    refWatcher = LeakCanary.install(this);
  }
}

方法二:Android Studio内存排查工具

这里写图片描述

不多说直接上图。Android Studio logcat旁边有个Monitors.切换到这个界面是不是直接可以查看当前的内存占用情况。Memory就是查看内存情况的。
点击CPU下面的Memory 第二个图片就是手动出发Gc的。
第三个图片到处单纯Android占用情况。
中间我标记了四个点
1之前先手动Gc,1我点开了一个新的Activity。 2内存增高。 2-3的途中我关闭的这个新开的Activity。3手动Gc,内存下降。4内存回收成功。和1一样多。
1-4的过程,如果4和1的内存一样多,如上图所示,那么新的这个Activity没有内存泄漏。如果4明显比1的内存要多。那么新的这个Activity一定有泄漏。

两个方法的优缺点

方法一:
优点:1.简单方便 2.能够知道直接是具体某个引用导致的泄漏,方便排查。
缺点:1.要注入代码 2.只能被动排查,泄漏了也不一定触发通知(不知道是不是我的问题,有时候不触发)。3.有时候暴露引用不够明显,比如Android自带的某个引导泄漏,没有具体到我们的代码(当然可能是我用法不对)
方法二:
优点:1.不用增加代码 2.主动排查,一定能触发。
缺点:1.步骤较为繁琐。 2.相比方法一,寻找范围更大,只能判断该Activity有泄漏。

方法二(使用与各种非报错问题)怎么排查

方法二:

//写一段伪代码:
public classA extends BaseActivity{

  @Override public void onCreate() {
    super.onCreate();
    【代码A】
    【代码B】
    【代码C】
  }
}

通过了方法二,发现类A发生了泄漏。若BaseActivity不泄漏,那么泄漏代码必然在【代码ABC】中,那么我们注释下面代码验证,若继续发生内存泄漏那么BaseActivity.onCreate方法一定有问题!不然代码泄漏泄漏出现在【代码ABC】中

//写一段伪代码:
public classA extends BaseActivity{

  @Override public void onCreate() {
    super.onCreate();
    //【代码A】
    //【代码B】
    //【代码C】
  }
}

好了假设问题出现在【代码ABC】中,我们继续排查

//写一段伪代码:
public classA extends BaseActivity{

  @Override public void onCreate() {
    super.onCreate();
    //【代码A】
    【代码B】
    【代码C】
  }
}

泄漏【代码A】肯定有问题;若不泄漏了,【代码A】肯定有泄漏,不然出现就是【代码B】【代码C】
假设【代码A】不泄漏,继续如下

//写一段伪代码:
public classA extends BaseActivity{

  @Override public void onCreate() {
    super.onCreate();
    //【代码A】
    //【代码B】
    【代码C】
  }
}

若还是泄漏,一定出在代码【代码C】,不然在代码【代码B】,那么再分析代码B C什么问题导致的泄漏

经常泄漏的点

好了说了好多废话,说些容易泄漏的点:
1、非静态内部类的静态实例容易造成内存泄漏
这个我没有出现过,没有发言权。因为代码中我较少使用静态实例。

2、activity使用静态成员
这个我也没有出现过。问题和1是类似了。
说下我对1,2的看法。经常少用或者不用静态对象,如用使用,使用与Application同生命周期的存储方式。不要在Application存在静态Context,如有有需求一定要存放。必须在Context生命周期结束的时候释放掉。

3、使用持续延定时的内存问题
如果有handler,特别是延时性handler一定 handler.removeMessages。
如果有handler.postDelayed(某个Runnable, 1000); 一定要mHandler.removeCallbacks(某个Runnable);
如果有Timer timer; 一定要 timer.cancel();
例子:定时循环中,无限循环。

4、注册某个对象后未反注册
这个就不说了,有绑定就一定要有解绑,特别在回调接口的时候,setListener(XXX)。一定要有removeListener(XXX)或者removeAllListeners。观察者模式中有几次addListener(XXX),一定要有几次removeListener(XXX)

5、集合中对象没清理造成的内存泄露
在全局List、Map中,处理了12的问题之后,应该remove调对应的List、Map中的对象。

6、资源对象没关闭造成的内存泄露
播放视频、录音一定要及时关闭。

7、动画泄漏
播放中的动画,一定要及时关闭,这个我再排查中出现了比较多。
三方代码中,其实也是很容易泄漏的。这次修改三方代码中比较多,问题还是在1-7中。可以通过继承或者修改源码的方式实现。

总结

内存泄漏真的很麻烦,因为他不报错。平时写代码的时候真要非常注意,包括一些三方代码中也是有很多泄漏的。一旦泄漏了OOM很可能就随之而来。出现泄漏,先用LeakCanary去排查,比较方便。其次再用方法二根据经常泄漏的点排查出问题。直到Gc能完全收回内存为止。目前Apk暂时未发现内存泄漏了。大概修改至少30处。30多处的内存泄漏,真的恐怖。所以个人分享出来,也是希望和我遇到同样情况的人能有点方向。写的不错,主要还是笔记一下。有纰漏或者不好之处,欢迎邮件[email protected]指出。

注意:转发请注明转帖链接** http://blog.csdn.net/u011850446/article/details/77524800

猜你喜欢

转载自blog.csdn.net/u011850446/article/details/77524800