原文出处: Drakeet (@drakeet) html
Android 编程所使用的 Java 是一门使用垃圾收集器(GC, garbage collection)来自动管理内存的语言,它使得咱们再也不须要手动调用代码来进行内存回收。那么它是如何判断的呢?简单说,若是一个对象,从它的根节点开始不可达的话,那么这个对象就是没有引用的了,是会被垃圾收集器回收的,其中,所谓的 “根节点” 每每是一个线程,好比主线程。所以,若是一个对象从它的根节点开始是可达的有引用的,但实际上它已经没有再使用了,是无用的,这样的对象就是内存泄漏的对象,它会在内存中占据咱们应用程序本来就不是不少的内存,致使程序变慢,甚至内存溢出(OOM)程序崩溃。android
内存泄漏的缘由并不难理解,但仅管知道它的存在,每每咱们仍是会不知觉中写出导致内存泄漏的代码。在 Android 编程中,也是有许多情景容易致使内存泄漏,如下将一一列举一些我所知道的内存泄漏案例,从这些例子中应该能更加直观了解怎么致使了内存泄漏,从而在编程过程当中去避免。
git
首先,比较简单的一种状况是,静态变量导致内存泄漏,说到静态变量,咱们至少得了解其生命周期才能完全明白。静态变量的生命周期,起始于类的加载,终止于类的释放。对于 Android 而言,程序也是从一个 main 方法进入,开始了主线程的工做,若是一个类在主线程或旁枝中被使用到,它就会被加载,反过来讲,假如一个类存在于咱们的项目中,但它从未被咱们使用过,算是个孤岛,这时它是没有被加载的。一旦被加载,只有等到咱们的 Android 应用进程结束它才会被卸载。github
因而,当咱们在 Activity 中声明一个静态变量引用了 Activity 自身,就会形成内存泄漏:编程
Java架构
1异步 2ide 3oop 4post 5 6 7 8 9 10 |
public class LeakActivity extends AppCompatActivity {
private static Context sContext;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_leak); sContext = this; } } |
这样的代码会致使当这个 Activity 结束的时候,sContext 仍然持有它的引用,导致 Activity 没法回收。解决办法就是在这个 Activity 的 onDestroy 时将 sContext 的值置空,或者避免使用静态变量这样的写法。
一样的,若是一个 Activity 的静态 field 变量内部得到了当前 Activity 的引用,好比咱们常常会把 this 传给 View 之类的对象,这个对象如果静态的,而且没有在 Activity 生命周期结束以前置空的话,也会致使一样的问题。
也是一个很常见的情景,常常会遇到的 Handler 问题就是这样一种状况,若是咱们在 field 声明一个 Handler 变量:
Java
1 2 3 4 5 |
private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); } }; |
因为在 Java 中,非静态内部类(包括匿名内部类,好比这个 Handler 匿名内部类)会引用外部类对象(好比 Activity),而静态的内部类则不会引用外部类对象。因此这里 Handler 会引用 Activity 对象,当它使用了 postDelayed 的时候,若是 Activity 已经 finish 了,而这个 handler 仍然引用着这个 Activity 就会导致内存泄漏,由于这个 handler 会在一段时间内继续被 main Looper 持有,致使引用仍然存在,在这段时间内,若是内存吃紧至超出,就很危险了。
解决办法就是你们都知道的使用静态内部类加 WeakReference:
Java
1 2 3 4 5 6 7 8 9 10 11 12 13 |
private StaticHandler mHandler = new StaticHandler(this);
public static class StaticHandler extends Handler { private final WeakReference mActivity;
public StaticHandler(Activity activity) { mActivity = new WeakReference(activity); }
@Override public void handleMessage(Message msg) { super.handleMessage(msg); } } |
另外,综合上面两种状况,若是一个变量,既是静态变量,并且是非静态的内部类对象,那么也会形成内存泄漏:
Java
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class LeakActivity extends AppCompatActivity {
private static Hello sHello;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_leak);
sHello = new Hello(); }
public class Hello {} } |
注意,这里咱们定义的 Hello 虽然是空的,但它是一个非静态的内部类,因此它必然会持有外部类即 LeakActivity.this 引用,致使 sHello 这个静态变量一直持有这个 Activity,因而结果就和第一个例子同样,Activity 没法被回收。
到这里你们应该能够看出,内存泄漏常常和静态变量有关。和静态变量有关的,还有一种常见情景,就是使用单例模式没有解绑导致内存泄漏,单例模式的对象常常是和咱们的应用相同的生命周期,若是咱们使用 EventBus 或 Otto 并生成单例,注册了一个 Activity 而没有在页面结束的时候进行解除注册,那么单例会一直持有咱们的 Activity,这个 Activity 虽然没有使用了,但会一直占用着内存。
另外当咱们使用属性动画,咱们须要调用一些方法将动画中止,特别是无限循环的动画,不然也会形成内存泄漏,好在使用 View 动画并不会出现内存泄漏,估计 View 内部有进行释放和中止。
最后说一说 RxJava 使用不当形成的内存泄漏,RxJava 是一个很是易用且优雅的异步操做库。对于异步的操做,若是没有及时取消订阅,就会形成内存泄漏:
Java
1 2 3 4 5 6 |
Observable.interval(1, TimeUnit.SECONDS) .subscribe(new Action1() { @Override public void call(Long aLong) { // pass } }); |
一样是匿名内部类形成的引用无法被释放,使得若是在 Activity 中使用就会致使它没法被回收,即便咱们的 Action1 看起来什么也没有作。解决办法就是接收 subscribe 返回的 Subscription 对象,在 Activity onDestroy 的时候将其取消订阅便可:
Java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public class LeakActivity extends AppCompatActivity {
private Subscription mSubscription;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_leak);
mSubscription = Observable.interval(1, TimeUnit.SECONDS) .subscribe(new Action1() { @Override public void call(Long aLong) { // pass } }); }
@Override protected void onDestroy() { super.onDestroy(); mSubscription.unsubscribe(); } } |
除了以上这种解决方式以外,还有一种解决方式就是经过 RxJava 的 compose 操做符和 Activity 的生命周期挂钩,咱们可使用一个很方便的第三方库叫作 RxLifecycle 来快捷作到这点,使用起来就像这样:
Java
1 2 3 4 5 6 7 8 9 |
public class MyActivity extends RxActivity { @Override public void onResume() { super.onResume(); myObservable .compose(bindToLifecycle()) .subscribe(); } } |
另外,它还提供了和 View 的便捷绑定,详情能够点击我提供的连接进行了解,这里很少说了。
总结来讲,仍然是前面说的内部类或匿名内部类引用了外部类形成了内存泄漏,因此在实际编程过程当中,若是涉及此类问题或者线程操做的,应该特别当心,极可能不知不觉中就写出了带内存泄漏的代码了。
前面说了很多内存泄漏的场景和对应的解决办法,但若是咱们不知不觉中写出了带有内存泄漏隐患的代码怎么办,面对这个问题,其实到如今,咱们是很幸运的,由于有不少相关的检查方式或组件能够选择,好比最简单的:观察 Memory Monitor 内存走势图,能够或多或少知道内存状况,但若是要精确地追踪到内存泄漏点,这里特别推荐伟大的 Square 公司开源的 LeakCanary 方案,LeakCanary 能够作到很是简单方便、低侵入性地捕获内存泄漏代码,甚至不少时候你能够捕捉到 Android 官方组件的内存泄漏代码,具体使用你们能够自行参看其说明,因为本文主要想讲的是内存泄漏的缘由和一些常见场景,对于检测,这里就很少说啦
问啊-定制化IT教育平台,牛人一对一服务,有问必答,开发编程社交头条 官方网站:www.wenaaa.com 下载问啊APP,参与官方悬赏,赚百元现金。
QQ群290551701 汇集不少互联网精英,技术总监,架构师,项目经理!开源技术研究,欢迎业内人士,大牛及新手有志于从事IT行业人员进入!