[转]How to Leak a Context: Handlers & Inner Classes
Consider the following code:
public class SampleActivity extends Activity {
private final Handler mLeakyHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// ...
}
}
}
While not readily obvious, this code can cause cause a massive memory leak. Android Lint will give the following warning:
In Android, Handler classes should be static or leaks might occur.
But where exactly is the leak and how might it happen? Let's determine the source of the problem by first documenting what we know:
When an Android application first starts, the framework creates a
Looperobject for the application's main thread. ALooperimplements a simple message queue, processingMessageobjects in a loop one after another. All major application framework events (such as Activity lifecycle method calls, button clicks, etc.) are contained insideMessageobjects, which are added to theLooper's message queue and are processed one-by-one. The main thread'sLooperexists throughout the application's lifecycle.When a
Handleris instantiated on the main thread, it is associated with theLooper's message queue. Messages posted to the message queue will hold a reference to theHandlerso that the framework can callHandler#handleMessage(Message)when theLoopereventually processes the message.In Java, non-static inner and anonymous classes hold an implicit reference to their outer class. Static inner classes, on the other hand, do not.
So where exactly is the memory leak? It's very subtle, but consider the following code as an example:
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();
}
}
When the activity is finished, the delayed message will continue to live in the main thread's message queue for 10 minutes before it is processed. The message holds a reference to the activity's Handler, and the Handler holds an implicit reference to its outer class (theSampleActivity, in this case). This reference will persist until the message is processed, thus preventing the activity context from being garbage collected and leaking all of the application's resources. Note that the same is true with the anonymous Runnable class on line 15. Non-static instances of anonymous classes hold an implicit reference to their outer class, so the context will be leaked.
To fix the problem, subclass the Handler in a new file or use a static inner class instead. Static inner classes do not hold an implicit reference to their outer class, so the activity will not be leaked. If you need to invoke the outer activity's methods from within the Handler, have the Handler hold a WeakReference to the activity so you don't accidentally leak a context. To fix the memory leak that occurs when we instantiate the anonymous Runnable class, we make the variable a static field of the class (since static instances of anonymous classes do not hold an implicit reference to their outer class):
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();
}
}
The difference between static and non-static inner classes is subtle, but is something every Android developer should understand. What's the bottom line? Avoid using non-static inner classes in an activity if instances of the inner class outlive the activity's lifecycle. Instead, prefer static inner classes and hold a weak reference to the activity inside.
As always, leave a comment if you have any questions and don't forget to +1 this blog in the top right corner! :)
【译】什么导致了Context泄露:Handler&内部类
思考下面代码
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、当一个Android应用程序第一次启动时,Android框架为应用程序的主线程创建一个Looper对象。一个Looper实现了一个简单的消息队列,在一个循环中处理Message对象。所有主要的应用程序框架事件(如活动生命周期方法调用,单击按钮,等等)都包含在Message对象,它被添加到Looper的消息队列然后一个个被处理。主线程的Looper在应用程序的整个生命周期中存在。
2、当一个Handle在主线程被实例化,它就被关联到Looper的消息队列。被发送到消息队列的消息会持有一个Handler的引用,以便Android框架可以在Looper最终处理这个消息的时候,调用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);
// 延时10分钟发送一个消息
mLeakyHandler.postDelayed(new Runnable() {
@Override
public void run() { }
}, 60 * 10 * 1000);
// 返回前一个Activity
finish();
}
}
当这个Activity被finished后,延时发送的消息会继续在主线程的消息队列中存活10分钟,直到他们被处理。这个消息持有这个Activity的Handler引用,这个Handler有隐式地持有他的外部类(在这个例子中是SampleActivity)。直到消息被处理前,这个引用都不会被释放。因此Activity不会被垃圾回收机制回收,泄露他所持有的应用程序资源。注意,第15行的匿名Runnable类也一样。匿名类的非静态实例持有一个隐式的外部类引用,因此context将被泄露。
为了解决这个问题,Handler的子类应该定义在一个新文件中或使用静态内部类。静态内部类不会隐式持有外部类的引用。所以不会导致它的Activity泄露。如果你需要在Handle内部调用外部Activity的方法,那么让Handler持有一个Activity的弱引用(WeakReference)以便你不会意外导致context泄露。为了解决我们实例化匿名Runnable类可能导致的内存泄露,我们将用一个静态变量来引用他(因为匿名类的静态实例不会隐式持有他们外部类的引用)。
public class SampleActivity extends Activity {
/**
* 匿名类的静态实例不会隐式持有他们外部类的引用
*/
private static final Runnable sRunnable = new Runnable() {
@Override
public void run() {
}
};
private final MyHandler mHandler = new MyHandler(this);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 延时10分钟发送一个消息.
mHandler.postDelayed(sRunnable, 60 * 10 * 1000);
// 返回前一个Activity
finish();
}
/**
* 静态内部类的实例不会隐式持有他们外部类的引用。
*/
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) {
// ...
}
}
}
}
静态和非静态内部类的区别是比较难懂的,但每一个Android开发人员都应该了解。开发中不能碰的雷区是什么?不在一个Activity中使用非静态内部类, 以防它的生命周期比Activity长。相反,尽量使用持有Activity弱引用的静态内部类。
[转]How to Leak a Context: Handlers & Inner Classes的更多相关文章
- 内存泄露了么: Handlers & Inner Classes
看到一篇关于handler和匿名类关于内存泄露的文章,觉得不错,充分发挥拿来主义,先放这儿看着! From:http://www.androiddesignpatterns.com/2013/01/i ...
- Android内存管理(2)HUNTING YOUR LEAKS: MEMORY MANAGEMENT IN ANDROID PART 2
from: http://www.raizlabs.com/dev/2014/04/hunting-your-leaks-memory-management-in-android-part-2-of- ...
- Android基础夯实--你了解Handler有多少?
概述 对于刚入门的同学来说,往往都会对Handler比较迷茫,到底Handler是个什么样的东西.当然,可能对于一些有工作经验的工程师来说,他们也不一定能很准确地描述,我们来看下API的介绍. Han ...
- JVM内存管理概述与android内存泄露分析
一.内存划分 将内存划分为六大部分,分别是PC寄存器.JAVA虚拟机栈.JAVA堆.方法区.运行时常量池以及本地方法栈. 1.PC寄存器(线程独有):全称是程序计数寄存器,它记载着每一个线程当前运行的 ...
- Android 内存泄漏总结(转)
Android 内存泄漏总结 内存管理的目的就是让我们在开发中怎么有效的避免我们的应用出现内存泄漏的问题.内存泄漏大家都不陌生了,简单粗俗的讲,就是该被释放的对象没有释放,一直被某个或某些实例所持有却 ...
- Android中Handler引起的内存泄露
在Android常用编程中,Handler在进行异步操作并处理返回结果时经常被使用.通常我们的代码会这样实现. 1 2 3 4 5 6 7 8 9 public class SampleActivit ...
- Android中由Handler和内部类引起的内存泄漏
原文地址:http://johnnyshieh.github.io/android/2015/09/03/android-handler-memory-leak/ 在Android中我们经常用Hand ...
- 【翻译】Android避免内存泄露(Activity的context 与Context.getApplicationContext)
原谅地址:http://android-developers.blogspot.com/2009/01/avoiding-memory-leaks.html ,英文原文在翻译之后 Android 应用 ...
- Investigating Your RAM Usage
转载自:http://developer.android.com/intl/zh-cn/tools/debugging/debugging-memory.html Because Android is ...
随机推荐
- 从零开始制作H5人脸融合小游戏
去年的建军节,一个展示军装照的H5人脸融合游戏火遍朋友圈,带来很好的传播效果.最近欧冠决赛要来了,公司决定做一个寻找和你最像的欧冠球星的H5游戏,那么该怎么做呢?认真分析了一下,这个游戏其实用到的技术 ...
- Expression Blend实例中文教程(4) - 布局控件快速入门Canvas
上一篇,我介绍了Silverlight控件被分为三种类型, 第一类: Layout Controls(布局控件) 第二类: Item Controls (项目控件) 第三类: User Interac ...
- JavaScript中 运算符
运算符对一个或多个变量或值(操作数)进行运算,并返回一个新值 根据所执行的运算,运算符可分为以下类别: (1) 算术运算符 (2) 比较运算符 运算符 说 明 示 例 == 等于. 如果两个操作数相 ...
- git 命令记录贴
记录下最近使用git的场景. 问题 1:将一个完整的项目发布到已创建好的git地址(码云) 执行步奏: 1.配置自己的公钥 2.检查是否连接成功 $ ssh -T git@git.oschina.ne ...
- C Primer Plus note2
warning: 'mmin' is used uninitialized in this function [-Wuninitialized]| 编译器出现如上图的警告,是因为变量‘mmin’没有初 ...
- [翻译]Review——The Inner Workings Of Virtual DOM
The Inner Workings Of Virtual DOM 虚拟DOM的内部工作机制 原文地址:https://medium.com/@rajaraodv/the-inner-workings ...
- 使用QQ第三方登录 并在父页面跳转刷新
<html> <head> <title>QQ登录跳转</title> <script src="http://lib.sinaapp. ...
- csharp:Microsoft.Ink 手写识别(HandWriting Recognition)
/* 下載: //Microsoft Windows XP Tablet PC Edition 2005 Recognizer Pack http://www.microsoft.com/zh-cn/ ...
- Django 模型层之多表操作
一.创建模型 实例: 作者表: 拥有字段:姓名(name),性别(sex),该表与书籍表之间为多对多的关系 作者详情表: 拥有字段:地址(addr),手机号(phone),该表与作者表之间为一对一的关 ...
- SQL Server中的游标CURSOR
游标是邪恶的! 在关系数据库中,我们对于查询的思考是面向集合的.而游标打破了这一规则,游标使得我们思考方式变为逐行进行.对于类C的开发人员来着,这样的思考方式会更加舒服. 正常面向集合的思维方式是: ...