Context泄漏:Handlers & Inner Classes

本文分析了Android中由于Handler和内部类可能导致的内存泄漏问题。当Handler在Activity中实例化并与消息队列关联时,如果Activity结束但Handler仍有消息待处理,会导致Activity无法被回收,从而引发内存泄漏。解决方案包括使用静态内部类和弱引用,以防止对Activity的隐式引用导致的资源泄漏。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >




先思考下面一个代码片段

publicclass SampleActivity extends Activity {
 
  private final Handler mLeakyHandler = new Handler() {
    @Override
    publicvoid handleMessage(Message msg) {
      // ...
    }
  }
}

虽然不容易发现,但上面的代码会导致大量的内存泄漏。如果是在Eclipse中编写代码的话Android Lint会警告: In Android,Handlers should be static or leaks might occur。但是内存泄漏到底是在哪发生的和到底是怎么发生的?现在就让我们来确定问题的根源,首先我们知道的是:

     1、当一个Android应用启动时,Android平台框架会为应用的UI主线程创建一个Looper对象。Looper简单的实现了一个消息队列,采用队列的方式处理一个个消息。所有主要的应用程序框架中的事件都包含在消息对象中,所有这些事件消息都加入到Looper的消息队列中,一个接着一个的处理掉。并且UI主线程的Looper对象贯穿于应用程序的整个生命周期。

     2、当一个Handler对象在UI主线程中经过实例化后,就与Looper消息队列相关联了。当一个消息发布进消息队列中时,Looper对象会持有一个Handler对象,以便于当Loope处理此消息时,Android平台框架能够调用Handler对象的   Handler#handleMessage(Message) 方法来进行处理 

     3、在Java当中,非静态的和匿名的内部类会隐式的持有它们外部类的一个引用

那么到底在哪里内存泄漏了呢?虽然非常的隐秘微妙,但是咋们不妨来看看下面一段代码

publicclass SampleActivity extends Activity {
 
  privatefinal Handler mLeakyHandler = new Handler() {
    @Override
    publicvoid handleMessage(Message msg) {
      // ...
    }
  }
 
  @Override
  protectedvoid onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
 
    // Post a message and delay its execution for 10 minutes.
    mLeakyHandler.postDelayed(newRunnable() {
      @Override
      publicvoid run() { }
    },60 * 10 * 1000);
     
    // Go back to the previous Activity.
    finish();
  }
}

当这个Activity结束销毁时,推迟的处理的消息依然会存在于消息队列中10分钟直到被处理掉。消息对象持有Activity的Handler对象的引用,而Handler对象持有一个隐式的外部类的引用(在上面的例子中是SampleActivity)。这个Activity引用会一直存在直到该消息被处理掉,因此阻止了垃圾回收器对Activity Context对象的回收,泄漏了所有的应用程序资源。上面的代码片中的第15行,非静态Runnable匿名类隐式持有Activity一个引用,而非静态匿名类对象Handler又隐式的持有Activity引用,所以Activity Context对象泄露了。

要解决此问题,我们可以使用一个外部的Handler子类(Activity Context依然可能会被泄漏,稍后会提到规避的方法)或者使用一个静态的Handler子类内部类。静态内部类并不会隐式的持有外部类的一个引用,所以不会泄漏Activity Context对象。但是如果你希望在静态内部类Handler子类中使用外部类Activity中的一些属性或方法,我们可以在静态内部类中持有一个外部类Activity的弱引用,这样Activity Context对象就不会泄漏了。当然我们也要把Runnable定义成一个静态的属性(静态的匿名类对象不会持有外部类的引用),如下代码所示

publicclass SampleActivity extends Activity {
 
  /**
   * Instances of static inner classes do not hold an implicit
   * reference to their outer class.
   */
  privatestatic class MyHandler extends Handler {
    privatefinal WeakReference<SampleActivity> mActivity;
 
    publicMyHandler(SampleActivity activity) {
      mActivity = new WeakReference<SampleActivity>(activity);
    }
 
    @Override
    publicvoid handleMessage(Message msg) {
      SampleActivity activity = mActivity.get();
      if(activity != null) {
        // ...
      }
    }
  }
 
  privatefinal MyHandler mHandler = new MyHandler(this);
 
  /**
   * Instances of anonymous classes do not hold an implicit
   * reference to their outer class when they are "static".
   */
  privatestatic final Runnable sRunnable = new Runnable() {
      @Override
      publicvoid run() { }
  };
 
  @Override
  protectedvoid onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
 
    // Post a message and delay its execution for 10 minutes.
    mHandler.postDelayed(sRunnable,600000);
     
    // Go back to the previous Activity.
    finish();
  }
}

虽然静态和非静态内部类之间的区别是相当微小的,但是每个Android开发者都应当知道。我们应当尽量避免在Activity中使用生命周期长于Activity生命周期的内部类,而应当使用静态内部类并且持有Activity的一个弱引用方式。





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值