引子

什么是内部类?什么是内存泄露?为什么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会有如下提示:

 

posted @ 2015-08-31 11:07 cacard 阅读(6654) 评论(0) 推荐(5) 编辑
摘要: 概览 * Message:消息。消息里面可包含简单数据、Object和Bundle,还可以包含一个Runnable(实际上可看做回调)。 * MessageQueue:消息队列,供Looper线程消费消息。 * Looper:用于循环处理Message,一个Thread结合一个Looper来实现消息 阅读全文
posted @ 2014-07-01 11:00 cacard 阅读(894) 评论(0) 推荐(0) 编辑
摘要: 疑问 * ArrayList是非线程非安全的,具体是指什么?具体会产生什么问题?* ArrayList的内部原理是什么?为什么可以动态扩容?* Vector是线程安全的,具体是如何实现的?为什么不再推荐使用?还有它的适用场景吗?* 迭代时集合发生了修改怎么办?什么是fail-fast? 线程安全和非 阅读全文
posted @ 2014-04-19 08:01 cacard 阅读(2576) 评论(0) 推荐(0) 编辑
摘要: JVM执行Java程序时需要装载各种数据,比如类型信息(Class)、类型实例(Instance)、常量数据(Constant)、本地变量等。不同的数据存放在不同的内存区中,这些数据内存区称作“运行时数据区(Runtime Data Area)”。运行时数据区有这样几个重要区:JVM Stack(简... 阅读全文
posted @ 2014-03-30 20:10 cacard 阅读(7075) 评论(0) 推荐(2) 编辑
摘要: 如何判断垃圾对象?垃圾收集的第一步就是先需要算法来标记哪些是垃圾,然后再对垃圾进行处理。引用计数(ReferenceCounting)算法这种方法比较简单直观,FlashPlayer/Python使用该算法,简单高效。核心思路是,给每个对象添加一个被引用计数器,被引用时+1,引用失效-1,等于0时就表示该对象没有被引用,可以被回收。但是,Java/C#并不采用该算法,因为该算法没有解决对象相互引用的问题,即:当两个对象相互引用且不被其它对象引用时,各自的引用计数为1,虽不为0,但仍然是可被回收的垃圾对象。根搜索(GC Roots Tracing)算法基本原理是:GCRoot对象作为起始点(根) 阅读全文
posted @ 2014-03-28 21:29 cacard 阅读(5804) 评论(1) 推荐(2) 编辑
摘要: 无锁编程 / lock-free / 非阻塞同步无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。实现非阻塞同步的方案称为“无锁编程算法”(Non-blocking al... 阅读全文
posted @ 2014-03-20 15:43 cacard 阅读(21768) 评论(1) 推荐(5) 编辑
摘要: JDK5引入了JMM新规范:JSR-133,引入了happens-before/可见性等概念,对synchronized/volatile/final等关键词进行了语义定义。解决了:final变量在构造器中初始化的线程安全问题以及volatile变量与no-volatile变量之间的重排序问题。为什么需要Memory Model在多线程的场景下,thread1修改了一个变量后,thread2要读取这个变量,其间可能会发生指令执行顺序的问题(因为编译器优化指令、处理器重排指令、写数据缓存未及时更新到主内存)。如何保证thread2要读的变量是想要的thread1修改后的变量呢?Memory Mo 阅读全文
posted @ 2014-03-18 09:52 cacard 阅读(2572) 评论(0) 推荐(1) 编辑
摘要: MQTT -MQ Telemetry Transport轻量级的 machine-to-machine 通信协议。publish/subscribe模式。基于TCP/IP。支持QoS。适合于低带宽、不可靠连接、嵌入式设备、CPU内存资源紧张。是一种比较不错的Android消息推送方案。FacebookMessenger采用了MQTT。MQTT有可能成为物联网的重要协议。消息体MessageTypeCONNECTTCP连接建立完毕后,Client向Server发出一个Request。如果一段时间内接收不到Server的Response,则关闭socket,重新建立一个session连接。如果一个 阅读全文
posted @ 2014-03-15 10:20 cacard 阅读(102638) 评论(1) 推荐(2) 编辑
摘要: 工作队列:Working Queue工作队列这个概念与简单的发送/接收消息的区别就是:接收方接收到消息后,可能需要花费更长的时间来处理消息,这个过程就叫一个Work/Task。几个概念分配:多个接收端接收同一个Queue时,如何分配?消息确认:Server端如何确定接收方的Work已经对消息进行了完整的处理?消息持久化:发送方、服务端Queue如何对未处理的消息进行磁盘持久化?Round-robin分配多个接收端接收同一个Queue时,采用了Round-robin分配算法,即轮叫调度——依次分配给各个接收方。消息确认默认开启了消息确认(接收方接收到消息后,立即向服务器发回确认)。消息接收方处理 阅读全文
posted @ 2014-03-14 10:55 cacard 阅读(10519) 评论(0) 推荐(1) 编辑
摘要: 简介RabbitMQ是一个Message Broker,核心思想就是接受消息,转发消息。实现的协议:AMQP。术语(Jargon)P,Producing,制造和发送信息的一方。Queue,消息队列。C,Consuming,接收消息的一方。Simple Demo发送方 ConnectionFactory factory = new ConnectionFactory(); factory.setHost("localhost"); Connection connection = factory.newConnection(); Channel channel = conn.. 阅读全文
posted @ 2014-03-13 20:09 cacard 阅读(8946) 评论(1) 推荐(0) 编辑
点击右上角即可分享
微信分享提示