context是如何泄漏的 - Handlers和内部类

本人翻译, 略有改动, 原文地址如下:

http://www.androiddesignpatterns.com/2013/01/inner-class-handler-memory-leak.html

 

考虑如下代码:

 

Java代码   收藏代码
  1. public class SampleActivity extends Activity {   
  2.     private final Handler mLeakyHandler = new Handler() {  
  3.         @Override  
  4.         public void handleMessage(Message msg) {  
  5.           /* ... */  
  6.         }  
  7.     }  
  8. }  

 

你可能看不出来这段代码会造成内存泄漏, 确实, 它不那么容易被发现. 如果你运行Android的lint工具, 它会给你一个警告, 提示你把handler定义成静态的(static), 否则可能造成内存泄漏. 但内存泄漏是怎么发生的呢?

 

首先, 我们应该知道如下几点:

1: 当一个android程序启动时, 框架层(framework)会为程序的主线程创建一个Looper对象. 该对象实现了一个简单的消息队列, 循环不断的处理队列上的消息对象(Message), 主线程上的Looper对象在程序的整个生命周期中一直存在.

2: 当一个Handler在主线程上被实例化时, 它就与Looper的消息队列关联到一起了. 队列上的Message对象持有一个handler的引用, 这使得Looper处理到某一个Message时, 能够调用handler.handleMessage()方法.

3: 在java里, 非静态内部类和匿名内部类持有一个隐式的外部类引用. 相反, 静态的内部类就没有该隐式引用.

 

那么, 泄露在哪里发生呢? 这个有点微妙, 考虑下面一个例子:

Java代码   收藏代码
  1. public class SampleActivity extends Activity {  
  2.    
  3.   private final Handler mLeakyHandler = new Handler() {  
  4.     @Override  
  5.     public void handleMessage(Message msg) {  
  6.       /* ... */  
  7.     }  
  8.   }  
  9.    
  10.   @Override  
  11.   protected void onCreate(Bundle savedInstanceState) {  
  12.     super.onCreate(savedInstanceState);  
  13.    
  14.     // Post a message and delay its execution for 10 minutes.  
  15.     mLeakyHandler.postDelayed(new Runnable() {  
  16.       public void run() { }  
  17.     }, 600000);  
  18.        
  19.     // Go back to the previous Activity.  
  20.     finish();  
  21.   }  
  22. }  
  23.    

 

当Activityfinish后, 我们发出的那个"延迟处理消息"将在主线程的消息队列中保持10分钟, 直到该消息最终被处理. 由于消息持有一个handler的引用, 而handler又持有一个它的外部类-SampleActivity的引用, 这样就阻止了activity的context被垃圾回收, 从而泄漏了Activty引用的所有的应用资源. 注意上述例子中的匿名的Runnable对象也一样造成了context的泄露.

 

要避免这个问题, 就需要将Handler改为静态内部类. 如果你需要在Handler中调用Activity外部类的方法, 你可以在handler中使用一个WeakReference来持有activity对象.

(注意我们将Handler和Runnable都定义成了static的)

Java代码   收藏代码
  1. public class SampleActivity extends Activity {  
  2.   private static class MyHandler extends Handler {  
  3.     private final WeakReference<SampleActivity> mActivity;  
  4.    
  5.     public MyHandler(SampleActivity activity) {  
  6.       mActivity = new WeakReference<SampleActivity>(activity);  
  7.     }  
  8.    
  9.     @Override  
  10.     public void handleMessage(Message msg) {  
  11.       SampleActivity activity = mActivity.get();  
  12.       if (activity != null) {  
  13.         /* ... */  
  14.       }  
  15.     }  
  16.   }  
  17.    
  18.   private final MyHandler mHandler = new MyHandler(this);  
  19.    
  20.   // Instances of anonymous classes do not hold an implicit  
  21.   // reference to their outer class when they are "static".  
  22.   private final static Runnable sRunnable = new Runnable() {  
  23.       public void run() { }  
  24.   };  
  25.    
  26.   @Override  
  27.   protected void onCreate(Bundle savedInstanceState) {  
  28.     super.onCreate(savedInstanceState);  
  29.    
  30.     // Post a message and delay its execution for 10 minutes.  
  31.     mHandler.postDelayed(sRunnable, 600000);  
  32.        
  33.     // Go back to the previous Activity.  
  34.     finish();  
  35.   }  
  36. }  

 

 

结论: 在Activity中使用非静态的内部类时, 尽量避免内部类生命周期超出了Activity之外. 类似的例子还有AsyncTask.

=================================

补充例子:

如果你看过PendingIntent的源代码, 你会看到它有一些send(Handler...)的方法, 如果某个Activity调用了PendingIntent.send(...), 并且传入一个非静态的内部Handler类, 当activity被销毁后, 内部类仍然持有它的引用, 导致它无法被垃圾收集.

当然, 如果你没有像这样不当的发布一个Handler到其它的类, 你就不用担心泄露发生.

=================================​

如果你不想每次都创建一个WeakReference, 可以先创建这样一个通用类:

 

Java代码   收藏代码
  1. public abstract class WeakReferenceHandler<T> extends Handler {  
  2.     private WeakReference<T> mReference;  
  3.    
  4.     public WeakReferenceHandler(T reference) {  
  5.         mReference = new WeakReference<T>(reference);  
  6.     }  
  7.    
  8.     @Override  
  9.     public void handleMessage(Message msg) {  
  10.         if (mReference.get() == null)  
  11.             return;  
  12.         handleMessage(mReference.get(), msg);  
  13.     }  
  14.    
  15.     protected abstract void handleMessage(T reference, Message msg);  
  16. }  
  17.    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值