Android消息传递机制Handler完全解析之源码解析
Android消息传递机制Handler完全解析之源码解析
本文将帮助大家梳理分析Handler的源码过程。
分析Handler源码有很多种思路,网上也有很多优秀的文章,
有的是分析Handler、Looper、MessageQueue三者的主要源码,
有的是以其中一个问题分析里面的源码,比如主线问题,如何延时工作问题等等。
本文将带着下面几个最主要的问题,帮大家梳理Handler里面的源码过程:
1、主线问题,Handler从发送sendMessage到接收回调handlerMessage的实现过程是怎么样的?
2、Looper、MessageQueue是什么时候创建的?
3、Looper是循环者,它是如何实现循环的?
4、MessageQueue是如何实现消息管理的?
5、sendMessageDelay是如何实现消息延迟的?
Handler外部接触的关键类有Handler、Looper、MessageQueue、Message,他们的作用如下:
Looper 消息轮训器,负责消息取出
MessageQueue 消息暂存队列,负责消息存储
Message 消息对象,消息本身
Handler 收发消息工具,发送和接收消息
这里我们可以看出Handler收发过程并不能对Message进行处理,当然接收到后进行的处理不算。
MessageQueue负责接收管理消息,并对消息进行排序,真正负责取出消息的是Looper。
整个收发消息的流程其实就是:
1、Handler使用sendMessage发送消息
2、消息传递到MessageQueue,并排序
3、Looper不断从MessageQueue获取消息,并发送給Handler
4、Looper发送消息給Handler后,在handlerMessage回调中就能接收到消息。
完整流程图如下:
一、主线问题,Handler从发送sendMessage到接收回调handlerMessage的实现过程是怎么样的?
1、Handler发送消息过程
Handler收发消息很简单就几句代码,比如:
Handler handler = new Handler(looper) {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
//接收消息
}
};
//发送消息
handler.sendEmptyMessage(1);
但是Handler发送消息有很多api方法,他们有什么区别呢?
Handler发送消息大概有如下几种:
1.post(Runnable)
2.postAtTime(Runnable,long)
3.postDelayed(Runnable,long)
4.sendEmptyMessage(int)
5.sendMessage(Message)
6.sendMessageAtTime(Message,long)
7.sendMessageDelayed(Message,long)
总结起来就是有两大类,
一类是在需要传入Runnable,Runnable里面的线程其实就是类似handleMessage直接接收数据,
并且没有数据传递,发送和接收都在一个地方写了;
另一类就是是我们常用的,发送和接收分开写。
关于post和非post发送区别,可以看后面源码解析过程。
每一大类里面又有三种不同的方式,一种是立即发送,一种是在某段时间发送,一种是延时时间多久发送。
立即发送比较好理解:如果当前没有消息就立即发送,如果有消息放到正在处理的消息的后面
某段时间发送:使用绝对时间值,定义发送时机,比如2020-10-01 8:08:00有一个固定的毫秒数
延迟多久发送:在当前是时间延迟多少毫秒发送
下面是发送的源码:
public class Handler {
// 这里先介绍post发送消息的三个方法,实际是把Runnable传入Message里面,最终也是调到非post方法中,代码如下:
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
public final boolean postAtTime(Runnable r, long uptimeMillis)
{
return sendMessageAtTime(getPostMessage(r), uptimeMillis);
}
public final boolean postAtTime(Runnable r, Object token, long uptimeMillis)
{
return sendMessageAtTime(getPostMessage(r, token), uptimeMillis);
}
//接着看常用的发送消息的过程:
// 直接发消息,实际是调到延迟0秒发消息
public final boolean sendEmptyMessage(int what)
{
return sendEmptyMessageDelayed(what, 0);
}
// 延迟发消息,实际是在当前时间加上延迟时间发消息,时间是使用绝对时间发消息
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
// 固定时间发消息,最终把消息发送給MessageQueue处理
//上面的五个方法,最终都是这个入口,把数据发给MessageQueue
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
//MessageQueue对消息就行排序处理,到这里Handler发送消息的过程就传递到MessageQueue了
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
/**
* Handle system messages here.
* 回调处理
* dispatchMessage是Handler給自己发送消息的方法,但是这个方法是在Looper中调用的
* Looper调用dispatchMessage,经过handleMessage让自己得到回调消息。
* Handler还有一个使用Callback对象监听Message回调信息的方式,这个也用得少。
*/
public void dispatchMessage(Message msg) {
//callback一般是不用的,所有不会进入
if (msg.callback != null) {
handleCallback(msg);
} else {
//mCallback默认为空,所有不会进这里
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
//正常都是直接到这里,这里之后,Handler就能收到回调了
handleMessage(msg);
}
}
//Handler的内部接口对象,也可以接收Message,可以在创建Handler的时候传入
public interface Callback {
public boolean handleMessage(Message msg);
}
}
到这里发送消息的过程已经非常清晰了吧,都是使用绝对时间来决定什么时候发送消息,
想想也是,MessageQueue要管理里面的Message,那肯定是需要一样绝对的元素,就是绝对时间啦。
接下来就是MessageQueue分析enqueueMessage方法里面如何对Message的处理了。
2、MessageQueue接收消息,管理消息、排序过程
下面就是以enqueueMessage方法实现的内容,展开逻辑分析,这个方法不是一下子就能看懂的,需要慢慢来分析。
这里要明确一点就是这里只是管理消息,并不负责消息发送給Handler,不然你找半天了找不到出去的路。
发送消息要到Looper对象中进行分析!
管理消息,就是说給当前传入的消息和以前的消息給他们重新排个序,就是这么简单。
MessageQueue有两个重要方法,一个是消息存储enqueueMessage,另一个是消息取出next()
public final class MessageQueue { // final类不能被继承哈
// 核心方法,主要代码如下
boolean enqueueMessage(Message msg, long when) {
msg.markInUse(); //标志该Message已经处理
msg.when = when; //获取该消息的绝对时间
Message p = mMessages; //上一次处理过的消息对象,也是最前面的一个数据,首先要处理的数据
boolean needWake; //是否需要唤醒线程,这个涉及到了底层硬件
//看第一个条件和里面的逻辑,一下子就有点懵逼了,不过一步步来还是不难的
//首先,p == null,mMessages第一个处理的消息是空的,所有会进入条件
//其次,when ==0 这个条件一般不会符合,已经消息对应的时间不会为0
//最后when <p.when 表示该Message的时间比上一个处理的时间还早,排在上一次处理的数据前面
if (p == null || when == 0 || when < p.when) {
//把上一个即将处理的数据,变成当前Message的next
msg.next = p;
//当前处理的对象为全局变量
mMessages = msg;
//判断是否需要唤醒线程
needWake = mBlocked; //mBlocked表现线程是否在阻塞状态,如果是阻塞状态,并且有消息,需要马上唤醒线程
} else {
//上面把数据插在前面,如果是插在中间就要做循环判断,寻找合适的位置了
//下面的才是大部分逻辑的数据,多个Message排序的主要代码
//1、定义临时Message对象prev变量
Message prev;
//for循环判断每一个Message的时间,并把传入的Message放到合适的位置
//这个过程可能有点绕,不懂算法的人可能看几遍都不懂,尽力而为吧
for (;;) {
//2、把上一个处理的数据放在最前面
prev = p;
//3、把下一个信息放到自己位置
p = p.next;
//4、如果p.next == null,说明没有下一个数据,不用继续循环排序了,放在最前面吧
//5、如果when < p.when,说明将要处理的消息的时间比下一个消息短,说明这个消息要放在p消息前面
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
//符合 when < p.when,说明先处理msg,再处理p,把p放到msg之后
msg.next = p; // invariant: p == prev.next
//把msg发到合适的位置,即p.next之前,p之后(上面循环中定义prev = p)
prev.next = msg;
}
//上面的for里面的循环逻辑确实会让人头晕,举个例子希望大家能理解一点,
//比如,有待处理的三个数据对应的时间是:n1 = 2; n2 =6; n3 = 8;数值表示延时的时长
//n1.next =包含 n2和n3,n2的next包含n3
//如何把时间为7插入到6和8直接?
//即:6对应的Message.next包含7、8
//简单的说就是从最短的时间开始比较,如果收集的消息的延时时长比这个消息小,这个消息就放在比较的消息前,并且结束循环
//(1)把7先和2判断不符合,接着和6判断也不符合,接着和8判断
//(2)发现7比8小,所以放在6后面,8前面
//(3)即在当前判断的p和p.next 插入msg,
//(4)变量替换过程为
保存变量过程prev = p , p = p.next,
替换变量过程msg.next = prev.next , p.next = msg
//到这里就完成了2:6:7:8 的排序,Looper对象就会从第一个开始取数据
//这个过程如果实在看不懂可以先跳过,以后慢慢理解也可以的
//线程休眠后,要继续处理消息才需要唤醒
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
}
由此可见,Message存储的数据,next表示下一个待处理的数据,
Message对象包含后面的所有数据,MessageQueue负责对数据进行管理,重新编排。
当然enqueueMessage方法,只是对Message排序的方法,
MessageQueue里面还有其他重要方法,后面数据取出也会调用到里面的方法,这个稍后介绍。
上面是Handler的发送和Message的管理,接下来就是分析Handler的回调是如何发生的了。
3、Looper管理消息以及选择发送消息过程
说到Looper,很多人可能对里面的过程并不了解,因为很多处理的开发者,甚至都没用过这个类。
这个looper类,在主线程会默认帮创建,不需要自己创建,如果是子线程回调数据,就要自己创建。
要分析Looper的管理消息和发送消息过程,就要分析Looper创建和工作的流程代码。
这里连带说一下Handler、Message、MessageQueue的创建。
Handler就不必描述了,直接new,如果需要接收回调消息,重写handlerMessage方法即可。
Message的创建可以new Message(),也可以使用Message.obtain(),第二种方法会多次复用同一个对象,减少内存使用。
MessageQueue的创建就麻烦多了,MessageQueue是在Looper中创建的。
Looper的创建使用的是Looper.prepare()方法;
Looper管理消息,回调消息的逻辑基本都在Looper.loop()里面。
Looper中最重要的两个方法就是对象创建prepare()和循环工作loop()了。
下面是Looper的主要代码:
public final class Looper { //不可继承的类哈
final MessageQueue mQueue; //只能初始化一次的对象,说明一个线程只能有一个Looper并且对应一个MessageQueue
final Thread mThread; //Handler内部数据处理,其实也是依赖线程在工作,只不过是一个固定的线程
//Looper对外暴露的创建Looper方法
public static void prepare() {
prepare(true);
}
//参数表示是否允许强制退出,一般是允许的
private static void prepare(boolean quitAllowed) {
//sThreadLocal.get()返回的是跟Looper绑定的对象,如果该对象已经创建,不能再调用prepare方法,否则会抛出异常。
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
//第一次调用prepare,就会创建Looper,并且把Looper跟ThreadLocal绑定
sThreadLocal.set(new Looper(quitAllowed));
}
//这里是私有方法,只能自身调用
//MessageQueue就是这里实例化的
//一些耗时处理都是在Thread中实现的
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
/**
* Looper的核心方法
*/
public static void loop() {
final Looper me = myLooper();
//要先调用Looper.prepare(),否则会报错,因为Looper没有实例化
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
// MessageQueue不必判空,以为创建Looper的时候,必定会创建MessageQueue
//下面就是不断取出Message,并且判断什么时候发送給Handler的逻辑了
for (;;) {
//取出队列最前面的消息,并且已经到时间处理的消息
Message msg = queue.next(); // might block,有可能在阻塞状态,比如待处理的都是延时消息
//这里的阻塞是硬件级别的,让把这个线程停下来,把cpu让出給其他线程做事情
//并不是普通的的Thread.sleep(XXX);这种是让整个线程数据在sleep过程啥都做不了,并且这个还占用cpu
//这个硬件级别的阻塞,是native方法实现的,并且唤醒后,会接着往下走
//如果没有消息,直接返回,退出循环
if (msg == null) {
return;
}
//...省去一些啰嗦的逻辑代码
//mSlowDispatchThresholdMs是Looper中一个不太重要属性,用于检测哪个消息处理超过某个值
//可以有set方法,设置一个标准值后,如果处理某个Message需要的时间比这个定义时间长,就会打印日志提示一下
final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
final long end;
try {
//消息分发,就这一句话
//msg.target就是获取Message对应的Handler
//调用Handler dispatchMessage,经过内部分发,然后就能在handleMessage收到Message消息了
msg.target.dispatchMessage(msg);
//记录发送该消息需要的时间
end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
}
//如果定义了消息处理时间标准值
if (slowDispatchThresholdMs > 0) {
final long time = end - start;//消息处理的时间
//如果消息处理的数据比预定的标准值大,则打印日志提醒一下
if (time > slowDispatchThresholdMs) {
Slog.w(TAG, "Dispatch took " + time + "ms on "
+ Thread.currentThread().getName() + ", h=" +
msg.target + " cb=" + msg.callback + " msg=" + msg.what);
}
}
//对该Message的缓存数据进行重置、清空
msg.recycleUnchecked();
}
}
}
主线程创建Looper方法在哪里呢?
Android的主线程,指的是Activity的主线程,这个线程是在Activity创建的时候自动创建的。
ActivityThread的代码:
public class ActivityThread{
public static void main(String[] args) {
/...
Process.setArgV0("<pre-initialized>");
Looper.prepareMainLooper(); //其实也是调用了prepare()方法,不过传入参数false,表示不可退出
Looper.loop();
}
}
下面介绍的是Handler出现机制里面最复杂的一个方法,也是最重要的一个方法,线程休眠也是在里面操作的。
Message的next()方法,这个方法是在Looper中调用的,用来取出即将处理的Message。
这里的操作有下面几方面:
1、该方法返回的Message是单个Message,不是MessageQueue管理的那个消息队列,返回的Message的Message.next会被置空
2、如果下一个消息是延时消息,那么会调用nativePollOnce方法休眠固定的延时的时间后自动唤醒
3、如果Handler没有消息处理,nativePollOnce传入-1参数,表示长久休眠,
直到下一个消息发过来为止,同时mBlocked = true;表示线程在休眠状态
4、后面当Handler继续有消息,会停止休眠,继续工作
代码如下:
public final class MessageQueue {
//该方法是取出,已经到时间处理的消息
//如果是延时消息会在里面进行阻塞
Message next() {
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0; //处理下一个消息需要等待的时间
for (;;) {
//native方法,让线程休眠一段时间,时间长度是到下一个消息发送的时间
//如果时间传入0不会休眠,传入-1会一直休眠,传入大于零的数据就是休眠该时间
//比如是一个不延时消息,会进入到下面,直接返回Message
//如果该消息是延时的,发现当前时间还不到消息处理的时间:now < msg.when
//计算出该消息还有多久才处理msg.when - now,然后休眠这段时间,休眠完成后,往下走就会返回消息
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
//获取当前时间
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// 往后遍历找到第一个异步的消息,一般消息都是同步顺序执行的,所有这个循环一般只进一次
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
// 该消息还没到处理时间
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
//该消息已经到时间了,可以马上回调处理
mBlocked = false;
//下面这个是在链表中减去已经处理的msg消息
if (prevMsg != null) { //不太明白,msg.next !=null但是msg为空的情况。
prevMsg.next = msg.next; //这里其实是把msg.next往前推一个位置,可以慢慢体会
} else {
//及时更新全局变量
mMessages = msg.next;
}
msg.next = null; //只返回单个Message,不返回整个链表数据
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse(); //标识该消息已经处理了
return msg;
}
} else {
// No more messages.
//消息为空,进入休眠状态
nextPollTimeoutMillis = -1;
}
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}
}
到这里Handler的主要源码和主要过程已经分析完成了,
当然还有一些相关变量没有进行详细分析,比如后面发送消息过来怎么再次唤醒的
但是经过上面代码的分析讲解,本文开始提出的几个问题都有思路了吧。
下面在总结一下:
代码图解:
问题总结:
1、主线问题,Handler从发送sendMessage到接收回调handlerMessage的实现过程是怎么样的?
(1)Handler通过方法enqueueMessage发送消息到MessageQueue中
(2)MessageQueue在方法enqueueMessage对传进来的Message进行排序管理,整理成链表Message
(3)Looper从MessageQueu中通过方法next()获取可以处理完成的信息,并通过dispatchMessage方法发送給Handler
(4)Handler经过判断处理后回调到handlerMessage()
到这里整个回调过程就完成了。
2、Looper、MessageQueue是什么时候创建的?
Looper和MessageQueue创建是用Looper.prepra()创建的,
并且主线程默认已经创建了,如果是Looper在子线程工作,就要自己调用这个方法创建。
3、Looper是循环者,它是如何实现循环的?
Looper循环主要依靠looper()方法,里面有个循环,循环中不断调用MessageQueue.next()方法
如果有消息可以处理就会收到,如果没有消息或者有延时消息,会在next()方法进行休眠
直到有消息或者延时时间到了,就会停止休眠,自动进入循环。
4、MessageQueue是如何实现消息管理的?
MessageQueue消息管理的逻辑都在enqueueMessage方法里面,
当Message传入,会重新对对Message链表进行排列处理
5、sendMessageDelay是如何实现消息延迟的?
延迟消息是通过方法nativePollOnce,让线程固定时间的休眠,休眠后让出cpu,直到休眠时间过了自动进入消息处理过程。
具体的逻辑代码都在MessageQueue的next()方法里面
扩展:如果Handler有几个延时消息,这是马上发送一个立即发送的消息怎么处理?
MessageQueue 存入消息后,发现有数据,会判断是否需要wake,如果要唤醒就调用nativeWake方法,就会马上进入工作状态。
这里的Handler源码分析,只是对主要问题进行代码分析,有些具体细节需要在自己琢磨,比如有些变量的作用,比如Looper和MessageQueu使用for (;;)做循环等等。
这个文章也有很源码文字比较多的介绍
https://blog.csdn.net/u011240877/article/details/72892321
还有些自己比较初级的Handler总结:
消息传递机制之Handler使用总结:https://blog.csdn.net/wenzhi20102321/article/details/53098943
Handler消息传送机制总结:https://blog.csdn.net/wenzhi20102321/article/details/52837834
Android消息传递机制Handler完全解析之1基础介绍:https://blog.csdn.net/wenzhi20102321/article/details/107888152
Android 多线程之HandlerThread 详解:https://blog.csdn.net/wenzhi20102321/article/details/82596297
共勉:感谢现在努力的自己。
推荐阅读
-
Android Handler之消息循环的深入解析
-
Android Handler之消息循环的深入解析
-
Android Studio 之 Android消息机制 之简单Demo --- 使用Handler和Message类来完成消息传递
-
Handler异步消息传递机制(四)Handler发送消息流程,源码(Android 9.0)彻底解析
-
Android消息通信机制Handler详解,Handler,Looper,MessageQueue,源码解析,讲解这几个类怎么配合工作的
-
Android消息机制三剑客之Handler、Looper、Message源码分析(一)
-
Android消息机制原理,仿写Handler Looper源码解析跨线程通信原理--之仿写模拟Handler(四)
-
Android Handler机制(三)----Looper源码解析
-
Android多线程(二)消息处理机制---Handler、Message、Looper源码原理解析
-
Handler机制之Message源码解析