Android之Handler内存泄漏分析及解决

    |     2016年8月16日   |   Android经验   |     0 条评论   |    2023

匿名内部类 Handler 是常见写法,却很容易把 Activity 钉死在主线程消息队列里。Lint 会警告: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) {
      // ... 
    }
  }
}

它会造成严重的内存泄漏。实际编写时往往会看到:

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

二、分析

1. Android 角度

应用启动时,framework 为主线程创建一个 Looper,里面有一条 Message Queue,循环处理队列中的 消息。Activity 生命周期、按钮点击等多数 framework 事件都会入队、逐个处理。主线程 Looper 伴随整个应用生命周期。

在主线程实例化 Handler 后,它会自动与主线程 Looper 的队列关联。每条 Message 都持有对 Handler 的引用,Looper 处理时据此回调 Handler#handleMessage(Message)。

2. 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 finish 之后,这条延时消息在处理前会在主线程队列里待 10 分钟。消息持有 Handler,Handler 又持有外部类 SampleActivity。这条链一直维持到消息被处理,从而阻止 Activity 被回收。

上面的 Runnable 是非静态匿名类,同样持有外部类引用,也会泄漏。

引用谁 为什么活着 后果
Message → Handler 延时消息还在主线程队列 Handler 不能回收
非静态 Handler → Activity 内部类隐含外部 this Activity 泄漏
匿名 Runnable → Activity 同样是非静态匿名类 即使 Handler 修好,Runnable 仍可能钉住页面

四、泄漏解决方案

来源已经明确:

  1. 未处理的消息引用 Handler,非静态 Handler 再引用 Activity,页面无法回收。
  2. 非静态匿名 Runnable 同样引用外部类。

要点:静态内部类不会持有外部类引用。可以把 Handler 放到单独文件,或写成静态内部类。若要在 Handler 里调 Activity,用弱引用指向它,同样不会泄漏。匿名 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();
  }
}

五、小结

静态与非静态内部类差别不大,但 Android 里必须分清:若内部类实例生命周期可能比 Activity 更长,不要用非静态内部类。更好的做法是静态内部类 + 弱引用指向 Activity。

原文链接:http://www.jianshu.com/p/cb9b4b71a820

一句话总结:延时 Message 会抓住 Handler,非静态 Handler/Runnable 再抓住 Activity;改成静态内部类加 WeakReference,泄漏链就断了。

转载请注明来源:Android之Handler内存泄漏分析及解决
本文链接地址:https://ai.zhousir.top/?p=1679

上一篇:

下一篇:

回复 取消