一、源码解析:
本文主要针对Handler机制原理进行源码分析,如果对Android的Handler机制不熟悉的可以查看Android Handler机制。
Android消息机制的本质是一个线程开启循环模式持续监听并依次处理其他线程给它发的消息。
总结来说,Handler是Android中引入的一种让开发者参与处理线程中消息循环的机制。那这种机制如果仅仅通过一个Handler是无法完成任务的,还需要Looper、MessageQueue、Thread等的协作才能完成这一整套的异步消息循环处理机制。这几个类之间的关系可以通过下图(图片引自孙群)来说明:
首先需要有一个Thread,Looper和MessageQueue都构建在Thread之上,Handler又构建在Looper和MessageQueue之上,通过Handler间接地与下面这几个类进行合作,来完成这一整套的异步消息循环处理机制。
下面我们通过源码(API 25)一步一步的进行分析:
1、Looper:
【在主线程中调用Looper.prepare() Looper.loop()就会把当前线程变为Looper线程(无线循环不退出)】
Looper是一个final类,主要的方法有两个prepare()、loop(),是MessageQueue的管理者,每个MessageQueue都不能脱离Looper而存在,同时一个Looper对象跟一个线程相关联,主线程有默认的Looper,而子线程没有,但可通过Looper.prepare()创建:
<1> prepare()方法:
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
//注释2
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
//注释1
sThreadLocal.set(new Looper(quitAllowed));
}
注释1:
prepare方法主要生成一个自身Looper对象,并将其放置ThreadLocal(可以在一个线程中存储变量)对象中。
注释2:
判断了ThreadLocal.get()是否为空,如果不为空的话就抛出异常。这也说明,prepare方法不能调用两次,即一个线程中的Looper只能有一个实例对象。
再往下看Looper的构造函数如下:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
可以看到,Looper的构造函数中,生成了一个MessageQueue(消息队列)对象和当前Thread对象。
<2> loop()方法:
public static void loop() {
//注释1
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
//注释2
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
//......此处省略n行代码......
try {
msg.target.dispatchMessage(msg);
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
//......此处省略n行代码......
msg.recycleUnchecked();
}
}
注释1:
通过myLooper()方法可以从之前存储到ThreadLocal实例中的当前Looper对象me,源码如下:
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
如果me为空,抛出异常,通过异常可以得知,在执行loop()方法之前,我们必须先执行prepare()方法。
然后将该Looper对象构造函数中生成的MessageQueue对象mQueue赋值给本地MessageQueue对象queue。
注释2:
这里是一个无限for循环,通过MessageQueue的next()的方法来不断的获取已存储到MessageQueue的Message对象msg。如果取出的一条msg是null,那么便阻塞循环。如果msg不为空,那么执行msg.target.dispatchMessage(msg)方法来分发消息。这里的target其实就是Handler对象。
看到这里大家可能要问:
Q:为什么主线程不会因为Looper.loop()里的死循环卡死 + 是否会消耗cpu资源?(参考)
A:这里插入一点知识,做一下简要说明: Android应用程序的主线程在进入消息队列的循环过程前,会在内部创建一个Linux管道(pipe/epoll)【其实是创建了一个Binder线程】,这个管道的作用是Android 应用程序的主线程在消息队列为空时可以进入空闲等待状态,并且使得应用程序的消息队列有消息处理时唤醒应用程序的主线程。
详细来说就是:当主线程的MQ没有消息时,就会阻塞在loop() queue.next()的nativePollOnce方法里,这时候主线程会释放cpu资源进入休眠状态,直到有下一个消息来唤醒主线程,通过往pipe管道写端写入数据来唤醒主线程工作。
所以说,线程大多数都是出于休眠状态,并不会消耗大量cpu资源。
如上分析,我们可以对Looper进行个小总结:
1、通过prepare方法,可以与当前线程绑定,保证一个线程中只有一个Looper实例,一个Looper实例中,只有一个MessageQueue实例。
2、通过loop方法,可以无限循环地从MessageQueue中拿取Message对象,如果消息不为空,则交给Handler的dispatchMessage方法来派送消息。到此我们便可以顺水推舟来查看Handler的dispatchMessage源码,来查看到底Handler是如何来发送消息的。
2、MessageQueue:
【每一个Looper线程都会维护一个MessageQueue】
下面会多次谈到MessageQueue,所以先提前说下:
MessageQueue是一种数据结构,即我们常说的消息队列,用来存放消息,需要注意的是每个线程最多只能由一个MessageQueue对象。MessageQueue有两个比较重要的方法:enqueueMessage()与next()方法。enqueueMessage()方法主要用于将消息(Message)存放入消息队列MessageQueue中;next()方法主要是从消息队列中阻塞式的取出一个Message。创建线程的时候,并不会主动创建MessageQueue,需要一个Looper对象对MessageQueue进行管理。创建主线程的时候,会默认创建一个Looper,在Looper对象的创建,将自然创建一个MessageQueue;而在子线程的创建中,并不会自动创建Looper,所以也就不会创建MessageQueue对象,在调用Looper.prepare()的时候才会自动创建该线程的消息队列。
enqueueMessage:
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {//msg.target就是发送此消息的Handler
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {//表示此消息正在被使用
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
if (mQuitting) {//表示此消息队列已经被放弃了
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when;//将延迟时间封装到msg内部
Message p = mMessages;//消息队列的第一个元素
boolean needWake;
if (p == null || when == 0 || when < p.when) {
//如果此队列中头部元素是null(空的队列,一般是第一次),
//或者此消息不是延时的消息,则此消息需要被立即处理,
//此时会将这个消息作为新的头部元素,并将此消息的next指向旧的头部元素,
//然后判断如果Looper获取消息的线程如果是阻塞状态则唤醒它,让它立刻去拿消息处理.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
//如果此消息是延时的消息,则将其添加到队列中,
//原理就是链表的添加新元素,按照when,也就是延迟的时间来插入的,
//延迟的时间越长,越靠后,这样就得到一条有序的延时消息链表,取出消息的时候,延迟时间越小的,就被先获取了。
//插入延时消息不需要唤醒Looper线程。
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {//唤醒线程
nativeWake(mPtr);
}
}
return true;
}
由以上源码可以看出,enqueueMessage方法主要有两个目的:
- 插入消息到消息队列
- 唤醒Looper中等待的线程(如果是及时消息并且线程是阻塞状态)
next:
Message next() {
final long ptr = mPtr;
if (ptr == 0) {
//从注释可以看出,只有looper被放弃的时候(调用了quit方法)才返回null,mPtr是MessageQueue的一个long型成员变量,关联的是一个在C++层的MessageQueue,阻塞操作就是通过底层的这个MessageQueue来操作的;当队列被放弃的时候其变为0。
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
//阻塞方法,主要是通过native层的epoll监听文件描述符的写入事件来实现的。
//如果nextPollTimeoutMillis=-1,一直阻塞不会超时。
//如果nextPollTimeoutMillis=0,不会阻塞,立即返回。
//如果nextPollTimeoutMillis>0,最长阻塞nextPollTimeoutMillis毫秒(超时),如果期间有程序唤醒会立即返回。
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
//msg.target == null表示此消息为消息屏障(通过postSyncBarrier方法发送来的)
//如果发现了一个消息屏障,会循环找出第一个异步消息(如果有异步消息的话),所有同步消息都将忽略(平常发送的一般都是同步消息)
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// 如果消息此刻还没有到时间,设置一下阻塞时间nextPollTimeoutMillis,进入下次循环的时候会调用nativePollOnce(ptr, nextPollTimeoutMillis)进行阻塞;
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
//正常取出消息
//设置mBlocked = false代表目前没有阻塞
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
msg.markInUse();
return msg;
}
} else {
//没有消息,会一直阻塞,直到被唤醒
nextPollTimeoutMillis = -1;
}
if (mQuitting) {
dispose();
return null;
}
//...
//此处有省略IdleHandler相关代码
//...
pendingIdleHandlerCount = 0;
nextPollTimeoutMillis = 0;
}
}
由next()源码可知:
- 当首次进入或所有消息队列已经处理完成,由于此刻队列中没有消息(mMessages为null),这时nextPollTimeoutMillis = -1 ,然后会处理一些不紧急的任务(IdleHandler),之后线程会一直阻塞,直到被主动唤醒(插入消息后根据消息类型决定是否需要唤醒)。
- 读取列表中的消息,如果发现消息屏障,则跳过后面的同步消息。
- 如果拿到的消息还没有到时间,则重新赋值nextPollTimeoutMillis = 延时的时间,线程会阻塞,直到时间到后自动唤醒
- 如果消息是及时消息或延时消息的时间到了,则会返回此消息给looper处理。
通过enqueueMessage和next两个方法的分析我们不难得出:
- 消息的入列和出列是一个生产-消费者模式,Looper.loop()在一个线程中调用next()不断的取出消息,另外一个线程则通过enqueueMessage向队列中插入消息,所以在这两个方法中使用了synchronized (this) {}同步机制,其中this为MessageQueue对象,不管在哪个线程,这个对象都是同一个,因为Handler中的mQueue指向的是Looper中的mQueue,这样防止了多个线程对同一个队列的同时操作。
3、Handler:
【用于同一个进程的线程间通信】
我们直接来看下Handler的diapatchMessage方法:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
//注释1
handleCallback(msg);
} else {
if (mCallback != null) {
//注释2
if (mCallback.handleMessage(msg)) {
return;
}
}
//注释3
handleMessage(msg);
}
}
首先判断了msg.callback方法是否为空,这个msg.callback是什么呢。首先我们来回顾下Handler在子线程中发送消息的较常使用的两种方式。
第一种是:
mHandler.sendMessage(msg);
第二种就是:
mHandler.post(new Runnable() {
@Override
public void run() {
//主线程UI更新
}
});
通过查看post源码:
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
上面注释1中的msg.callback即是通过post传递进来的Runnable对象。
到这里我们就比较清晰了。首先Handler发送消息的时候,首先检查是否有mHandler通过post方法传递过来了Runnable对象,如果有,则直接回调该Runnable的run()方法,如下:
private static void handleCallback(Message message) {
message.callback.run();
}
我们在接着dispatchMessage方法往下看,如果msg.call为空,那么此时注释2处出现了个mCallback,那么这个mCallback又为何物呢。
我们看下Handler的构造函数,其中有一个:
public Handler(Callback callback) {
this(callback, false);
}
public Handler(Callback callback, boolean async) {
//.....此处省略n行代码......
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
//注释2
mQueue = mLooper.mQueue;
//注释1
mCallback = callback;
mAsynchronous = async;
}
由构造函数的注释1可以得知,mCallback方法是由Handler内部的Callback赋值过来的。此Handler构造函数使用方式如下:
private static Handler mHandler = new Handler(new Callback() {
@Override
public boolean handleMessage(Message msg) {
//更新UI操作
return true;
}
});
通过上面dispatchMessage方法注释2处得知,如果此handlerMessage返回true,则直接调用完此回调后终止。否则继续往下执行,我们再往下看注释3,此处调用的就是我们常用的Handler本身的handleMessage方法,使用方式如下:
private static Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
//更新UI操作
}
};
至此通过对上面dispatchMessage方法注释1、2、3的分析,我们可以做一个小结:
即为了能够使Handler处理Message,我们大致有三种实现方式:
<1>通过Hander的post方法,直接传递一个Runnable对象。
<2>向Handler的构造函数中传递一个Handler.Callback对象,并实现其handleMessage方法。
<3>无需向Handler构造函数中传递任何对象,只需重写Handler的handleMessage方法。
至此,我们再回过头来看一下上面说到的发送消息的两种方式sendMessage与post方式,通过查看源码可知两者最终都会走到如下方法:
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
进而进入到sengMessageAtTime方法
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
//注释1
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);
}
通过注释1得知,mQueue赋值给了本地queue,并作为了enqueueMessage的参数传入。此mQueue是哪个消息队列呢。通过上面Handler构造函数注释2的源码
mQueue = mLooper.mQueue;
可知,此消息队列即是原Looper中生成的消息队列(mQueue)。至此Handler、Looper、MessageQueue均已关联起来。
我们接着往下看enqueueMessage方法:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
//注释1
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
注释1可知,将msg.target绑定为当前的Handler。继续往下看,再通过Handler绑定的MessageQueue,通过调用其enqueueMessage方法,将消息排列入MessageQueue。
以上就是异步消息处理机制的源码解析,是不是对整个机制有所了解了呢,欢迎评论交流。
二、典型问题记录:
Q:一个线程中可以有几个Looper、MessageQueue、Handler?
A:只有一个Looper、MessageQueue,可以有多个Handler。
Q:如何理解同步屏障的概念以及同步消息处理?
A:为了优先执行异步 Message ,如:渲染 UI 、响应用户操作等消息因此设计了同步障碍这个机制。当有异步 Message 需要优先执行时,先向 MessageQueue 的头部插入一条 target(即处理消息的Handler) 为空的消息(设置同步障碍),并将需要执行的 Message 的是否是异步消息的标志设置为 true(isAsynchronous()返回值为 true)。在后续的 next() 方法中,当碰到同步障碍时就会忽略同步消息,选择性的优先执行异步消息。
Q:Looper死循环为什么不会导致应用卡死,会耗费大量资源吗?
A:这里涉及到 Linux pipe/epoll机制,简单说就是在主线程的 MessageQueue 没有消息时,便阻塞在 loop 的 queue.next() 中的 nativePollOnce() 方法里,此时主线程会释放 CPU 资源进入休眠状态,直到下个消息到达或者有事务发生,通过往 pipe 管道写端写入数据来唤醒主线程工作。这里采用的 epoll 机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。
Q:如何解决Handler造成的内存泄漏问题?
Q:一个线程中有两个Hander,共用一个Looper,其中一个Hander发送消息,另一个Hander是否能收到消息?
A:不能。
可通过Handler的enqueueMessage:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
及Looper的loop方法中的代码:
msg.target.dispatchMessage(msg);
得知,发送的消息与其Handler已绑定。
Q:通过方式1、2、3,相同条件下哪个会优先接受到消息?
//方式1
new Handler().post(new Runnable() {
@Override
public void run() {
}
});
//方式2
Handler mHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
return false;
}
});
//方式3
Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
A:方式1 > 方式2 > 方式3
参考:
Android 消息处理机制(Looper、Handler、MessageQueue,Message)
【鸿洋】Android 异步消息处理机制 让你深入理解 Looper、Handler、Message三者关系
Android 中Message,MessageQueue,Looper,Handler详解+实例 - 泡在网上的日子