Android消息机制(未完待续~~)

本文详细介绍了Android消息机制,涵盖Handler的用法、源码解析、内存泄漏解决方案、HandlerThread的应用以及消息同步屏障等。通过实例分析了内存泄漏的原因及防止方法,并探讨了多线程Handler在实际开发中的应用。此外,还涉及了面试中常见的Handler相关问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、消息机制介绍

首先,要了解消息机制中的四个概念;
A Handler:负责消息的发送与消息的处理
B MessageQueue:用来存放消息
C Looper:轮询消息队列,负责取消息
D ThreadLocal:保证每个线程都有自己的消息轮询器Looper

1.1 Handler的用法:因发送消息到消息队列的方式不同而不同

  • Handler.sendMessage()—内部类/匿名Handler子类
  • Handler.post()——更新的UI操作可直接在重写的run()中定义

以上两种方式本质没有区别,都是向messageQueue中加入了消息,只是回调方式的不同,post runnable 回调是回到run方法中,send 最终会调用到handlerMessage

二、 Handler源码工作流程——生产者消费者设计模式(参考资料)

如何实现线程间跨越?
2.1 使用流程
handler.sendMessage==>handler.handlerMessage()期间做了什么?

  • 不管是sendMessage还是sendEmptyMessage或post都会走到handler.sendMessageAtTime==》handler.enqueueMessage
  • MessageQueue.enqueueMessage将消息保存在消息队列中(消息队列/消息队列插入)
  • 通过Looper.loop中MessageQueue.next()函数取消息队列中的消息
  • 再调用handler.dispatch()最后走handler.handlerMessage()

2.2 Message

  • Message通过new或者obtain获取,最好通过obtain,可检查是否有可复用的Message,避免过多创建销毁Message对象达到优化内存和性能的目的(不停创建Message会导致崩溃);
  • Message实现内存共享,内存不分线程,
  • Message如何实现主线程与子线程内存共享:子线程中发送消息,主线程中处理消息
  • Message 享元设计模式:实现内存复用

2.3 MessageQueue:单链表实现的优先级队列(根据时间排序)
消息管理中保证线程安全的两个函数:
enqueueMessage 消息入库
synchronized锁是个内置锁,对所有调用同一个MessageQueue对象的线程来说都是互斥的,一个线程是对应着一个唯一的Looper对象,而Looper中又只有一个唯一的MessageQueue,所以主线程中只有一个MessageQueue对象,也就是说,所有的子线程向主线程发送消息的时候,主线程一次都只会处理一个消息,其他的都需要等待,那么这个时候消息队列就不会出现混乱

next 消息出库
在next里面加锁,因为,这样由于synchronized(this)作用范围是所有 this正在访问的代码块都会有保护作用,也就是它可以保证 next函数和 enqueueMessage函数能够实现互斥。这样才能真正的保证多
线程访问的时候messagequeue的有序进行

MessageQueue属于哪个线程?
==》跟随着Looper创建,是一个容器,不属于任何线程

2.4 Looper源码
构造函数——私有构造函数,通过Looper.prepare来初始化
==》ThreadLocal: 线程上下文存储变量(ThreadLocalMap)
==》prepare初始化时有判空处理,故一个线程只有一个Looper,且不可修改

private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
         throw new RuntimeException("Only one Looper may be created per thread");
     }
     sThreadLocal.set(new Looper(quitAllowed));
 }

loop方法

ActivityThread的main函数中
Looper.prepareMainLooper();

Looper.loop();其中有个for死循环
说明所有代码都是在Handler上运行的,通过消息的机制将信息发出

若要死循环停止,从代码来看需要获取一个空的message,什么情况下会出现呢?
==》应用退出
==》

