【Android -- 多线程】Handler 消息机制

一、消息机制的简介

提起 Android 消息机制,想必都不陌生。其中包含三个部分:Handler,MessageQueue 以及 Looper,三者共同协作,完成消息机制的运行。

在 Android 中使用消息机制,我们首先想到的就是 Handler 。没错,Handler 是 Android 消息机制的上层接口。Handler 的使用过程很简单,通过它可以轻松地将一个任务切换到 Handler 所在的线程中去执行。通常情况下,Handler 的使用场景就是更新 UI 。

在子线程中,进行耗时操作,执行完操作后,发送消息,通知主线程更新 UI。这便是消息机制的典型应用场景。我们通常只会接触到 Handler 和 Message 来完成消息机制,其实内部还有两大助手来共同完成消息传递。

二、消息机制的模型
消息(Message)

需要传递的消息,可以传递数据;

消息队列(Message Queue)

消息队列,但是它的内部实现并不是用的队列,实际上是通过一个单链表的数据结构来维护消息列表,因为单链表在插入和删除上比较有优势。主要功能向消息池投递消息(MessageQueue.enqueueMessage)和取走消息池的消息(MessageQueue.next);

处理者(Handler)

消息辅助类,主要功能向消息池发送各种消息事件(Handler.sendMessage)和处理相应消息事件(Handler.handleMessage)

循环器(Looper)

不断循环执行(Looper.loop),从MessageQueue中读取消息,按分发机制将消息分发给目标处理者。

**消息机制的运行流程:**在子线程执行完耗时操作,当 Handler 发送消息时,将会调用MessageQueue.enqueueMessage,向消息队列中添加消息。当通过Looper.loop开启循环后,会不断地从线程池中读取消息,即调用MessageQueue.next,然后调用目标Handler(即发送该消息的Handler)的dispatchMessage方法传递消息,然后返回到Handler所在线程,目标Handler收到消息,调用handleMessage方法,接收消息,处理消息。

三、消息机制的源码解析

1.Looper

要想使用消息机制,首先要创建一个 Looper。
初始化 Looper

无参情况下,默认调用 prepare(true) ;表示的是这个Looper可以退出,而对于false的情况则表示当前Looper不可以退出。

每个线程中只能存在一个 Looper,Looper是保存在 ThreadLocal 中的。主线程(UI线程)已经创建了一个 Looper ,所以在主线程中不需要再创建 Looper ,但是在其他线程中需要创建 Looper 。每个线程中可以有多个 Handler ,即一个 Looper 可以处理来自多个 Handler 的消息。 Looper 中维护一个 MessageQueue ,来维护消息队列,消息队列中的 Message 可以来自不同的 Handler 。

不能重复创建 Looper,只能创建一个。创建 Looper,并保存在 ThreadLocal 。其中 ThreadLocal 是线程本地存储区(Thread Local Storage,简称为TLS),每个线程都有自己的私有的本地存储区域,不同线程之间彼此不能访问对方的 TLS 区域。

开启 Looper

loop() 进入循环模式,不断重复下面的操作,直到消息为空时退出循环:
读取 MessageQueue 的下一条 Message(关于next(),后面详细介绍);
把 Message 分发给相应的 target 。

当next()取出下一条消息时,队列中已经没有消息时,next()会无限循环,产生阻塞。等待MessageQueue中加入消息,然后重新唤醒。

主线程中不需要自己创建Looper,这是由于在程序启动的时候,系统已经帮我们自动调用了Looper.prepare()方法。查看ActivityThread中的main()方法,代码如下所示:

public static void main(String[] args) {

..........................

Looper.prepareMainLooper();

..........................

Looper.loop();

..........................

}

2.Handler

创建 Handler

对于 Handler 的无参构造方法,默认采用当前线程TLS中的 Looper 对象,并且 callback 回调方法为null,且消息为同步处理方式。只要执行的Looper.prepare()方法,那么便可以获取有效的 Looper 对象。

3. 发送消息

发送消息有几种方式,但是归根结底都是调用了sendMessageAtTime()方法。

在子线程中通过Handler的post()方式或send()方式发送消息,最终都是调用了sendMessageAtTime()方法。

就连子线程中调用Activity中的runOnUiThread()中更新UI,其实也是发送消息通知主线程更新UI,最终也会调用sendMessageAtTime()方法。

public final void runOnUiThread(Runnable action) {

if (Thread.currentThread() != mUiThread) {

mHandler.post(action);

} else {

action.run();

}

}

如果当前的线程不等于UI线程(主线程),就去调用Handler的post()方法,最终会调用sendMessageAtTime()方法。否则就直接调用Runnable对象的run()方法。

sendMessageAtTime()方法的作用很简单,就是调用MessageQueueenqueueMessage()方法,往消息队列中添加一个消息。

MessageQueue是按照Message触发时间的先后顺序排列的,队头的消息是将要最早触发的消息。当有消息需要加入消息队列时,会从队列头开始遍历,直到找到消息应该插入的合适位置,以保证所有消息的时间顺序。

4. 获取消息

当发送了消息后,在MessageQueue维护了消息队列,然后在Looper中通过loop()方法,不断地获取消息。上面对loop()方法进行了介绍,其中最重要的是调用了queue.next()方法,通过该方法来提取下一条信息。

nativePollOnce是阻塞操作,其中nextPollTimeoutMillis代表下一个消息到来前,还需要等待的时长;当nextPollTimeoutMillis = -1时,表示消息队列中无消息,会一直等待下去。

可以看出next()方法根据消息的触发时间,获取下一条需要执行的消息,队列中消息为空时,则会进行阻塞操作。

5. 分发消息

loop()方法中,获取到下一条消息后,执行msg.target.dispatchMessage(msg),来分发消息到目标 Handler 对象。

分发消息流程:

当Message的msg.callback不为空时,则回调方法msg.callback.run();

当Handler的mCallback不为空时,则回调方法mCallback.handleMessage(msg);

最后调用Handler自身的回调方法handleMessage(),该方法默认为空,Handler子类通过覆写该方法来完成具体的逻辑。

消息分发的优先级:

Message的回调方法:message.callback.run(),优先级最高;

Handler中Callback的回调方法:Handler.mCallback.handleMessage(msg),优先级仅次于1;

Handler的默认方法:Handler.handleMessage(msg),优先级最低。

相关推荐
安东尼肉店8 小时前
Android compose屏幕适配终极解决方案
android
2501_916007478 小时前
HTTPS 抓包乱码怎么办?原因剖析、排查步骤与实战工具对策(HTTPS 抓包乱码、gzipbrotli、TLS 解密、iOS 抓包)
android·ios·小程序·https·uni-app·iphone·webview
feiyangqingyun9 小时前
基于Qt和FFmpeg的安卓监控模拟器/手机摄像头模拟成onvif和28181设备
android·qt·ffmpeg
用户20187928316713 小时前
ANR之RenderThread不可中断睡眠state=D
android
煤球王子13 小时前
简单学:Android14中的Bluetooth—PBAP下载
android
小趴菜822714 小时前
安卓接入Max广告源
android
齊家治國平天下14 小时前
Android 14 系统 ANR (Application Not Responding) 深度分析与解决指南
android·anr
ZHANG13HAO14 小时前
Android 13.0 Framework 实现应用通知使用权默认开启的技术指南
android
【ql君】qlexcel14 小时前
Android 安卓RIL介绍
android·安卓·ril
写点啥呢14 小时前
android12解决非CarProperty接口深色模式设置后开机无法保持
android·车机·aosp·深色模式·座舱