美文网首页Android 性能优化篇
Handler 引起内存泄露的真正原因

Handler 引起内存泄露的真正原因

作者: Tsm_2020 | 来源:发表于2023-08-06 10:49 被阅读0次

这个问题对于android 来说应该是一个老生常谈的问题,也几乎所有人都知道了使用Handler会引起内存泄露,对于内存泄露本人的理解也只能停留在非静态内部类会隐士的持有外部类的引用这个层面,但是再向下深究就不得而知了,为了能够提高自身的代码能力,在接下来的一段时间,我会在这里记录我学习framework 后的一些体会以及以前懵懂的问题

说道内存泄露就要知道Android 在GC 发生的时候,都干了什么,使用了什么样的方式来对内存中的对象回收的

1.引用计数法:

这个方式被应用在早期的系统中,一个内存对象每被使用一次,这个对象的引用技术则+1,反之亦然 ,但是这种方式存在一个弊端,那就是如果存在一个环状引用,并且这个环没有被外界所引用,虽然他的引用计数不为0,但是他应该是一个需要被回收的对象.

2. 可达性分析法:

在jvm中,如果一个内存对象, 有通过gcroot 能够找到的引用对象,证明这个对象不需要被回收,如果没有则需要被回收,从网上找了一张图来看一下

image.png

在这张图片中 object 1-4 ,都是被gc 直接或者间接引用的到对应,他们在gc过程中是不需要被回收的,而 obj5-7 则是需要被回收的对象,
这个概念很好理解,那么什么是gcroot, 哪些对象可以被当做gcroot 来看待呢,

GC Root

从网上找了一些资料对于gcroot 描述一般如下
1.虚拟机栈中引用的对象
2.方法区中类静态属性引用的对象
3.方法区中常量引用的对象
4.Native方法中引用的对象
5.活动线程中的对象
6.当前类加载器加载的类的对象

知道了上面的关系,我们来一步一步的分析,handler 是如何引起内存泄露的

1. 非静态内部类隐士持有外部类的引用

image.png

由于是隐士的持有,那么在 内部类中可以直接调用外部的testFun 这个方法,也可以通过他所持有的外部类的对象来调用

那么现在的引用关系就是 Handler--> Activity

2.发送消息的过程

发送消息的过程中,这个引用关系又增加了好几层,我们先来看看Handler 是如何与message 建立关系的,这里贴一下源码

image.png image.png

从图片中可以看到,调用了handler.obtainMessage()方法是,handler 将自身传入了进去,并且将自己赋值给了message 的 target 属性

那么现在的引用关系就是 Message--> Handler--> Activity

3.消息入队

调用完 handler.obtainMessage() 后,还需要将消息发送出去,对应的方法也就是 Message.sendToTarget()方法将消息加入MessageQueue 中,源码如下


image.png image.png

这里需要注意的是入队过程是一个synchronized 对象锁的同步方法,我理解的原因有2个,

  1. 发送消息可以是在任意一个线程,如果多线程同时操作链表,可能会引起并发问题
    2.如果入队和出队同时发生,也会引起并发问题, 从 MessageQueue .next( ) 获取消息也同样的使用synchronized 同一个对象锁 来想到的,


    image.png

那么现在的引用关系就是 MessageQueue--> Message--> Handler--> Activity

4.查询MessageQueue 是在哪里创建,又被何人使用-->Looper

image.png

在Looper 的构造方法中,发现 MessagerQueue 在Looper 创建的同时,被创建

那么现在的引用关系就是 Looper-> MessageQueue--> Message--> Handler--> Activity

我们再回过头来看关于gcroot的描述 5.活动线程中的对象

image.png

looper 是ActivityThread 中创建,并且开始他的轮询的,而ActivityThread 也就是我们的主线程,那么looper 就是一个活动线程中的对象,进而证明了他是一个间接被gcroot持有引用的对象

那么现在的引用关系就是ActivityThread --> Looper-> MessageQueue--> Message--> Handler--> Activity

其实我们还可以通过另一个地方来证明 ThreadLocal

image.png

从这张图片中可以看出 ActivityThread 调用了 prepareMainLooper, 就是创建了一个looper ,同时把mainLooper设置为了静态变量,

那么现在的引用关系就是sMainLooper-> MessageQueue--> Message--> Handler--> Activity

这个符合关于gcroot 描述的第二条2.方法区中类静态属性引用的对象

从上面分析我们就能看出来,想要做到让activity 不发生内存泄露,只能打破这个引用链,一般常用的做法是将 Handler 变成静态内部类,他也就没有外部类的对象了,或者使用WeakReference 来修饰 Hnadler 持有的内部类对象,或者在activity 退出前将 removeAllCallBackAndMessage

相关文章

网友评论

    本文标题:Handler 引起内存泄露的真正原因

    本文链接:https://www.haomeiwen.com/subject/usgipdtx.html