Handler 泄漏场景、解决方案、深入理解

Handler基础原理与内存泄漏机制 Handler通过消息队列和Looper实现线程通信,但非静态内部类隐式持有Activity引用,延迟消息未清除时易导致内存泄漏。通过静态内部类+弱引用、及时移除消息、使用HandlerThread等方案可规避风险,结合LeakCanary工具辅助检测,是Android性能优化的核心实践。 阅读详情

Handler泄漏场景

一般情况下使用Handler不会出现内存泄漏,但是页面退出后Handler需要即时清理消息列表,特别是通过postDelay发布的延时任务,这种任务特别容易造成页面(Aty、Fgm)对象泄漏。

看看下面代码

class HandlerTestActivity : AppCompatActivity() {
    private val mHandler = Handler(Looper.getMainLooper()) // 主线程Handler
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mHandler.postDelayed({ // 发布一个延时任务,延时10s
            val a = "匿名内部类,持有外部类Activity的对象"
            Log.d("xiaoma", "a = $a")
        }, 10000)
    }
}

可以尝试下,打开页面后在10s内关闭Aty,就会出现内存泄漏。咦?这是为什么呢?先不解释,先抛一个大家都认同的:

如上,Handler在postDelay任务前,会先构建一个匿名内部类的任务对象,这种对象会持有外部类的对象。酱紫一来,是不是说明如果有个长生命周期的对象(例如static、单例)引用了这个任务对象,那就相当于持有了Activity的对象?是的话,结果会是什么,会造成什么问题?

很明显,Activity、Fragment这种短生命周期的对象被长生命周期对象持有,是危险的,容易出现内存泄漏,内存泄漏多了,容易频繁触发JVM GC,如果刚好泄漏的Activity很占用内存,甚至容易触发卡顿、OOM。

如何解决这种场景下的内存泄漏呢?本章只提出一种解决办法,其他方案不在讨论范围内哈哈。

解决Handler内存泄漏

在Activity#onDestroy()中清除Handler所有待执行的任务,如下:

class HandlerTestActivity : AppCompatActivity() {
    private val mHandler = Handler(Looper.getMainLooper())
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mHandler.postDelayed({
            val a = "匿名内部类,持有外部类Activity的对象"
            Log.d("xiaoma", "a = $a")
        }, 10000)
    }

    override fun onDestroy() {
        super.onDestroy()
        mHandler.removeCallbacksAndMessages(null) // 移除所有的回调和消息
    }
}

 为什么移除了就可以呢?如何结合JVM和Handler源码进行解释?

JVM GC

GC

(Garbage Collection,Java虚拟机垃圾回收器)

手机内存是有限的,供我们应用可使用的对象更是少之又少,那问题就来了,我们平时打开的Activity、Fragment、以及其他new出来的对象,都是需要占用内存的,当这些对象不需要用到时,这些对象占用的内存是怎么回收以便重新分配给新new的对象的?

这些对象的清理工作都是JVM GC在负责的,而这些需要被清理的对象称为垃圾对象。

这里抛个问题,如果一个对象在GC时没有被识别成垃圾对象,是不是一般就不会被回收?是的话,什么对象不会被识别成垃圾对象?这个问题对我们很重要,关系着我们是否能自己找出一个对象是否可能出现内存泄漏,以及如何处理、规避内存泄漏问题。

这里抛个知识点:

JVM 在GC时是基于“可达性算法”分析对象是否垃圾对象的

即JVM根据一系列称为“GC Roots”的跟对象作为起始节点集,以这些节点为开始,根据引用关系向下搜索,搜索走过的路径称为“引用链”,不在引用链内的对象是不可达的,允许被GC回收(此时并不是非死不可,有兴趣的可以度娘查一下GC对象的自救)

基于此我们可以理解为,当Activity对象给所谓称为"GC Roots"的对象引用了,就会出现在搜索的引用链上,被GC认为是非垃圾对象,从而不被释放。咦?这不就是内存泄漏?

那,能被GC认为是"GC Roots"的对象有哪些?这里提供一下。

GC Root

