[译]Handlers和内部类如何造成Context泄露

How to Leak a Context: Handlers & Inner Classes
看一下下面代码:

public class SampleActivity extends Activity {

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

虽然看起来没有很明显的问题,但是这段代码却存在着会导致严重的内存泄露的风险。Android Lint会给出以下提示:

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

但是,究竟泄露会发生在哪里还有它是怎样可能会发生的?下面让我们一起来通过我们所知道的知识来确定问题的源头:
1. 当一个安卓应用第一次启动的时候,安卓框架会给APP的主线程创建一个Looper对象。Looper继承了一个简单的在一个圆环中一个接一个地处理消息对象的消息队列。所有的应用层框架事件(例如Activity的生命周期方法的调用,按钮点击事件等等)都是包含在消息对象的范围内,都会添加到Looper的消息队列中并且一个接一个地被处理掉。主线程的Looper通过应用的生命周期的存在而得以存在。
2. 当一个Handler在主线程被初始化的时候,它和Looper的消息队列关联着。那么被发送到消息队列的消息会对Handler持有一个引用以便当Looper最终会处理这个消息的时候,框架可以调用Handler的handleMessage(Message message)方法。
3. 在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();
  }
}

当这个Activity被结束掉的时候,这个延迟的消息会在它被Looper处理掉之前继续在主线程的消息队列里面继续存活十分钟。而这个时候,消息对象持有着这个Activity的Handler一个引用,而这个Handler就对它的外部类(这里就是指SampleActivity)持有着一个隐式引用。这个引用会一直存在直到消息对象被处理掉,从而导致这个Activity的Context不能被GC进行垃圾回收并且泄露所有App的资源。记住,匿名Runnable类那一行也是如此。非静态的匿名内部类会对他们的外部类持有一个隐式引用,从而导致Contexxt会造成泄露。
要想解决这个问题,可以在一个新文件里面继承Handler或者使用一个静态的内部类进行替代。静态的内部类不会对它们的外部类持有一个隐式引用,所以Activity不会造成泄露。如果你需要在内部类里面调用外部类的方法,你可以让这个Handler对Activity持有一个弱引用,那么你就不会意外地泄露一个Context了。为了解决当我们初始化一个匿名Runnable类的时候发生的内存泄露,我们创建了这个类的静态成员变量(因为静态的内部类对象不会对他们的外部类持有引用):

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();
  }
}

静态和非静态的内部类的区别是很微妙的(我还是感觉不出来哪里微妙了),但这是每一个安卓开发者都应该理解的一些东西。那么重点是什么呢?(还以为要翻译成底线哩) 那就是,在一个Activity里面如果内部类对象可以在Activity生命周期外存在,那么就应该避免使用非静态的内部类。相反,更加推荐使用一个静态内部类并且在其内部对Activity保持一个弱引用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值