在使用Handler更新UI的时候。我是这样写的:

public class SampleActivity extends Activity {

  private final Handler mLeakyHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// TODO
}
}
}

看起来非常正常的,可是 Android Lint 却给出了警告:

This Handler class should be static or leaks might occur

意思是说:这个Handler 必须是static的。否则就会引发内存泄露。

事实上。对于这个问题,Android Framework 的project师 Romain Guy 早已经在Google论坛上做出过解释,而且给出了他的建议写法:

I wrote that debugging code because of a couple of memory leaks I

found in the Android codebase. Like you said, a Message has a

reference to the Handler which, when it’s inner and non-static, has a

reference to the outer this (an Activity for instance.) If the Message

lives in the queue for a long time, which happens fairly easily when

posting a delayed message for instance, you keep a reference to the

Activity and “leak” all the views and resources. It gets even worse

when you obtain a Message and don’t post it right away but keep it

somewhere (for instance in a static structure) for later use.

他的建议写法是:

class OuterClass {

  class InnerClass {
private final WeakReference<OuterClass> mTarget; InnerClass(OuterClass target) {
mTarget = new WeakReference<OuterClass>(target);
} void doSomething() {
OuterClass target = mTarget.get();
if (target != null) {
target.do();
}
}
}

以下,我们进一步解释一下:

1.Android App启动的时候,Android Framework 为主线程创建一个Looper对象。这个Looper对象将贯穿这个App的整个生命周期,它实现了一个消息队列(Message Queue),而且开启一个循环来处理Message对象。而Framework的主要事件都包括着内部Message对象,当这些事件被触发的时候,Message对象会被加到消息队列中运行。

2.当一个Handler被实例化时(如上面那样),它将和主线程Looper对象的消息队列相关联,被推到消息队列中的Message对象将持有一个Handler的引用以便于当Looper处理到这个Message的时候。Framework运行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() { }
}, 60 * 10 * 1000); // Go back to the previous Activity.
finish();
}
}

当Activity被finish()掉。Message 将存在于消息队列中长达10分钟的时间才会被运行到。

这个Message持有一个对Handler的引用,Handler也会持有一个对于外部类(SampleActivity)的隐式引用,这些引用在Message被运行前将一直保持。这样会保证Activity的上下文不被垃圾回收机制回收,同一时候也会泄露应用程序的资源(views and resources)。

为解决问题,以下这段代码中的Handler则是一个静态匿名内部类。静态匿名内部类不会持有一个对外部类的隐式引用,因此Activity将不会被泄露。假设你须要在Handler中调用外部Activity的方法,就让Handler持有一个对Activity的WeakReference,这样就不会泄露Activity的上下文了,例如以下所看到的:

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

总结:

在实际开发中。假设内部类的生命周期和Activity的生命周期不一致(比方上面那种,Activity finish()之后要等10分钟,内部类的实例才会运行),则在Activity中要避免使用非静态的内部类,这样的情况,就使用一个静态内部类。同一时候持有一个对Activity的WeakReference。

关于handler内存泄露的问题的更多相关文章

  1. 小题大做 | Handler内存泄露全面分析

    前言 嗨,大家好,问大家一个"简单"的问题: Handler内存泄露的原因是什么? 你会怎么答呢? 这是错误的回答 有的朋友看到这个题表示,就这?太简单了吧. "内部类持 ...

  2. handler内存泄露

    原因: 在Activity中新建一个Handler后,Handler执行计时操作,如果Activity销毁,Handler是不会主动销毁的,而且会占用Activity的空间,不使其回收,积累久了就会内 ...

  3. Android handler 内存泄露分析及解决方法

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

  4. Android性能优化:手把手带你全面了解 内存泄露 & 解决方案

    . 简介 即 ML (Memory Leak)指 程序在申请内存后,当该内存不需再使用 但 却无法被释放 & 归还给 程序的现象2. 对应用程序的影响 容易使得应用程序发生内存溢出,即 OOM ...

  5. Android 内存泄露总结(附内存检测工具)

    https://segmentfault.com/a/1190000006852540 主要是分三块: 静态储存区:编译时就分配好,在程序整个运行期间都存在.它主要存放静态数据和常量. 栈区:当方法执 ...

  6. Android 常见内存泄露 & 解决方案

    前言 内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃 (OOM) 等严重后果. 那什么情况下不能被 ...

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

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

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

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

  9. Android 中 Handler 引起的内存泄露

    在Android常用编程中,Handler在进行异步操作并处理返回结果时经常被使用.其实这可能导致内存泄露,代码中哪里可能导致内存泄露,又是如何导致内存泄露的呢?那我们就慢慢分析一下.http://w ...

随机推荐

  1. VS2017、netcore版本更新升级

    VS2017 剩下的就是下一步了. netcore 访问:https://www.microsoft.com/net/download/archives 找到对应版本(最新版本) 下载安装就可以了 装 ...

  2. springboot学习(九) 使用mybatis访问数据库

    1.添加maven依赖 <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId ...

  3. Linux 性能监控 —— Load Average

    一. 简单介绍 top. uptime. cat /proc/loadavg 命令中 Load average: 4.90, 5.51, 5.77        整体含义: 正在执行的任务数量 + 排 ...

  4. Centos 7 部署FTP服务简单版

    第三方教程推荐与参考: http://blog.csdn.net/somehow1002/article/details/70232791 先安装成功了,有信心了.再进一步扩展配置. 1.安装vsft ...

  5. Spring 读取配置文件(二)

    Spring 读取配置文件并调用 bean package cn.com.test.receive; import org.springframework.beans.factory.annotati ...

  6. cmake工程使用distcc

    distcc可以加速编译,但是遇到cmake可能就需要处理下. 问题 distcc在 /usr/lib/distcc 中放了各编译器的soft link(如cc/gcc等等),如果 /usr/lib/ ...

  7. file、inode在应用层和驱动层之间的联系_转

    转自:http://blog.csdn.net/dreaming_my_dreams/article/details/8272586 应用层和驱动的衔接,一直是一个老大难问题,若弄不清楚,总觉得驱动写 ...

  8. 密码学hash函数-SHA256-512

    [latexpage] Hash函数又称哈希函数.散列函数.杂凑函数.它是一种单向密码体制,即从一个从明文到密文的不可逆映射,只有加密过程,没有解密过程. Hash函数H将可变长度的数据块M作为输入, ...

  9. Harmonic Number 求Hn; Hn = 1 + 1/2 + 1/3 + ... + 1/n; (n<=1e8) T<=1e4; 精确到1e-8; 打表或者调和级数

    /** 题目:Harmonic Number 链接:https://vjudge.net/contest/154246#problem/I 题意:求Hn: Hn = 1 + 1/2 + 1/3 + . ...

  10. 解决pip安装模块报错Cannot fetch index base URL http://pypi.python.org/simple/

    产生这个问题的原因呢和github一样,因为他们用的cdn被墙.经小伙伴反馈,解决办法如下. 通过指定国内镜像源来安装: pip --trusted-host 镜像源 install 模块名 -i 镜 ...