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

作者: 瓶子里的王国 | 来源:发表于2017-03-23 22:03 被阅读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.

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

  • 当一个Android应用第一次启动的时候,Framework会为整个应用程序的主线程创建一个Looper对象,一个Looper对象维护了一个message queue消息队列,并且Looper对象会去一个一个的去轮询消息队列里的Message消息对象。应用程序框架的所有主要事件(例如,Activity生命周期方法的回调,一个Button的点击事件等)都会被一个Message对象持有,并添加到Loopermessage queue中被一个接一个地去处理。主线程中的Looper对象的生命周期贯穿整个应用的生命周期。
  • 当一个Handler在主线程被创建,它就和Looper以及Looper所对应的MessageQueue关联起来了,被这个Handler对象sendmessage queue中的所有Message对象都会持有这个Handler对象的引用,正因为如此framework才可以去调用相应HandlerhandleMessage(Message)方法去处理相应的消息。这句比较拗口,Handler对象send一个Message对象的时候,会将Handler自身赋值给Messagetarget属性,处理该消息的时候会取出该target并调用它的handleMessage(Message)方法去处理,所以每个Message对象都会持有send它的那个Handler对象的引用。
  • 在Java中,非静态的内部类和匿名内部类都会隐式的持有它们外部类的引用;而静态内部类不会。

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

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?

相关文章

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

    Handler和内部类怎么引起内存泄漏? 先看下面一段代码: 我们平时使用Handler的时候,相信很多人都会这样...

  • java基础

    Handler怎样防止内存泄漏 handler引起内存泄漏的原因:由于handler的写法问题,如果handler...

  • 内存泄漏实战总结

    一.handler造成内存泄漏: 非静态内部类会持有外部类的引用,handler的message的target持有...

  • Android性能-内存泄漏

    泄漏原因: 单例造成的内存泄漏 非静态内部类(匿名类等)创建静态实例造成的内存泄漏 Handler/Thread/...

  • Handler

    一,什么是handler 二,handler的使用 三,handler机制原理 四,handler引起的内存泄漏和...

  • Handler内存泄漏警告处理

    Handler内存泄漏原理:非静态内部类会隐性持有外部类的引用.在Message发送过程中,引用了Handler对...

  • Android内存泄漏

    1. 内部类隐性的持有外部类的引用导致内存泄漏 private Handler Myhander=new Hand...

  • Handler的正确使用姿势

    为了避免因为handler持有activity导致的内存泄漏 可以将内部类写成静态内部类 static class...

  • 内存泄露

    什么是内存泄漏? 引用导致的对象未被释放 引起内存泄漏的因素: 单例 非静态内部类持有外部类的隐式引用,而在外部类...

  • 2018-04-18 Handler的基本用法

    1.Handler实现原理: 2.为了防止使用Handler导致内存泄漏,所以采用静态内部类MyHandler 使...

网友评论

  • 楷桐:不要用长生命周期的引用短生命周期的,如果必须要引用短周期的方法或变量,要用弱引用去修饰一下短生命周期的对象
  • HaiNiu0:好文!但是静态内部类必须使用外部静态变量,这点好像和文章最后一句话冲突了,请问楼主如何看待这点呢?
    瓶子里的王国:@HaiNiu0 如果是静态内部类的话,就不要直接去引用外部类的变量了,可以在内部类里弱引用一个外部类实例,然后用外部类.this.变量,这样外部类变量就不用声明为static。
    HaiNiu0:@瓶子里的王国 就有一点一直困惑我的。外部变量用了静态,确实内部类用完就被gc掉了,但是外部的成员变量是静态的了。当需要在其他的地方用到该外部类的成员变量时就只能类.成员变量了,这样的话外部类岂不是一直被引用了么?
    瓶子里的王国:@HaiNiu0 就是因为静态内部类不可引用外部类的非静态成员,所以它才不会对外部类有隐式引用。好好体会体会哦😊

本文标题:Handler和内部类引起的内存泄漏

本文链接:https://www.haomeiwen.com/subject/rrztottx.html