认清楚GC Root,避免我们的Activity、Fragment被其引用

  1. 虚拟机栈(栈帧中的本地变量表)中引用的对象
  2. 本地方法栈中JNI(即一般说的Native方法)引用的对象
  3. 方法区中类静态属性引用的对象
  4. 方法区中常量引用的对象
  5. 跨代引用:老年代引用新生代
  6. 所有的同步锁(synchronized关键字)持有的对象
  7. JVM的内部引用(class对象、异常对象NullPointerException、OutOfMemoryError、系统类加载器)
  8. JVM内部的JMXBean、JVMTI中注册的回调、本地代码缓存等
  9. JVM实现中的“临时性”对象

以上佐证了,static修饰的对象是GC 认为的 GC Root,被这种对象引用的对象,不会被GC识别为垃圾对象而回收。

那,这跟上面场景的Handler内存泄漏有什么关系?

为了解答这个问题,我们先进入Handler大致看看,一个延时任务是怎么被保存起来的。

Handler 源码

这里只通过源码,分析延时任务保存后,对象的引用链

延时任务保存

先通过AS下载SDK代码,查看Handler#postDelay()源码

SDK 源码下载查看
SDK源码下载查看
Handler#postDelayed(@NonNull Runnable r, long delayMillis)
Handler#postDelayed(@NonNull Runnable r, long delayMillis)
Handler#getPostMessage(Runnable r)
Handler#sendMessageDelayed(@NonNull Message msg, long delayMillis)
Handler#sendMessageDelayed(@NonNull Message msg, long delayMillis)
Handler#enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis)

进过上面流程发现,Handler通过构建一个Message对象,包裹Activity中构建的Runnable匿名内部类对象,然后Message对象会放到Handler#mQueue(MessageQueue对象)#mMessages这个队列的相应位置,这里我们称这个队列为MessageQueue,看到这里,我们发现mQueue是非static的,到此并没发现一个GC Roots引用了mActivity。

不着急,我们看看mQueue赋值

这个Looper我们给的是Looper.getMainLooper,也就是说这个MessageQueue就是MainLooper中的,那这个MainLooper在哪?

哈哈,这个MainLooper就是Looper类中的静态对象sMainLooper,在ActivityThread#main(应用在Java层的程序入口)初始化的。这下明了了,GC在触发时基于可达性算法,分析发现引用链:

Looper#sMainLooper -> sMainLooper#mQueue -> mQueue#mMessages -> 包裹目标Runnable对象的Message对象 -> 目标Runnable对象 -> Activity对象

最后发现Activity对象虽然退出了,但GC时通过可达性算法分析后发现不是垃圾对象,不予回收,最终出现内存泄漏。

感谢。

Android Studio Profiler 内存泄漏排查:3步定位并解决 Activity/Fragment 泄漏 本文详细介绍了如何使用Android Studio Profiler工具排查和解决Android开发中的内存泄漏问题,特别是Activity和Fragment泄漏。通过三步定位流程:强制GC确认内存状态、捕获堆转储分析、追踪引用链,开发者可以高效识别并修复常见泄漏场景。文章还提供了性能优化和内存管理的实用技巧,帮助提升应用稳定性。 阅读详情

相关推荐

Android Handler机制:线程通信与UI更新的核心解决方案

在Android开发中,线程间通信是处理UI更新的关键技术。Handler机制作为Android系统的核心组件,实现了子线程与主线程的安全通信,解决了耗时操作与UI更新的矛盾。其基于生产者-消费者模型,通过Message、Handler、MessageQueue和Looper四个关键组件协同工作,确保线程安全。Handler不仅支持基础消息传递,还能处理延迟任务,广泛应用于网络请求回调、定时任务等场景。现代开发中,虽然RxJava和Kotlin协程提供了替代方案,但Handler仍是性能最优的原生解决方案。

weixin_30352191的博客 410

Handler内存泄漏原因及解决方案

在Activity中,将Handler声明成非静态内部类或匿名内部类,这样Handle默认持有外部类Activity的引用。如果Activity在销毁时,Handler还有未执行完或者正在执行的Message,而Handler又持有Activity的引用,导致GC无法回收Activity,导致内存泄漏。如以下两种情形可能导致内存泄漏。3:非静态内部类 + 弱引用,在activity要回收时清除引用(麻烦,不推荐)1、在Activity内将Handler声明成匿名内部类。1、静态内部类 + 弱引用。

