引子

什么是内部类?什么是内存泄露?为什么Android的内部类容易引起内存泄露?如何解决?

什么是内部类?

什么是内部类?什么又是外部类、匿名类、局部类、顶层类、嵌套类?大家可以参考我这篇文章 ,再查查一些资料,先弄清楚什么是内部类和内部类的特性再向下看。

经常会遇见Android程序中这样使用handler:

public class SomeActivity {

    // ......

    private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch(msg.what) {
case 0:
// do something
break;
case 1:
// do something
break;
default:
break;
}
}
}; private void someMethod () {
mHandler.sendEmptyMessage(0);
}
}

上述代码中,mHandler字段指向一个匿名Handler类。匿名类是内部类吗?匿名类会持有外部类的对象吗? 答案是:匿名类是内部类,但是是特殊的内部类,如果把匿名类放到一个static方法中,它是不会持有外部类实例的。而在上面的代码中,这个mHandler会持有外部类(SomeActivity)实例的引用,因为它处于一个对象的上下文中,而不是类型上下文中。

什么是”持有外部类实例的引用“?你可以这么理解:

public class InnerClass {
private OuterClass outer;
public InnerClass(OuterClass outer) {
this.outer = outer;
}
}

就是说,创建InnerClass对象的时,必须传递进去一个OuterClass对象,赋值给InnerClass的一个字段outer,该字段是OuterClass对象的引用。回忆一下GC原理,如果InnerClass对象没有被标记为垃圾对象,那么outer指向的OuterClass对象会可能被标记为垃圾对象吗?答案是:InnerClass对象与GC Root有引用路径,InnerClass对象又引用了OuterClass对象,那么OuterClass对象到GC Root也是有引用路径的,所以,OuterClass不可能是垃圾对象。

为什么发生内存泄露?

由上文可以看出:当mHandler没有被回收时,其外围Activity对象不能被回收。当Activity被用户关闭(finish),而此时mHandler还未被回收,那么Activity对象就不会被回收,造成Activity内存泄露。

问题的关键转入到了这个问题:为什么Activity被finish了,mHandler还不能被回收?

发送消息时,我们使用了这个函数:mHandler.sendEmptyMessage(0)函数。通过查看源码追踪调用关系,发现走到了:

Message对象有个target字段,该字段是Handler类型,引用了当前Handler对象。一句话就是:你通过Handler发往消息队列的Message对象持有了Handler对象的引用。假如Message对象一直在消息队列中未被处理释放掉,你的Handler对象就不会被释放,进而你的Activity也不会被释放。

这种现象很常见,当消息队列中含有大量的Message等待处理,你发的Message需要等几秒才能被处理,而此时你关闭Activity,就会引起内存泄露。如果你经常send一些delay的消息,即使消息队列不繁忙,在delay到达之前关闭Activity也会造成内存泄露。

有什么解决方案?

方案#1:在关闭Activity时(finish/onStop等函数中),取消还在排队的Message:
mHandler.removeCallbacksAndMessages(null);

方案#2:使用WeakReference截断StrongReference。问题的症结既然是内部类持有外部类对象的引用,那我不用内部类就行了,直接使用静态成员类。但mHandler又需要与Activity对象交互,那就来个WeakReference,指向外部Activity对象。

public class SomeActivity {
private Handler mHandler = new MyHandler(this);
private static class MyHandler extends Handler {
private WeakReference<SomeActivity> ref;
public MyHandler(SomeActivity activity) {
if (activity != null) {
ref = new WeakReference<SomeActivity>(activity);
}
}
@Override
public void handleMessage(Message msg) {
if (ref == null) {
return;
}
SomeActivity v = ref.get();
if (v == null) {
return;
}
// handle message
}
}
}

当Activity想关闭销毁时,mHandler对它的弱引用没有影响,该销毁销毁;当mHandler通过WeakReference拿不到Activity对象时,说明Activity已经销毁了,就不用处理了,相当于丢弃了消息。

另外,当你使用Handler有内存泄露时候,Android Studio的Lint会有如下提示:

