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

小 z 我今天去面试,一下子被这个平时一直在用的东西难住了,比较丢人。

该文章为转发:http://hchaojie.iteye.com/blog/1774772

博客分类: Java
android

.



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

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.  

猜你喜欢

转载自zjingye.iteye.com/blog/1882933