Android面试经验Android程序猿Android开发

Handler和内部类引起的内存泄漏

2017-03-23  本文已影响279人  瓶子里的王国

Handler和内部类怎么引起内存泄漏?

先看下面一段代码:

public class SampleActivity extends Activity {

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

我们平时使用Handler的时候,相信很多人都会这样写一个Handler。但是这样会引起内存泄漏。Android Lint也会给出下面的警告:

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

那么,这块代码的内存泄漏究竟怎么引起呢?先看以下几个知识点:

知道了以上几个知识点,就可以去检查哪里会出现内存泄漏了,再看下面的代码:

public class SampleActivity extends Activity {
 
  private final Handler mLeakyHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
      // ...
    }
  };
 
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
 
    // Post a message and delay its execution for 10 minutes.
    mLeakyHandler.postDelayed(new Runnable() {
      @Override
      public void run() { /* ... */ }
    }, 1000 * 60 * 10);
 
    // Go back to the previous Activity.
    finish();
  }
}

当这个Activityfinish掉的时候,Handler发送的这个Message会继续在主线程的message queue中存在1000*60*10ms才会被处理掉。这个Message持有了activityHandler对象的引用,并且Handler内部类又隐式地持有它的外部类SampleActivity的引用,在Message被处理之前,这个引用链会一直存在,从而会阻止activitycontext被垃圾回收器回收,这样就会导致这个activity引用的所有resources造成内存泄漏。(Message被处理掉之后,再遇到GC,该Message对象就会被回收,其引用的Handler对象也会被回收,相应的activity也就可以被回收了,如果没有其他引用继续引用它时)。代码中的new Runnable也是同理。非静态的匿名内部类也会隐式地持有外部类的引用,也会造成内存泄漏。

解决这种情况造成的内存泄漏

要解决这个问题,可以在单独的java文件里去写一个类继承Handler或者将Handler内部类声明为staticstatic修饰的内部类不会持有外部类的引用,也不会造成activity的泄漏。如果你要在内部类中调用activity的方法,可以使用ActivityWeakReference。要解决匿名内部类Runnable造成的内存泄漏,我们可以将该匿名内部类声明为activity的静态属性(匿名内部类的静态实例不会持有外部类的引用)。

public class SampleActivity extends Activity {

  /**
   * Instances of static inner classes do not hold an implicit
   * reference to their outer class.
   */
  private static class MyHandler extends Handler {
    private final WeakReference<SampleActivity> mActivity;

    public MyHandler(SampleActivity activity) {
      mActivity = new WeakReference<SampleActivity>(activity);
    }

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

  private final MyHandler mHandler = new MyHandler(this);

  /**
   * Instances of anonymous classes do not hold an implicit
   * reference to their outer class when they are "static".
   */
  private static final Runnable sRunnable = new Runnable() {
      @Override
      public void run() { /* ... */ }
  };

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

    // Post a message and delay its execution for 10 minutes.
    mHandler.postDelayed(sRunnable, 1000 * 60 * 10);
    
    // Go back to the previous Activity.
    finish();
  }
}

静态内部类和非静态内部类的区别很微妙,也是每一个Android程序员应该明白的。

使用Activity的生命周期函数解除引用

就是在ActivityonPauseonStoponDestroyremove掉相应的messagecallback

结论:

如果一个内部类的实例的生命周期比Activity的生命周期长,就避免使用非静态的内部类,改用静态内部类。

附:静态变量和静态内部类

static关键字在静态变量和静态类中的作用不同。

静态变量意思是该变量属于该类的所有实例,当该类的一个实例被GC回收掉之后,它并不会被GC回收掉,它会一直存在于内存中,直到你显示地给它赋值为null。把一个Drawable对象设置成static修饰的,因为一个Drawable对象往往会持有一个View的引用,而View对象往往又会持有activity的引用,所以一个staticDrawable对象会强制使activity在被销毁后还要在内存中存在,除非你显示地将Drawable置为空。

static修饰的class和static修饰的变量意义不同。static修饰的内部类就像在单独的.java文件中声明的类一样;一个非静态的内部类则和它的外部类有隐式的关联,一个非静态内部类的实例不能脱离了外部类的实例而单独存在。

总之记住一句话,在activity内部如果要使用内部类,尽量使用static,成员变量尽量少用static

How to Leak a Context: Handlers & Inner Classes

How to stop runnable when the app goes to background?

上一篇下一篇

猜你喜欢

热点阅读