handler内存泄漏情景分析

目录

前言

一、handler的几种创建方式

二、何时产生内存泄漏?

三、再谈handler内存泄漏


前言

一直以来,无论是在工作还是面试中,关于内存泄漏的话题,肯定是绕不开的。其中最典型的就是handler的内存泄漏问题,今天就带着大家一起探究一下handler内存泄漏的真正原因和场景。


一、handler的几种创建方式

先上图:

 

相信大家在平时开发过程中定义handler时,最常见就是以上这三种了。

第一种:直接使用匿名内部类

第二种:内部类方式

第三种:静态内部类方式

那么这三种方式肯定是有区别的,区别在哪里?

查看一下编译后的字节码:

 

 可以看到SecondActivity被分成了四部分,很容易看出SecondActivity$1、SecondActivity$handler1、SecondActivity$handler2分别对应了以上三种创建方式。

先看SecondActivity$1

 原来匿名内部类方式,构造函数中传入了一个外部类对象的引用,且被handler持有了,这里指的就是activity被持有了。

再看SecondActivity$handler1

 

 和SecondActivity$1一样,activity也被持有了。

最后看看SecondActivity$handler2

 可以看到静态内部类方式,不会持有activity的引用。

ps:这里其实跟Android没啥关系,纯粹的java基础。。。

二、何时产生内存泄漏?

有了上面的分析,我可以初步得出结论:使用第三种方式即静态内部类的方式是最“保险”的。

但是有一点要说明,并不是说第三种就一定不会产生内存泄漏,其他两种方式就一定会产生内存泄漏,实际还是要看后续的用法。

ok,我们继续往下分析如何产生的。

先在原有的代码上补充一些逻辑:

 三个handler各自向主线程的消息队里投递了消息。

跟一下handler#post的源码:

 public final boolean post(@NonNull Runnable r) {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

  /
 public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }


///
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }

///
 private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

定位到enqueueMessage这个方法中,"msg.target = this",原来msg的target持有了当前的handler。

ps:很多博文都喜欢用postdelay举例,其实个人觉得post也可以解释。因为post出去的消息,不一定会立即被执行,尤其是在looper很忙的时候。

假设此时消息都没有被执行,而activity现在被finish掉了,此时三种方式对象之间的持有关系:

1)匿名内部类:handler持有activity   msg持有handler 

2)内部类:handler持有activity   msg持有handler 

3)静态内部类:handler不持有activity   msg持有handler 

根据gc原理(可达性算法),可以指中1和2中ativity都不能被回收,3可以被回收。

那么仅仅使用静态内部类,就可以完全避免了吗?答案:不是的

接下来我们修改一下代码:

 一目了然,还是上面的情形,还是会造成内存泄漏。

怎么改呢?如下:

 比较简单,就是使用弱引用。。

三、再谈handler内存泄漏

不知道大家有没有发现,上面所述设定的场景,其实只会造成activity暂时无法回收,当时handler的任务执行完时,当jvm再次gc,activity还是能被回收的,因为,任务执行完后,对应的msg会被回收,target会被置空,handler被解除持有,可以被回收,随即activity当然也可以被回收了。

所以严格来讲,还是不会发生内存泄露。

那么什么场景下才会发生呢?

我们再修改一下代码:

类似搞了不断执行的任务,可能这里的写法不严谨,只是为了表现一种不断执行的状态,实际项目中,我们在使用handler时,很少让全局的handler执行执行一次任务,太浪费了。。。如果真的只需要执行一次任务,那直接就是写成了new Handler().post.....

归回正题,如果是这样的场景,那就会有不断的msg持有handler对象,handler又持有了activity,这样的结果就是activity是真的不能回收了,即发生内存泄漏。

那么此种情况下怎么解决呢?

在onDestroy时使用removeCallbacksAndMessages移除所有未执行的消息,即可停止handler。

另外,我以前一直有这么个疑惑:根据removeCallbacksAndMessages的源码得知,该方法是移除属于该handler的所有等待执行的消息,那么正在执行消息无法移除,那岂不是会发生停止不了的情况吗?  --------------现在我明白了,其实post和ondestroy同在主线程,是同步的,一旦ondestroy执行,dodo肯定不会执行,也就是说当前正在执行的任务不是handler2投递的消息而是执行的是生命周期相关handler投递的消息(主线程的looper只有一个,handler却有多个),此时removeCallbacksAndMessgage清空了所有handler2的消息,终止了handler2。

到此,分析完结,,睡觉咯

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值