原文地址:http://johnnyshieh.github.io/android/2015/09/03/android-handler-memory-leak/

在Android中我们经常用Handler来处理异步消息,把非主线程的操作结果放在主线程中更新UI。请思考一下下面的代码有没有问题:

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, Messages enqueued on the application thread’s MessageQueue also retain their target Handler. If the Handler is an inner class, its outer class will be retained as well. To avoid leaking the outer class, declare the Handler as a static nested class with a WeakReference to its outer class

但是什么东西泄漏了,内存泄漏怎么发生的我们还是不清楚。接下来我们慢慢分析泄漏的原因:

  1. 当一个Android应用启动时,框架会分配到一个Looper实例给应用的主线程。这个Looper的主要工作就是处理一个接着一个的消息对象。在Android中,所有Android框架的事件(比如Activity的生命周期方法的调用和按钮的点击等)都是放到消息中,然后加入到Looper要处理的消息队列中,由Looper依次处理。主线程的Looper的生命周期和应用的一样长。

  2. 当在主线程中初始化一个Handler时,它就会关联到Looper的消息队列。发送到消息队列的消息本身就持有Handler的引用,只有这样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); // 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();
}
}

注意上面的匿名Runnable类也隐式地持有SampleActivity的引用,这也会阻止context被回收。当Activity是finished的时候,被延迟的消息在被处理之前依然会存活在主线程的消息队列中10分钟。这个消息持有Activity的Handler的引用,而Handler隐式地持有其外部类(SampleActivity)的引用。这些引用会一直保持直到消息被处理完成,而这会导致Activity无法被垃圾回收器回收并且泄漏应用的所有资源,因此就产生了内存泄漏。

要解决这个问题,在继承Handler的时候要么放在一个单独的类文件中,要么用静态内部类替代。因为静态内部类不会持有其外部类的隐式引用,所以不会产生contex泄漏。如果要在Handler中调用外部Activity的方法,可以持有Activity的弱引用。同样的道理,要解决匿名Runanable类引起的内存泄漏,只需要把它设为静态的成员属性(静态的匿名内部类不会持有其外部类的隐式引用)。

修改后的代码如下:

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();
}
}

原文链接:How to Leak a Context: Handlers & Inner Classes 作者:Alex Lockwood静态内部类和非静态内部类的区别是我们每一个Android开发人员应该掌握的。当内部类的实例的生命周期可能大于Activity的生命周期时,避免使用非静态的内部类。个人更倾向于用静态内部类或持有Activity的弱引用。

Android中由Handler和内部类引起的内存泄漏的更多相关文章

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

    什么是内存泄露?Java使用有向图机制,通过GC自动检查内存中的对象(什么时候检查由虚拟机决定),如果GC发现一个或一组对象为不可到达状态,则将该对象从内存中回收.也就是说,一个对象不被任何引用所指向 ...

  2. Android中使用Handler造成内存泄露

    1.什么是内存泄露? Java使用有向图机制,通过GC自动检查内存中的对象(什么时候检查由虚拟机决定),如果GC发现一个或一组对象为不可到达状态,则将该对象从内存中回收.也就是说,一个对象不被任何引用 ...

  3. Android中的Handler的机制与用法详解

    概述: 很多android初学者对android 中的handler不是很明白,其实Google参考了Windows的消息处理机制, 在Android系统中实现了一套类似的消息处理机制.在下面介绍ha ...

  4. android中的Handler和Runnable

    最近在做一个项目,在网络请求时考虑用Handler进行处理,然后就研究了一下Handler和Runnable 首先在看一下java中的Runnable The Runnable interface s ...

  5. Android中利用Handler实现消息的分发机制(三)

    在第二篇文章<Android中利用Handler实现消息的分发机制(一)>中,我们讲到主线程的Looper是Android系统在启动App的时候,已经帮我们创建好了,而假设在子线程中须要去 ...

  6. 转:Android中的Handler的机制与用法详解

    注:Message类的用法: message的几个参数都可以携带数据,其中arg1与arg2可以携带int类型,what是用户自定义的int型,这样接受者可以了解这个消息的信息. 说明:使用Messa ...

  7. 深入源代码解析Android中的Handler,Message,MessageQueue,Looper

    本文主要是对Handler和消息循环的实现原理进行源代码分析.假设不熟悉Handler能够參见博文< Android中Handler的使用>,里面对Android为何以引入Handler机 ...

  8. Android中的Handler及它所引出的Looper、MessageQueue、Message

    0.引入 0.1.线程间通信的目的 首先,线程间通信要交流些什么呢? 解答这个问题要从为什么要有多线程开始,需要多线程的原因大概有这些 最早也最基本:有的任务需要大量的时间,但其实并不占用计算资源,比 ...

  9. Android中的Handler,以及用Handler延迟执行

    项目中遇到一个情况,先生成文件再上传:但有时候发出指令后上传会不成功,需要再发一次指令方能上传. 猜想是由于文件还没生成就执行「上传」指令了.想到要延时.Android中单纯用currentThrea ...

随机推荐

  1. 二叉树系列 - 二叉树的深度,例 [LeetCode]

    二叉树的深度的概念最值得注意的地方,在于 到"叶子"节点的距离. 一般来说,如果直接说“深度”,都是指最大深度,即最远叶子的距离. 这里放两道例题,最小深度和最大深度. 1. 二叉 ...

  2. LightOJ 1065 - Number Sequence 矩阵快速幂水题

    http://www.lightoj.com/volume_showproblem.php?problem=1065 题意:给出递推式f(0) = a, f(1) = b, f(n) = f(n - ...

  3. JAVA多线程提高二:传统线程的互斥与同步&传统线程通信机制

    本文主要是回顾线程之间互斥和同步,以及线程之间通信,在最开始没有juc并发包情况下,如何实现的,也就是我们传统的方式如何来实现的,回顾知识是为了后面的提高作准备. 一.线程的互斥 为什么会有线程的互斥 ...

  4. SSM框架整合遇到的问题

    1.Maven中Dubbo集成spring2.5以上版本 项目中dubbo集成spring4.x,配置pom时需要注意排除spring的依赖,我这里用的是tomcat,所以把jboss也排除了: &l ...

  5. [php]HTTP协议头解析

    (Request-Line) GET /static/imgs/bqpd.jpg HTTP/1.1 Accept image/png, image/svg+xml, image/*;q=0.8, */ ...

  6. array_unshift() 函数

    出处:http://www.w3school.com.cn/php/func_array_unshift.asp

  7. 小程序Openid 获取,服务器 encryptedData 解密 遇到的坑

    获取客户 openId 和 unionId 需要以下步骤(都为必须步骤) 1.从验证从客户端传上来code, 获取sessionKey (需要配合小程序appid ,secret 发送到微信服务器) ...

  8. 浅谈卡特兰数(Catalan number)的原理和相关应用

    一.卡特兰数(Catalan number) 1.定义 组合数学中一个常出现在各种计数问题中出现的数列(用c表示).以比利时的数学家欧仁·查理·卡特兰的名字来命名: 2.计算公式 (1)递推公式 c[ ...

  9. 信息收集之zoomeye

    一.浏览器上使用api接口 1.https://api.zoomeye.org/user/login post传参:{"username" : "username&quo ...

  10. PHP文本式留言板——php经典实例

    文件结构: index.php 主页和添加页 show.php 查看留言页 ly.db 文本保存页 doAdd.php 添加功能页 doDel.php 删除功能页  index.php <htm ...