解决由Handler引发的内存泄漏

在使用Handler更新UI的时候,我是这样写的:

public class SampleActivity extends Activity {

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

看起来很正常的,但是 Android Lint 却给出了警告:

This Handler class should be static or leaks might occur
Since this Handler is declared as an inner class, it may prevent the outer class from being garbage collected. If the Handler is using a Looper or MessageQueue for a thread other than the main thread, then there is no issue. If the Handler is using the Looper or MessageQueue of the main thread, you need to fix your Handler declaration, as follows: Declare the Handler as a static class; In the outer class, instantiate a WeakReference to the outer class and pass this object to your Handler when you instantiate the Handler; Make all references to members of the outer class using the WeakReference object.

意思是说:这个Handler 必须是static的,否则就会引发内存泄露。

其实,对于这个问题,Android Framework 的工程师 Romain Guy 早已经在Google论坛上做出过解释,并且给出了他的建议写法:

I wrote that debugging code because of a couple of memory leaks I found in the Android codebase. Like you said, a Message has a reference to the Handler which, when it’s inner and non-static, has a reference to the outer this (an Activity for instance.) If the Message lives in the queue for a long time, which happens fairly easily when posting a delayed message for instance, you keep a reference to the Activity and “leak” all the views and resources. It gets even worse when you obtain a Message and don’t post it right away but keep it somewhere (for instance in a static structure) for later use.

他的建议写法是:

class OuterClass {

  class InnerClass {
    private final WeakReference<OuterClass> mTarget;

    InnerClass(OuterClass target) {
           mTarget = newWeakReference<OuterClass>(target);
    }

    void doSomething() {
           OuterClass target = mTarget.get();
           if(target != null) {
                target.do();   
           }
     }
}

下面,我们进一步解释一下:

1.Android App启动的时候,Android Framework 为主线程创建一个Looper对象,这个Looper对象将贯穿这个App的整个生命周期,它实现了一个消息队列(MessageQueue),并且开启一个循环来处理Message对象。而Framework的主要事件都包含着内部Message对象,当这些事件被触发的时候,Message对象会被加到消息队列中执行。
2.当一个Handler被实例化时(如上面那样),它将和主线程Looper对象的消息队列相关联,被推到消息队列中的Message对象将持有一个Handler的引用以便于当Looper处理到这个Message的时候,Framework执行Handler的handleMessage(Message)方法。
3.在 Java 语言中,非静态匿名内部类将持有一个对外部类的隐式引用,而静态内部类则不会。

到底内存泄露是在哪里发生的呢?以下面代码为例:

public class SampleActivity extends Activity {

  private final Handler mLeakyHandler = newHandler() {
    @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(newRunnable() {
      @Override
      public void run() { }
    }, 60 * 10 * 1000);

    // Go back to the previous Activity.
    finish();
  }
}

当Activity被finish()掉,Message将存在于消息队列中长达10分钟的时间才会被执行到。这个Message持有一个对Handler的引用,Handler也会持有一个对于外部类(SampleActivity)的隐式引用,这些引用在Message被执行前将一直保持,这样会保证Activity的上下文不被垃圾回收机制回收,同时也会泄露应用程序的资源(views and resources)。

为解决这个问题,下面这段代码中的Handler则是一个静态匿名内部类。静态匿名内部类不会持有一个对外部类的隐式引用,因此Activity将不会被泄露。如果你需要在Handler中调用外部Activity的方法,就让Handler持有一个对Activity的WeakReference,这样就不会泄露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 = newWeakReference<SampleActivity>(activity);
    }

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

  private final MyHandler mHandler = newMyHandler(this);

  /**
   * Instances of anonymous classes do not hold an implicit
   * reference to their outer class when they are "static".
   */
  private static final Runnable sRunnable = newRunnable() {
       @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, 60 * 10 * 1000);

    // Go back to the previous Activity.
    finish();
  }
}

总结:
在实际开发中,如果内部类的生命周期和Activity的生命周期不一致(比如上面那种,Activity finish()之后要等10分钟,内部类的实例才会执行),则在Activity中要避免使用非静态的内部类,这种情况,就使用一个静态内部类,同时持有一个对Activity的WeakReference。

转载自:

http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2014/1106/1922.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值