Handler Class Should be Static or Leaks Occur

我们都知道在Android开发中容易引起内存泄露,特别是通过云测的log日志查看,所以我们一定要用过AndroidLint工具进行一些代码监测,避免内存泄露的发生,常见的内存泄露比如内部类持有外部类的应用,导致对象无法正常释放。下面在上网截取了两个不错的案例,特此记录。

一、Handler引起的内存泄露

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

public class SampleActivity extends Activity {
 
  private final Handler mLeakyHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
      // TODO
    }
  }
}

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

This Handler class should be static or leaks might occur

意思是说:这个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 = new WeakReference<OuterClass>(target); 
    } 
 
    void doSomething() { 
           OuterClass target = mTarget.get(); 
           if (target != null) {
                target.do();     
           }
     }
}

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

1.Android App启动的时候,Android Framework 为主线程创建一个Looper对象,这个Looper对象将贯穿这个App的整个生命周期,它实现了一个消息队列(Message  Queue),并且开启一个循环来处理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 = 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() { }
    }, 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 = 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, 60 * 10 * 1000);
     
    // Go back to the previous Activity.
    finish();
  }
}

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

转载出处:http://my.oschina.net/liucundong/blog/294127


二、内部类里的对象持有外部类的引用

public class Util {
 
    private Context mContext;
    private static Util sInstance;
    
    private Util(Context context) {
        this.mContext = context;
    }
    
    public static Util getInstance(Context context) {
        if (sInstance == null) {
            sInstance = new Util(context);
        }
        return sInstance;
    }
    
    //other methods
}

假设Activity  A 里使用Util类:

Util.getInstance(this);

代码大意就是这样,这样写的问题就是,在Activity A 里使用Util类,传入的context 是 actvitiy-context。试想一下,当Activity A 生命周期结束,但Util类里面却还存在A的引用 (mContext),这样Activity A占用的内存就一直不能回收,而A的对象也不会再被使用。本人写代码测试过,在A中调用了finish(),A的destroy()方法也被执行了,但其占用的内存,比如说,ImageView占用的内存,还是不能释放的。有兴趣的话,可以自己测试一下。

那么如何解决这个问题呢?在A中,可以用Util.getInstance(getApplicationContext()); 或Util.getInstance(getApplication()); 代替。

因为Application的生命周期是贯穿整个程序的,所以Util类持有它的引用,也不会造成内存泄露问题。


其实这个问题android官方文档的Resources目录下的一篇题为

Avoiding Memory Leaks

的文章已经提到过。


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

This Handler class should be static or leaks might occur

In summary, to avoid context-related memory leaks, remember the following:

  • Do not keep long-lived references to a context-activity (a reference to an activity should have the same life cycle as the activity itself)
  • Try using the context-application instead of a context-activity
  • Avoid non-static inner classes in an activity if you don't control their life cycle, use a static inner class and make a weak reference to the activity inside. The solution to this issue is to use a static inner class with a  to the outer class, as done in  and its W inner class for instance
  • A garbage collector is not an insurance against memory leaks。

摘自:http://xiechengfa.iteye.com/blog/1413688#bc2348883

两篇文章都不错,记录下。


This Handler class should be static or leaks might occur
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值