Android中使用Handler造成内存泄露的分析和解决

摘要  内存泄露,听起来很屌的样子?看了这篇文章,你会发现其实这个概念很简单。Android程序经常出现的一个现象是:内存占用越来越大,到最后用户不得不强行关闭程序来释放内存。导致内存越来越大的原因很多,其中最主要的原因之一,就是Handler使用不当导致的内存泄露。

Android Handler 内存泄露 Memory Leak


什么是内存泄露?
Java使用有向图机制,通过GC自动检查内存中的对象(什么时候检查由虚拟机决定),如果GC发现一个或一组对象为不可到达状态,则将该对象从内存中回收。也就是说,一个对象不被任何引用所指向,则该对象会在被GC发现的时候被回收;另外,如果一组对象中只包含互相的引用,而没有来自它们外部的引用(例如有两个对象A和B互相持有引用,但没有任何外部对象持有指向A或B的引用),这仍然属于不可到达,同样会被GC回收。

Android中使用Handler造成内存泄露的原因

1 Handler mHandler = new Handler() {
2     @Override
3     public void handleMessage(Message msg) {
4         mImageView.setImageBitmap(mBitmap);
5     }
6 }

上面是一段简单的Handler的使用。当使用内部类(包括匿名类)来创建Handler的时候,Handler对象会隐式地持有一个外部类对象(通常是一个Activity)的引用(不然你怎么可能通过Handler来操作Activity中的View?)。而Handler通常会伴随着一个耗时的后台线程(例如从网络拉取图片)一起出现,这个后台线程在任务执行完毕(例如图片下载完毕)之后,通过消息机制通知Handler,然后Handler把图片更新到界面。然而,如果用户在网络请求过程中关闭了Activity,正常情况下,Activity不再被使用,它就有可能在GC检查时被回收掉,但由于这时线程尚未执行完,而该线程持有Handler的引用(不然它怎么发消息给Handler?),这个Handler又持有Activity的引用,就导致该Activity无法被回收(即内存泄露),直到网络请求结束(例如图片下载完毕)。另外,如果你执行了Handler的postDelayed()方法,该方法会将你的Handler装入一个Message,并把这条Message推到MessageQueue中,那么在你设定的delay到达之前,会有一条MessageQueue -> Message -> Handler -> Activity的链,导致你的Activity被持有引用而无法被回收。

内存泄露的危害
只有一个,那就是虚拟机占用内存过高,导致OOM(内存溢出),程序出错。对于Android应用来说,就是你的用户打开一个Activity,使用完之后关闭它,内存泄露;又打开,又关闭,又泄露;几次之后,程序占用内存超过系统限制,FC。

使用Handler导致内存泄露的解决方法
方法一:通过程序逻辑来进行保护。
1.在关闭Activity的时候停掉你的后台线程。线程停掉了,就相当于切断了Handler和外部连接的线,Activity自然会在合适的时候被回收。 
2.如果你的Handler是被delay的Message持有了引用,那么使用相应的Handler的removeCallbacks()方法,把消息对象从消息队列移除就行了。 
方法二:将Handler声明为静态类。
静态类不持有外部类的对象,所以你的Activity可以随意被回收。代码如下:

1 static class MyHandler extends Handler {
2     @Override
3     public void handleMessage(Message msg) {
4         mImageView.setImageBitmap(mBitmap);
5     }
6 }

但其实没这么简单。使用了以上代码之后,你会发现,由于Handler不再持有外部类对象的引用,导致程序不允许你在Handler中操作Activity中的对象了。所以你需要在Handler中增加一个对Activity的弱引用(WeakReference):

01 static class MyHandler extends Handler {
02     WeakReference<Activity > mActivityReference;
03  
04     MyHandler(Activity activity) {
05         mActivityReference= new WeakReference<Activity>(activity);
06     }
07  
08     @Override
09     public void handleMessage(Message msg) {
10         final Activity activity = mActivityReference.get();
11         if (activity != null) {
12             mImageView.setImageBitmap(mBitmap);
13         }
14     }
15 }

将代码改为以上形式之后,就算完成了。

延伸:什么是WeakReference?
WeakReference弱引用,与强引用(即我们常说的引用)相对,它的特点是,GC在回收时会忽略掉弱引用,即就算有弱引用指向某对象,但只要该对象没有被强引用指向(实际上多数时候还要求没有软引用,但此处软引用的概念可以忽略),该对象就会在被GC检查到时回收掉。对于上面的代码,用户在关闭Activity之后,就算后台线程还没结束,但由于仅有一条来自Handler的弱引用指向Activity,所以GC仍然会在检查的时候把Activity回收掉。这样,内存泄露的问题就不会出现了。


来源:泡在网上的日子

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

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

1
2
3
4
5
6
7
8
9
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. 


他的建议写法是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 语言中,非静态匿名内部类将持有一个对外部类的隐式引用,而静态内部类则不会。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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的上下文了,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值