Android中的内部类引起的内存泄露的更多相关文章

  1. Android学习系列(37)--App调试内存泄露之Context篇(下)

    接着<Android学习系列(36)--App调试内存泄露之Context篇(上)>继续分析. 5. AsyncTask对象 我N年前去盛大面过一次试,当时面试官极力推荐我使用AsyncT ...

  2. JavaScript 中 4 种常见的内存泄露陷阱

    了解 JavaScript 的内存泄露和解决方式! 在这篇文章中我们将要探索客户端 JavaScript 代码中常见的一些内存泄漏的情况,并且学习如何使用 Chrome 的开发工具来发现他们.读一读吧 ...

  3. Android开发过程中使用弱引用解决内存泄露的习惯

    Java虽然有垃圾回收,但是仍然存在内存泄露,比如静态变量.缓存或其他长生命周期的对象引用了其他对象,这些被引用的对象就会长期不能被GC释放,导致内存泄露. 弱引用(WeakReference)是解决 ...

  4. Android学习系列(36)--App调试内存泄露之Context篇(上)

    Context作为最基本的上下文,承载着Activity,Service等最基本组件.当有对象引用到Activity,并不能被回收释放,必将造成大范围的对象无法被回收释放,进而造成内存泄漏. 下面针对 ...

  5. Android -&gt; 怎样避免Handler引起内存泄露

    很多其它内容,可訪问个人博客www.liangfeizc.com 错误代码 假设在Activiy中通过内部类(Runnable)的方式定义了一个变量runnable, final Runnable r ...

  6. Android Studio 使用Memory Monitor进行内存泄露分析

    在使用Android Studio进行内存泄露分析之前,我们先回顾一下Java相关的内存管理机制,然后再讲述一下内存分析工具如何使用. 一.Java内存管理机制 1. Java内存分配策略 Java ...

  7. 【转载】Java中如何写一段内存泄露的程序 & ThreadLocal 介绍和使用

    可以参考这段文章: link A1:通过以下步骤可以很容易产生内存泄露(程序代码不能访问到某些对象,但是它们仍然保存在内存中): 上文中提到了使用ThreadLocal造成了内存泄露,但是写的不清不楚 ...

  8. Android中一张图片占据的内存大小是如何计算

    本篇文章已授权微信公众号 hongyangAndroid (鸿洋)独家发布 最近封装了个高斯模糊组件,正好将图片相关的理论基础也梳理了下,所以,这次就来讲讲,在 Android 中,怎么计算一张图片在 ...

  9. Android APP常见的5类内存泄露及解决方法

    1.static变量引起的内存泄漏 因为static变量的生命周期是在类加载时开始 类卸载时结束,也就是说static变量是在程序进程死亡时才释放,如果在static变量中 引用了Activity 那 ...

随机推荐

  1. [Office][C#] NPOI、OpenXML SDK、OpenOffice.org SDK 写入资料到 EXCEL 档案[转]

    原文地址:http://www.dotblogs.com.tw/chou/archive/2010/04/29/14912.aspx 一.簡介 要將資料寫入 EXCEL 檔案有許多的方法,但假如電腦不 ...

  2. Java MD5加密工具类

    public final static String MD5(String s) { char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6', ' ...

  3. IOS网络编程。。

    ASI 与AFN框架:  越低层性能越好. AFNetworking ASIHTTPRequest(性能好点) NSURL会更好. NSURL NSURLRequest NSData * data = ...

  4. PyMongo下载及安装

    PyMongo最新版本下载地址: http://pypi.python.org/pypi/pymongo/#downloads PyMongo旧版本下载地址: http://pypi.python.o ...

  5. C3P0连接池连接MySQL出现的问题

    1.Clearing pending acquires. While trying to acquire a needed new resource, we failed to succeed mor ...

  6. JBOSS最大连接数配置和jvm内存配置

    一.调整JBOSS最大连接数. 配置deploy/jboss-web.deployer/server.xml文件 .       <Connector         port="80 ...

  7. 记一次SQLServer数据库误删数据找回

            昨天 同事在本机清理数据库表时,连接到了生产机,误删了二十几张表,幸好是晚上加班的时候删除的,生产机上当时是一天一备份,还原备份是最后的策略,最关键的还是要找回数据.         ...

  8. 大熊君说说JS与设计模式之(门面模式Facade)迪米特法则的救赎篇------(监狱的故事)

    一,总体概要 1,笔者浅谈 说起“门面”这个设计模式其实不论新老程序猿都是在无意中就已经运用到此模式了,就像我们美丽的JS程序员一样不经意就使用了闭包处理问题, function Employee(n ...

  9. 初识ASP.NET 5中的Sake与KoreBuild

    从github上签出基于ASP.NET 5的MVC 6的源代码进行编译,发现有2个编译命令: build.cmd是针对Windows的编译命令,build.sh是针对Mac/Linux的编译命令,这本 ...

  10. Unity3.0基于约定的自动注册机制

    前文<Unity2.0容器自动注册机制>中,介绍了如何在 Unity 2.0 版本中使用 Auto Registration 自动注册机制.在 Unity 3.0 版本中(2013年),新 ...