面试官问:Handler内存泄露的场景,别就只知道静态内部类&弱引用……

本文深入探讨了Android中Handler内存泄露的一个特殊场景,涉及Message Pool和DialogFragment的交互,解释了如何由于Message的重用导致Activity无法正常回收,以及系统和App层面的解决方案。通过案例分析,揭示了内存泄露可能的复杂性和解决思路。
摘要由CSDN通过智能技术生成

我们在编码的过程中,如果出现疏忽或错误,造成程序未能释放已经不再使用的内存,就会导致内存泄露,随着泄露内存的增长,最终一定会导致 OOM。

在 JVM 中,对对象的回收 GC 是基于可达性分析。简单来说,就是从 GC Root 出发,被引用的对象均被标记为存活,而没有被引用的对象,则被标记为垃圾,即可以被 GC 回收。

那么如果出现内存泄露,可以理解为就是一个长生命周期的对象,引用了短生命周期的对象,导致短生命周期的对象,在生命周期结束后,仍然得不到回收,最终导致内存泄露。

而 Handler 若是使用不当,就有内存泄露的可能。这种情况,通常发生在对应的 MessageQueue 中,持有了延迟 Message,而这个 Message 又间接持有了 Activity,导致 Activity 回收不即时出现内存泄露。针对这种情况,我们也有了成熟的解决方案,例如使用静态内部类 + WeakReference 解决。

今天给大家介绍另外一个 Handler 体系下,出现的内存泄露的场景。主要由于基于享元模式的 Message Pool 导致 Message 对象的重用,这个 Message 会被 Looper 在循环时,作为局部变量短暂的持有,那么如果这个 Message 又被其他短生命周期的对象持有了,就会导致它的内存泄露。

本文介绍的场景,比较特殊。就是子线程 Looper(HandlerThread)持有的 Message 对象,又被 DialogFragment 持有了,导致这个弹出的 Dialog 的 Activity,在 finish() 后仍得不到回收的情况。

文章内我会在适当的地方加一些补充,仅代表个人理解,文末也追加了一个总结。希望各位阅读愉快,接下来是原文。


某一个 HandlerThread 的 Looper#loop 方法,一直等待 queue#next 方法返回,但是它的 msg 局部变量还引用着上一个循环中已经被放到 Message Pool 中 Message,我们称之为 MessageA。

Q: 咋回事?正常使用 Dialog 和 DialogFragment 也有可能会导致内存泄漏?

A: … 是的,说来话长。

长话短说:

  1. 某一个 HandlerThread 的 Looper#loop 方法,一直等待 queue#next 方法返回,但是它的 msg 局部变量还引用着上一个循环中已经被放到 Message Pool 中 Message,我们称之为 MessageA;
  2. DialogFragment 的 onActivityCreated 方法中,会调用 Dialog#setOnCancelListener 方法,将自身的引用作为 listener 参数传递给该方法;
  3. Dialog 的 setOnCancelListener 方法内部,会尝试从 Message Pool 中获取一个 Message,取出的 Message 刚好是 MessageA,然后将传入的 Listener 实例赋值给 MessageA#obj
  4. 外部调用 cancel() 的时候,Dialog 内部会将 MessageA 拷贝一份,我们称它为 MessageB,然后将 MessageB 发送到消息队列中;
  5. DialogFragment 收到 onDestory 回调之后,LeakCanary 开始监听这个 DialogFragment 是否正常被回收,发现这个实例一直存在,dump 内存,分析引用链,报告内存泄漏问题;

墨影补充:DialogFragment 实现在构造时,会从 Message Pool 中获取一个 Message,但在结束时又不会消费这个 Message 对象,会持续持有这个 Message 对象。

具体细节介绍见下文。

一、发现问题

开发的时候, LeakCanary 报告了一个诡异的内存泄漏链。

操作路径:App 显示 DialogFragment 然后点击外部使其消失,之后 LeakCanary 就报了如下问题:

从上面的截图,可以看出:GCRoot 是 HandlerThread 正在执行的方法中的一个局部变量。这个局部变量强引用了一个 Message 对象,messageobj 字段又强引用了 NormalDialogFragment ,导致其调用了 onDestory() 方法之后,也无法被回收。

二、分析

注:本文中的「HandlerThread」,泛指那些带有 Looper 并且开启了消息循环(调用了 Looper#loop)的线程。

DialogFragment 为啥会被一个 Message 的 obj 字段强引用?而且那还是一个被 HandlerThread 引用着的 Message。

回顾一下我们正常显示 DialogFragment 的流程。

  1. 实例化 DialogFragment;
  2. 调用 DialogFragment#show 方法让其显示出来;

这个流程中有可能导致 Fragment 被 Message 强引用吗?

  • 首先看 DialogFragment 的构造方法是一个空实现。(排除)
  • 其次看 DialogFragment show 方法逻辑如下,也是正常的 Fragment 显示逻辑。(排除)
public void show(@NonNull FragmentManager manager, @Nullable String tag) {
  mDismissed = false;
  mShownByMe = true;
  FragmentTransaction ft = manager.beginTransaction();
  ft.add(this, tag);
  ft.commit();
}

难道是 show() 过程的某个步骤中去获取了 Message?

DialogFragment#onActivityCreated 方法中,可以看到:

@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
  s
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值