[译]Handlers和内部类如何造成Context泄露
2015-09-17 18:49
363 查看
How to Leak a Context: Handlers & Inner Classes
看一下下面代码:
虽然看起来没有很明显的问题,但是这段代码却存在着会导致严重的内存泄露的风险。Android Lint会给出以下提示:
但是,究竟泄露会发生在哪里还有它是怎样可能会发生的?下面让我们一起来通过我们所知道的知识来确定问题的源头:
1. 当一个安卓应用第一次启动的时候,安卓框架会给APP的主线程创建一个Looper对象。Looper继承了一个简单的在一个圆环中一个接一个地处理消息对象的消息队列。所有的应用层框架事件(例如Activity的生命周期方法的调用,按钮点击事件等等)都是包含在消息对象的范围内,都会添加到Looper的消息队列中并且一个接一个地被处理掉。主线程的Looper通过应用的生命周期的存在而得以存在。
2. 当一个Handler在主线程被初始化的时候,它和Looper的消息队列关联着。那么被发送到消息队列的消息会对Handler持有一个引用以便当Looper最终会处理这个消息的时候,框架可以调用Handler的handleMessage(Message message)方法。
3. 在Java里面,非静态的匿名类会对他们的外部类持有一个隐式的引用。相反,静态的内部内则不会。
那么内存泄露究竟发生在哪里?这是非常微妙的(感觉翻译得很不微妙),但是请再看一下以下代码作为例子:
当这个Activity被结束掉的时候,这个延迟的消息会在它被Looper处理掉之前继续在主线程的消息队列里面继续存活十分钟。而这个时候,消息对象持有着这个Activity的Handler一个引用,而这个Handler就对它的外部类(这里就是指SampleActivity)持有着一个隐式引用。这个引用会一直存在直到消息对象被处理掉,从而导致这个Activity的Context不能被GC进行垃圾回收并且泄露所有App的资源。记住,匿名Runnable类那一行也是如此。非静态的匿名内部类会对他们的外部类持有一个隐式引用,从而导致Contexxt会造成泄露。
要想解决这个问题,可以在一个新文件里面继承Handler或者使用一个静态的内部类进行替代。静态的内部类不会对它们的外部类持有一个隐式引用,所以Activity不会造成泄露。如果你需要在内部类里面调用外部类的方法,你可以让这个Handler对Activity持有一个弱引用,那么你就不会意外地泄露一个Context了。为了解决当我们初始化一个匿名Runnable类的时候发生的内存泄露,我们创建了这个类的静态成员变量(因为静态的内部类对象不会对他们的外部类持有引用):
静态和非静态的内部类的区别是很微妙的(我还是感觉不出来哪里微妙了),但这是每一个安卓开发者都应该理解的一些东西。那么重点是什么呢?(还以为要翻译成底线哩) 那就是,在一个Activity里面如果内部类对象可以在Activity生命周期外存在,那么就应该避免使用非静态的内部类。相反,更加推荐使用一个静态内部类并且在其内部对Activity保持一个弱引用。
看一下下面代码:
public class SampleActivity extends Activity { private final Handler mLeakyHandler = new Handler() { @Override public void handleMessage(Message msg) { // ... } } }
虽然看起来没有很明显的问题,但是这段代码却存在着会导致严重的内存泄露的风险。Android Lint会给出以下提示:
In Android, Handler classes should be static or leaks might occur.
但是,究竟泄露会发生在哪里还有它是怎样可能会发生的?下面让我们一起来通过我们所知道的知识来确定问题的源头:
1. 当一个安卓应用第一次启动的时候,安卓框架会给APP的主线程创建一个Looper对象。Looper继承了一个简单的在一个圆环中一个接一个地处理消息对象的消息队列。所有的应用层框架事件(例如Activity的生命周期方法的调用,按钮点击事件等等)都是包含在消息对象的范围内,都会添加到Looper的消息队列中并且一个接一个地被处理掉。主线程的Looper通过应用的生命周期的存在而得以存在。
2. 当一个Handler在主线程被初始化的时候,它和Looper的消息队列关联着。那么被发送到消息队列的消息会对Handler持有一个引用以便当Looper最终会处理这个消息的时候,框架可以调用Handler的handleMessage(Message 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() { /* ... */ } }, 1000 * 60 * 10); // Go back to the previous Activity. finish(); } }
当这个Activity被结束掉的时候,这个延迟的消息会在它被Looper处理掉之前继续在主线程的消息队列里面继续存活十分钟。而这个时候,消息对象持有着这个Activity的Handler一个引用,而这个Handler就对它的外部类(这里就是指SampleActivity)持有着一个隐式引用。这个引用会一直存在直到消息对象被处理掉,从而导致这个Activity的Context不能被GC进行垃圾回收并且泄露所有App的资源。记住,匿名Runnable类那一行也是如此。非静态的匿名内部类会对他们的外部类持有一个隐式引用,从而导致Contexxt会造成泄露。
要想解决这个问题,可以在一个新文件里面继承Handler或者使用一个静态的内部类进行替代。静态的内部类不会对它们的外部类持有一个隐式引用,所以Activity不会造成泄露。如果你需要在内部类里面调用外部类的方法,你可以让这个Handler对Activity持有一个弱引用,那么你就不会意外地泄露一个Context了。为了解决当我们初始化一个匿名Runnable类的时候发生的内存泄露,我们创建了这个类的静态成员变量(因为静态的内部类对象不会对他们的外部类持有引用):
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, 1000 * 60 * 10); // Go back to the previous Activity. finish(); } }
静态和非静态的内部类的区别是很微妙的(我还是感觉不出来哪里微妙了),但这是每一个安卓开发者都应该理解的一些东西。那么重点是什么呢?(还以为要翻译成底线哩) 那就是,在一个Activity里面如果内部类对象可以在Activity生命周期外存在,那么就应该避免使用非静态的内部类。相反,更加推荐使用一个静态内部类并且在其内部对Activity保持一个弱引用。
相关文章推荐
- CppUtest发现的STL容器内存泄漏问题
- 应该避免的Android内存泄露的种种原因,你知道吗?(持续更新学习中)
- Android内存管理及Memory Leak、OOM分析
- Android - 内存泄漏处理
- 内存泄漏分析文章参考
- 启动tomcat8提示内存泄漏问题解决
- Tomcat MemoryLeak 问题
- iOS MKMapView Memory Leak
- iOS中objecive-c语言和android中java语言的区别
- 麻省理工告诉你男女配对的真相
- html5屏幕旋转事件 onorientationchange
- Android自由选择TextView的文字
- android ui分析 - 今日头条UI整体结构
- HDU 5015 233 Matrix
- 黑马程序员 java 设计模式之 工厂方法
- 深入理解Java的接口和抽象类
- FMDB实战
- html5实现微信摇一摇功能
- protobuf相关的操作函数
- ORACLE触发器详解