Android 内存泄漏案例和解析

原文出处: 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 使用不当形成的内存泄漏,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行业人员进入!

http://cxy.liuzhihengseo.com/479.html

相关文章
相关标签/搜索