关于Handler 或 内部类 的内存泄漏:The xxx class should be static or leaks might occur

这篇文章是翻译过来的!

我们先来看一下下面的代码:

public class MainActivity extends Activity {

  private final Handler mLeakyHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
      // ... 
    }
  };
}

当我们这样写的时候,就可能会导致内存泄漏,

我们的编译器可能会出现类似下面的警告:

In Android, Handler classes should be static or leaks might occur.

This Handler class should be static or leaks might occur 

这里先补一下基础:

1、当Application 第一次启动时,framework 会为 Application 的主线程 创建一个 Looper 对象,实现一个简单的消息队列,一个接一个循环处理Message 对象。Message对象包含所有主要的应用程序框架事件(如Activity生命周期的方法调用,button的点击事件,等),message会被加入到Looper的消息队列并一个一个处理。主线程的Looper存在于整个Application的生命周期。

2、当一个Handler在主线程中实例化时,它关联着Looper的消息队列,发送到消息队列的Message 持有Handler的引用,所以framework可以在Looper处理Message时通过对Handler的引用,调用Handler的handleMessage(Message)方法。

3、在Java中,非静态内部类和匿名类会持有一个外部类的(隐式)引用。静态内部类则不会持有外部类的引用。

那么问题出在哪里呢?

首先,非静态的Handler类会默认持有外部类的引用,包含Activity等。
然后,还未处理完的消息(Message)中会持有Handler的引用。
还未处理完的消息会处于消息队列中,即消息队列MessageQueue会持有Message的引用。
消息队列MessageQueue位于Looper中,Looper的生命周期跟应用一致。

因此,此时的引用关系链是Looper -> MessageQueue -> Message -> Handler -> Activity。所以,此时退出Activity的话,由于存在上述引用关系,垃圾回收器将无法回收Activity,造成内存泄漏。

拿下面代码举个例子:

public class MainActivity extends Activity {
 
  private final Handler mLeakyHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
      // ...
    }
  };
 
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
 
    mLeakyHandler.postDelayed(new Runnable() {
      @Override
      public void run() { /* ... */ }
    }, 1000 * 60 * 10);
     
    //销毁Activity
    finish();
  }
}

当Activity被 finish 时,延迟的message仍然在主线程的消息队列存活10分钟,直到它被处理,这个message持有对Handler的引用,然后Handler又持有对Activity的隐式引用。这个引用将会保持到message被处理,阻止activity被回收,导致内存泄漏。同样匿名类 Runnable也是一样。非静态匿名类持有外部类的隐式引用,也会导致内存泄漏。

解决这个问题的方法:

①Activity退出时,移除所有信息,移除信息后,Handler将会跟Activity生命周期同步

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mHandler.removeCallbacksAndMessages(null);
    }

②可以将Handler和Runnable声明为静态,静态内部类不会持有外部类的隐式引用,所以不会导致内存泄漏。如果你需要在Handler中调用外部Activity的方法,可以让Handler持有Activity的弱引用

弱引用:当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象,不过要注意的是,这里所说的被弱引用关联的对象是指只有弱引用与之关联,如果存在强引用同时与之关联,则进行垃圾回收时也不会回收该对象。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。 

如下:

public class MainActivity extends Activity {

  private static class MyHandler extends Handler {
    private final WeakReference mActivity;

    public MyHandler(MainActivity activity) {
      mActivity = new WeakReference(activity);
    }

    @Override
    public void handleMessage(Message msg) {
      MainActivity activity = mActivity.get();
      if (activity != null) {
        // ...
      }
    }
  }

  private final MyHandler mHandler = new MyHandler(this);

  private static final Runnable sRunnable = new Runnable() {
      @Override
      public void run() { /* ... */ }
  };

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    mHandler.postDelayed(sRunnable, 1000 * 60 * 10);

    finish();
  }
}

总结:

当内部类的实例可以在Activity的生命周期外存活时,避免使用非静态内部类。
相对的,使用静态内部类并持有Activity的弱引用。(或者在Activity退出时,移除所有信息)

你可能感兴趣的:(android)