xiaowang_lj的博客 2863

使用Handler引发的内存泄漏问题及解决方案

在上述代码中,我们在主线程中创建了一个子线程,并在子线程中使用Handler发送了一个延迟消息。当延迟时间到达时,Runnable对象将被执行,但是由于耗时操作的存在,主线程无法及时处理该消息,从而导致主线程被长时间阻塞。这将导致主线程无法被垃圾回收,从而引发内存泄漏。为了避免Handler引发的内存泄漏,我们可以使用WeakReference来持有Activity或Fragment的引用,并在需要时移除消息和回调。通过这种方式,我们可以避免Handler持有过多的消息和回调,从而减少内存泄漏的风险。

PixelNinja的博客 469

Android ---【内存优化】Handler 内存泄漏原因及解决方案

Handler造成内存泄露的原因。非静态内部类,或者匿名内部类。使得Handler默认持有外部类的引用。在Activity销毁时,由于Handler可能有未执行完/正在执行的Message。导致Handler持有Activity的引用。进而导致GC无法回收Activity。匿名内部类: 非静态内部类: 三、解决方法 静态内部类+弱引用 Activity销毁时,清空Handler中,未执行或正在执行的Callback以及Message。 四、内部类为什么会持有外部类的引用 这是因为内部类虽然和外部类写在同一个

梁同学与Android 8277

对于Handler造成Activity内存泄漏的理解

写的很仓促。重新梳理一下。

知道这个有什么用? 788

深入理解Android Handler机制与实践

Handler是Android中用于处理线程间通信的类,它可以发送和处理Message和Runnable对象。Handler的主要功能包括:确保Message和Runnable对象的执行被分发到创建Handler的线程中。在不同的线程中进行数据的传递。实现复杂的线程间通信逻辑。一个典型的使用场景是在非UI线程中处理耗时任务,完成后再通过Handler更新UI线程。是Handler类的核心方法,用于处理从消息队列中取出的Message对象。该方法在。

weixin_30336531的博客 1073

Handler 在 Android 开发中的高级用法:从基础到复杂场景

是处理线程间通信的核心组件,它基于消息队列机制,允许在不同线程间安全地传递任务。以下内容将从基础概念逐步过渡到高级应用场景,帮助您深入理解其用法。所有代码示例均使用 Java 语言,确保真实可靠。实际开发中,建议结合 Kotlin 协程或。是 Android 提供的工具类,封装了。需结合线程管理、内存优化和任务调度。通过以上内容,您可以从基础逐步掌握。在 Android 开发中,:在主线程更新 UI。仍是底层通信的基石。

2501_93878243的博客 490

深入理解Android Handler与Looper的工作机制

本文深入探讨了Android开发中Handler与Looper的工作原理及其在应用架构中的重要性。通过Handler类提供的接口,开发者能够将消息提交到Looper队列,并在指定的线程中处理这些消息。文章详细解释了如何在不同线程中创建和绑定Looper,以及如何通过Handler发送和处理消息。此外,还讨论了Handler在资源管理中的潜在问题,例如隐式引用可能导致的内存泄漏,并提供了相应的解决建议。

weixin_30923011的博客 505

android 内存泄漏 检查_Android内存泄漏分析

概述 内存泄漏,即Memory Leak,指程序中不再使用到的对象因某种原因而无法被GC正常回收。发生内存泄漏,会导致一些不再使用到的对象没有及时释放,这些对象占据着宝贵的内存空间,很容易导致后续分配内存的时候,内存空间不足而出现OOM(内存溢出)。无用对象占据的空间越多,那么可用的空闲空间也就越少,GC就会更容易被触发,GC进行时会停止其他线程的工作,因此有可能造成卡顿等情况。Java内存分配策...

weixin_42398056的博客 665

学习STM32的无人机控制

下面是一个基于STM32的无人机控制的简单示例代码,包括四旋翼的PID控制、遥控器信号解析和电机控制。本示例以STM32F103系列为例,使用Keil C编译器进行开发。

qq_67153941的博客 706

2024高项范文-论项目成本管理