三 、内存泄漏情况 + 解决方案(参考实例

在Java中,非静态内部类 & 匿名内部类都默认持有 外部类的引用

在Handler消息队列 还有未处理的消息 / 正在处理消息时,消息队列中的Message持有Handler实例的引用

由于Handler = 非静态内部类 / 匿名内部类(2种使用方式),故又默认持有外部类的引用

垃圾回收器(GC)无法回收MainActivity,从而造成内存泄漏

解决方案分析:
造成内存泄露的原因有2个关键条件:
存在“未被处理 / 正处理的消息 -> Handler实例 -> 外部类” 的引用关系
Handler的生命周期 > 外部类的生命周期

只要使之以上条件不满足即可(推荐 静态内部类 + 弱引用的方式):
静态内部类 不默认持有外部类的引用,从而使得 “未被处理 / 正处理的消息 -> Handler实例 -> 外部类” 的引用关系 的引用关系 不复存在。
==》将Handler的子类设置成 静态内部类
==》使用WeakReference弱引用持有Activity实例

亦可当外部类结束生命周期时,清空Handler内消息队列
不仅使得 “未被处理 / 正处理的消息 -> Handler实例 -> 外部类” 的引用关系 不复存在,同时 使得 Handler的生命周期(即 消息存在的时期) 与 外部类的生命周期 同步

@Override
protected void onDestroy() {
    super.onDestroy();
    mHandler.removeCallbacksAndMessages(null);
    // 外部类Activity生命周期结束时,同时清空消息队列 & 结束Handler生命周期
}

四、 HandlerThread(参考实例)

子线程中创建子线程------Looper
主线程发送子线程的消息------不行

子线程中创建自己的looper——HandlerThread
==》线程安全
==》封装获取looper
所以引入HandlerThread------方便使用/保证线程安全

五、 消息机制同步屏障——架构思维(类比车辆让救护车的优先处理)

MessageQueue中的next() 对应sychronized的第一个判断中msg.target == null 条件是指轮询执行同步消息直到找到一个异步消息为止?

 if (msg != null && msg.target == null) {
    // Stalled by a barrier.  Find the next asynchronous message in the queue.
     do {
         prevMsg = msg;
         msg = msg.next;
     } while (msg != null && !msg.isAsynchronous());
 }

同步消息--------MessageQueue.postSyncBarrier(),立刻执行
异步消息------一般的都是异步消息

六、应用场景——多线程Handler的应用

1.6.1 IntentService——子线程按顺序执行
==>onStart生命周期发送消息
==》ServiceHandler-----onHandleIntent
==>stopSelf——处理完Service自动停止,实现内存释放,不会导致内存问题

1.6.2 Fragment生命周期管理
attachFragment
commit
destachFragment

1.6.3 Glide生命周期管理
==》Glide.with(context).from().to()
context不能用fragment.getApplicationContext,因为生命周期会一直执行,而用fragment则会更具fragment的生命周期加载,节省内存
==》多次判空问题

七、面试题

1、一个线程有几个Handler
==》n个,可随时创建

2、一个线程有几个Looper,如何保证
==》1个,prepare中有添加判断

3、Handler什么情况下有内存泄漏?而其他内部类没有此现象
==》内部类持有外部类对象,当内部类持有外部类引用为被释放时------static
==》生命周期问题

4、为何主线程可 new Handler ,若想在子线程 new Handler要如何操作?
==》因为ActivityThread中有prepareMainLooper
==》子线程必须有prepare再looper

5、子线程中维护的Looper,消息队列无消息时的处理方案?有什么作用?
==》Looper.quit() :唤醒+
----- Message处理是 生产者消费者设计模式
------ 没有阻塞设置,可以一直存Message,因系统也用,若设上限会导致系统卡机
---- 消息睡眠和唤醒机制 nativeWake()
==》next中msg == null时返回null
==》nativePollOnce message不到时间自动唤醒 + messageQueue为空无限等待

6、可存在多个Handler向MessageQueue中添加数据,但如何保证线程安全?
==》锁 enqueueMessage中有synchronized 内置锁
next()

7、Looper在主线程中死循环为什么没有导致界面的卡死?(参考资料

  • 导致卡死的是在Ui线程中执行耗时操作导致界面出现掉帧,甚至ANR,Looper.loop()这个操作本身不会导致这个情况。
  • 有人可能会说,我在点击事件中设置死循环会导致界面卡死,同样都是死循环,不都一样的吗?Looper会在没有消息的时候阻塞当前线程,释放CPU资源,等到有消息到来的时候,再唤醒主线程。
  • App进程中是需要死循环的,如果循环结束的话,App进程就结束了。

八、IdleHandler介绍?

Hanlder空闲时处理空闲任务的一种机制。
应用场景:

  • MessageQueue没有消息,队列为空的时候。
  • MessageQueue属于延迟消息,当前没有消息执行的时候。

不会发生死循环,MessageQueue使用计数的方法保证一次调用MessageQueue#next方法只会使用一次的IdleHandler集合。

九、封装后的实现

十、封装后的异步任务实现

极化码(Polar Code)是由土耳其科学家Erdal Arıkan在2009年提出的一种新型纠错编码技术。它通过利用信道的极化现象,将虚拟信道分为误码率接近0和接近1/2的两类。在编码设计中,数据被放置在误码率极低的信道上,从而实现高效的数据传输。极化码的主要优势在于其理论编码容量能够达到香农限,并且构造方法较为简单。 MATLAB是一种功能强大的数学计算和编程工具,广泛应用于科学研究和工程领域。在极化码的研究中,MATLAB可用于构建编码和解码算法,模拟数据在不同信道条件下的传输效果,验证理论性能,并优化相关参数。 SC(Successive Cancellation,逐位取消)译码是极化码的基本解码方法。它从最可靠的比特开始,依次解码每个虚拟信道,且每个比特的解码结果会影响后续比特的解码,因为它们之间存在依赖关系。虽然SC译码的实现较为简单,但其计算复杂度较高,随着码长的增加,解码时间会线性增长。 SCL(Successive Cancellation List,逐位取消列表)译码是SC译码的改进版本。它通过引入列表机制,同时处理多个路径,从而增强了错误校正能力,并在一定程度上降低了错误率。与SC译码相比,SCL译码虽然需要消耗更多的计算资源,但能够提供更好的性能。 一个完整的MATLAB仿真资源通常包含以下内容: 编码模块:用于实现极化码的生成,包括码字构造和极化矩阵操作等。 信道模型:用于模拟各种通信信道,例如AWGN(加性高斯白噪声)信道或衰落信道。 SC/SCL译码模块:包含SC译码和SCL译码的算法实现。 误码率(BER)计算:通过比较发送和接收的码字,计算误码率,以评估编码性能。 性能曲线绘制:绘制误码率与信噪比(SNR)之间的关系曲线,展示不同译码策略的性能差异。 使用说明:指导用户如何运行仿真,理解代码结构,以及如何调整参数以进行自定义实验。 代码注
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值