例如,在需求分析和设计阶段,安排了较多经验丰富的人员,确保基础工作的高质量完成,同时也避免了后续因需求变更等问题导致的时间和成本浪费。虽然项目进度略有延迟,但通过有效的成本管理和调整,最终在可控的成本范围内完成了项目,并且软件的质量也得到了保障。通过这个案例可以看出,在项目成本管理过程中,要紧密结合时间管理,根据实际情况灵活调整策略,注重风险的防范和应对,才能实现时间和成本的平衡,确保项目的成功。资源规划是成本基准形成的基础。识别和评估项目中的成本风险,并制定相应的应对措施,以降低风险对项目成本的影响。

xjflion的博客 1188

字节跳动面试:月薪20k+的Android面试都问些什么?建议收藏

前言 文章篇幅较长,建议耐心食用,相信对你有帮助。码字不易,如果有误,欢迎指出。 前部分是面试经历,后部分是附带的问题笔记/推荐回答/书籍。 我的微信小号有一组特殊的朋友。他们从 优快云上看到我之前发布的《从阿里到网易,一位非985/211本科应届生的一年求职路》后留下邮箱询要面试资料的,我在邮箱发送资料之后我们互相添加微信认识聊上的。 上上周,他们中有几个生通过微信找我咨询了一些找工作的问题,包括面试会问哪些技术,怎么更好的回答,如何怎么选择工作机会等等。和他们沟通交流中可以明显感觉他们的就业焦

clhcowboy的博客 350

SDI-12协议与STM32 进行uart通信

其中DIR_OUT_SDI是一个IO引脚,控制UART_TX_SDI是否使能,U10是三态门IC,即拉低DIR_OUT_SDI使能stm32输出,拉高DIR_OUT_SDI失能输出,串口配置8位数据位,偶校验,1位停止位,1200波特率。示波器测试得到传感器的应答信号如下:第一个是唤醒信号,拉高电平13毫秒,之后保持20毫秒低电平,第二个数据帧是激活信号,即ascii码: "0!",第三个是传感器的应答信号:"0\r\n"

weixin_42186805的博客 4403

价值2000元的学习资源泄露,实战篇

笼统来说,中年程序员容易被淘汰的原因其实不外乎三点。 1、输出能力已到顶点。这个人奋斗十来年了,依旧碌碌无为,很明显这人的天花板就这样了,说白了,天赋就这样。 2、适应能力越来越差。年纪大,有家庭,老油条,学习能力下降,磨洋工等等。 3、性价比逐渐降低。到了35岁的年薪基本都不会低的,然而输出能力到天花板了,随着年纪的逐渐增大,性价比会越来越低。说来说去就那经典的一句话:可替代性太高。25岁的年轻人便宜且前途可期,35岁已兑现天赋价格还高,这两选谁都差不多的时候,你说企业会怎么选……任何一个当了老板的人都会

m0_56222134的博客 76

华为校招&实习&留学生机试真题目录+算法分类+在线OJ+通过率+视频讲解

全网最新最全华为校招&实习&留学生机试真题题库

m0_73659489的博客 6872

STM32H743的FDCAN使用方法(1):STM32CubeMX初始化代码生成

STM32H743的FDCAN使用方法(1):STM32CubeMX初始化代码生成。

kevin1499的博客 2566

Android的内存泄漏场景及解决方案

内存泄漏是指无用的对象由于被错误的引用持有而无法被 GC 回收,导致内存占用不断增加,最终可能引发 OOM。

weixin_41553587的博客 906

2026马斯克《Moonshots》播客独家访谈全记录与深度解析

本次访谈是马斯克2026年首份重磅公开对话,录制于2025年12月22日美国得克萨斯州奥斯汀特斯拉超级工厂(Cybertruck与Optimus机器人核心生产基地),于2026年1月6日通过《Moonshots》播客正式上线,全时长近3小时。

lpfasd123的博客 2657

Android Handler 机制原理详解

Android Handler机制是线程间通信的核心,基于消息队列(MessageQueue)和循环器(Looper)构建,实现跨线程任务调度。主要组件包括: Handler:负责发送/处理消息,需绑定Looper; MessageQueue:按时间排序存储消息; Looper:驱动消息循环,每个线程唯一; Message:消息载体,含执行时间和目标Handler。 工作流程:子线程需手动调用Looper.prepare()初始化,通过Handler发送消息到队列,Looper.loop()循环取出消息并分

csdn_silent的博客 8922

PCL 点云配准-ICP算法(精配准)

ICP(Iterative Closest Point)算法是一种常用的点云精细配准算法。它通过迭代计算将一组点云变换到另一组点云的最佳位置,最终实现精确的对齐。ICP算法在每次迭代中选择与目标点云最接近的点作为对应点,通过计算最小化源点云和目标点云之间的均方误差(MSE)来进行刚性变换(包括平移和旋转)。

纵马踏花向自由 1725

前端Three.js面试题及参考答案

渲染循环(Render Loop)是 Three.js 中一个非常重要的概念,它是一个不断重复执行的过程,用于更新和渲染 3D 场景。在每次循环中,渲染循环会更新场景中的对象状态,如位置、旋转、缩放等,然后使用渲染器将更新后的场景渲染成 2D 图像显示在屏幕上。通过不断地重复这个过程,就可以实现动画效果。渲染循环的核心作用是确保场景能够实时更新,给用户带来流畅的视觉体验。如果没有渲染循环,场景将是静态的,不会有任何变化。只有通过不断地更新和渲染,才能让场景中的对象动起来,实现各种动画效果。

大模型大数据攻城狮的专栏 2722

Lamson进阶开发:深入理解Handler机制与状态管理

Lamson作为一个Pythonic SMTP应用服务器,其核心魅力在于其强大的路由和状态管理机制。本文将深入探讨Lamson的Handler机制与状态管理系统,帮助开发者更好地构建复杂的邮件处理应用。🚀 ## Lamson的Handler机制基础 Lamson的Handler机制是其最核心的功能之一,它允许开发者像编写Web应用一样处理电子邮件。通过`@route`装饰器,你可以定义邮件地

gitblog_01065的博客 339

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

InterviewQuestion实战:Handler、Looper、MessageQueue源码解析

在Android开发中,Handler、Looper、MessageQueue构成了核心的异步消息处理机制,是保证UI线程不阻塞、实现组件间通信的关键。本文将从实战角度解析这三者的工作原理及源码实现,帮助开发者深入理解Android消息循环机制。 ## 一、Handler机制核心组件关系 Handler机制主要由**Handler**、**Looper**、**MessageQueue**和*

gitblog_00318的博客 664

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

Handler相关知识点

一 主线程为什么没有被loop阻塞 因为应用中不管是Activity,还是Service,所有的操作都是在各自的生命周期中执行的,所以它所有的操作都逃不出生命周期。所以,所有的操作都执行在ActivityThread.java中的loop()里面,所以,应用所有的操作都是在这个loop()中来管理的,也正是因为这个原因,主线程的loop()是不能够退出去的。只有一种情况,我们在一个应用的一个界面下不动,这个应用没有任何事件发生,也没有任何别的事件要处理,这个时候,我们的Looper就处于一个block状态

Ankas 716

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

深入理解DeallocationChecker源码:手把手教你实现ViewController泄漏检测

在iOS开发中,ViewController内存泄漏是常见问题,会导致应用性能下降甚至崩溃。DeallocationChecker作为一款轻量级泄漏检测工具,无需打开Instruments就能快速定位泄漏问题。本文将带你深入理解其实现原理,并手把手教你如何在项目中集成和使用。 ## 什么是DeallocationChecker? DeallocationChecker是一个专门用于检测View

gitblog_00468的博客 1020

Handler、Looper、多线程

1. Handler与UI同线程,它用于在子线程中通知主线程更新。      诞生一个主线程的Handler物件,当做Listener去让子线程能将讯息Push到主线程的Message Quene里,以便触发主线程的handlerMessage()函数,让主线程知道子线程的状态,并在主线程更新UI。       例如,在子线程的状态发生变化时,我们需要更新UI。如果在子线程中直接更新UI,通常

783
上一篇: Android GestureDetector支持View拖动、长按拖动
下一篇: 为什么匿名内部类会持有外部类对象?
PokeMa
博客等级 码龄10年 101粉丝 28原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值