什么是Android消息机制?从开发者使用的角度,消息机制只有一个上层接口,就是我们经常使用的Handler,通过Handler可以轻松的将任务切换到Handler所在的线程去执行,而最常见的做法相信每个开发者都用过,就是更新UI。Android的UI控件的操作只能在主线程(也叫UI线程)中执行,但我们经常在子线程或者线程池做一些耗时或者联网的工作,当工作完成我们希望在UI中做一些更新,比如关闭对话框、改变按钮显示等等。那么最常见也是比较容易的方法就是创建一个Handler对象,将操作在Handler的handleMessage函数中执行(也就是在主线程中),只需要在子线程发送消息,就可以轻松切换到主线程。当然我们也可以看出Handler不是专门来做此操作才出现的,只是非常适合这个情况,那么Handler是如何轻松切换到主线程的呢?为什么Handler就可以在主线程中创建呢?我们从源码的角度来做一次全面的分析。
一、消息机制的角色分析
首先我们介绍一下消息机制的几位主人公,正是有它们的通力合作,消息机制才能正常的运行:
1、Handler:处理器,负责的内容是消息的发送和具体处理流程,一般使用时由开发者重写handleMessage函数,根据自定义的不同message做不同的UI更新操作;
2、Message:消息对象,代表一个消息实体,存放消息的基本内容;
3、MessageQueue:消息队列,按顺序存放消息实体,由单链表实现,被Looper(4)所使用(一个Looper具有一个消息队列);
4、Looper:循环器,也是我们的消息机制的主角,一个线程至多具有一个并且与线程一一对应(如何理解等会来说),负责的内容是不断从消息队列取出放入的消息并且交由消息对应的Handler进行处理(Handler的具体handleMessage操作);
5、ThreadLocal:线程本地数据,是一个线程内部的数据存储类,为每个线程存储属于自己的独立的数据。
二、消息机制总览
我们来用最简短的语言说明一下消息循环的整个过程,有个整体性的认识,之后再进行逐一的进行源码分析。
1、首先,我们知道ThreadLocal是线程内部的数据存储类,一个线程对应一个自己独一无二的数据,而我们的主角Looper就是这样一个对象,每个线程都可以有自己独一无二的Looper,而Looper自己具有一个属于自己的MessageQueue,它不断地从MessageQueue中取Message(注意,Looper的代码是运行在自己对应的线程中的),如果MessageQueue中没有消息,Looper只会不断地循环尝试取消息(阻塞)。
2、这时,我们在主线程创建了Handler对象,它需要它所在的线程(这里是主线程)所拥有的Looper对象(也就是说,没有Looper的线程创建不了Handler,后面我们也会看到具体代码),一旦我们用Handler发送了消息(可以在别的线程中,这里假设在某个子线程中),Handler就会把这个消息放入自己拥有的Looper对象的属于这个Looper对象的MessageQueue中(这句有点拗口,就是放入Looper的MessageQueue中)。
3、我们已经知道Looper会不断地尝试从自己的MessageQueue中取出消息,我们刚刚放入的消息立刻被Looper取出来了,它得到了消息就执行了发出消息的Handler(也就是2过程我们所创建的)的消息处理函数handleMessage,我们编写的UI更新操作就在Looper对象的代码中执行了,而我们刚才也说了这个Looper运行在我们创建Handler的线程中的,也就是主线程(UI线程),那这样一来就达到了我们的目标,成功的把代码执行从子线程切换到了主线程中,这个过程我们也就有个总览了,下面我们进行源码的一步一步分析。
三、MessageQueue与Looper
1、MessageQueue
MessageQueue的作用我们已经大致了解了,就是一个可以存放消息和可以取消息的消息队列,那么它主要的两个操作就是插入和读取,分别对应的函数就是enqueueMessage函数和next函数,分别是用作插入消息和读取并删除消息,我们来看一下具体的实现源码:
(1)enqueueMessage方法
final boolean enqueueMessage(Message msg, long when) {
if (msg.isInUse()) {
throw new AndroidRuntimeException(msg + " This message is already in use.");
}
if (msg.target == null) {
throw new AndroidRuntimeException("Message must have a target.");
}
boolean needWake;
synchronized (this) {
if (mQuiting) {
return false;
}
msg.when = when;
Message p = mMessages;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
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;
}
}
if (needWake) {
nativeWake(mPtr);
}
return true;
}
首先,进行消息的合理性的检查,防止两个非法情况:消息已经在使用中和消息没有target(也就是我们发出消息的Handler,它是作为target最终要来真正做相应处理);之后,进行单链表的插入操作。插入完成之后,我们会看到这样一个操作:
if (needWake) {
nativeWake(mPtr);
}
这个needWake和本地方法nativeWake(mPtr)
有一些关系,同样有关的还有插入过程的一些if语句,但这里对我们了解整体机制没有非常重要的作用,本地方法也非常复杂,要与next方法中的本地方法一起讲解,在讲完整体机制后我们再详细介绍。
(2)next方法
省略了部分非关键代码(用于一些优化操作)
final Message next() {
int pendingIdleHandlerCount = -1; //空闲处理器的个数
int nextPollTimeoutMillis = 0; //等待时间
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(mPtr, nextPollTimeoutMillis); //本地方法调用
synchronized (this) {
if (mQuiting) {
return null;
}
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
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;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (false) Log.v("MessageQueue", "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
...
}
next的作用就是为了获取队列中的被放入的消息,我们先介绍一下变量mMessages,这个其实就是MessageQueue的链表头,也就是最先需要处理的消息;接着我们可以看出next是一个阻塞方法,如果没有寻找到需要处理的消息,即msg == null
的情况,那么next方法一直在无限循环中;如果寻找到了位于链表头的消息,还要判断是否处于需要执行的时间,如果这些都满足了要求,就把message取走(移除)并返回,关键代码如下:
// Got a message.
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (false) Log.v("MessageQueue", "Returning message: " + msg);
msg.markInUse();
return msg;
这里有本地方法上面我们也说到了,其实涉及到了底层的一些处理,其中最重要的就是这句函数:
nativePollOnce(mPtr, nextPollTimeoutMillis);
并且我们还有两个变量的作用没有讲解,就是next开头创建的两个变量:
int pendingIdleHandlerCount = -1; //空闲处理器的个数
int nextPollTimeoutMillis = 0; //等待时间
这两个变量的作用我们也没有讲解,他们与这个本地方法有着非常重要的联系,我们会在底层源码分析中进行介绍,在分析完整体机制之后会着重分析MessageQueue的底层部分,先不着急,我们现在只需要确定MessageQueue是作为一个存放消息的消息队列就行了,但实际上它在消息循环中的地位非常的特殊,讲完整体框架我们再细看这个类的实现,从整体来看我们只需要大致了解它添加、读取的作用即可。
2、Looper
Looper作为我们消息处理的主人公,在消息处理中起了非常大的作用,我们来好好看一下Looper的具体实现。Looper拥有一个属于自己的MessageQueue,并且一直查看其中是否有新的消息,如果有就会立刻进行处理,否则就会一直阻塞在循环中。
(1)首先看一看Looper拥有的MessageQueue
//成员变量之一
final MessageQueue mQueue;
//私有构造函数
private Looper() {
mQueue = new MessageQueue();
mRun = true;
mThread = Thread.currentThread();
}
这里可以看出,每个Looper对象都有自己的MessageQueue,在构造函数中初始化。
(2)Looper的初始化过程
既然Looper是一个与线程有关的变量,它在哪里初始化呢?前面我也说过,一个线程没有Looper就不能创建Handler,那我们平时怎么没接触到Looper的初始化呢?因为我们那时在主线程中创建的Handler,而主线程本身就有一个主消息循环(MainLooper),就不用我们自己去创建了。我们来看一看如何在子线程中初始化Looper,开启属于自己的消息循环:
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();//给线程创建一个消息循环
mHandler = new Handler() {
public void handleMessage(Message msg) {
// process incoming messages here
}
};
Looper.loop();//开启消息循环
}
}
这里用到了两个颇为关键的方法,prepare和loop,我们来详细的看一下
2.1 prepare方法
//静态成员变量之一
private static final ThreadLocal sThreadLocal = new ThreadLocal();
//静态方法prepare
public static final void prepare() {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper()); //放入新的Looper对象(对应当前线程)
}
首先我们要明确一下刚才说的另一个小主角:ThreadLocal,它的作用就是为每个线程存储属于每个线程自己的数据,这里的sThreadLocal就是为每个线程存储属于自己的Looper变量,prepare方法做了什么呢?首先判断当前线程是否已经有Looper变量,如果有就报异常,因为每个线程不能有多个Looper对象;然后为当前线程创建属于自己的Looper对象,并保存到sThreadLocal变量中,这样当前线程就具有了属于自己的Looper。
2.2 loop方法
public static void loop() {
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;
...
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
msg.target.dispatchMessage(msg);
msg.recycle();
}
}
终于看到loop方法的真面目,首先我们要明确一个重要的观点,loop函数是运行在Looper所在的线程中的,比如我们的loop就是运行在我们的LooperThread中,而主线程得loop方法就是运行在主线程中。我们来慢慢看loop的操作:
步骤1、得到当前所在线程的Looper对象,调用了函数myLooper
final Looper me = myLooper();
这个myLooper的作用就是获取当前线程的Looper对象,相信大家估计都知道是怎么实现的了:
//静态成员变量之一
private static final ThreadLocal sThreadLocal = new ThreadLocal();
//静态方法
public static final Looper myLooper() {
return (Looper)sThreadLocal.get();
}
其作用就是获取当前线程的Looper对象,从代码中我们可以很清楚的看到,从静态成员变量ThreadLocal中获取当前线程的Looper对象,我们接着往下看。
步骤2:
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
这里做一个简单的判断,如果没有调用之前的Looper.prepare()
进行Looper对象的创建,这里就会触发异常,下面进入关键的loop阶段:
步骤3:
首先获取了当前线程Looper对象的MessageQueue,紧接着开始循环,在循环中不断进行获取message的尝试:
final MessageQueue queue = me.mQueue;
...
for (;;) {
Message msg = queue.next(); //阻塞方法,可能阻塞
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
msg.target.dispatchMessage(msg); //Handler的消息处理函数执行
msg.recycle();
}
MessageQueue的next方法我们刚才已经分析过了,这是一个阻塞方法,如果没有Message会一直阻塞;一旦获取到了Message,首先判断是否为null,这个的作用就是用来判断Looper的退出,当Looper调用quit方法,就会调用MessageQueue的quit方法,它的next就会返回null,然后loop方法就return,即退出了循环,达到了退出的作用。一旦获取到了不为null的Message,就会在loop函数中调用msg的target的dispatchMessage
函数,Message我们知道,那这个target是什么呢?我们来看一看Message的成员变量中的target:
Handler target; // 发送和处理消息的 Handler
原来target就是我们的Handler对象,也就是我们下一个要介绍的主人公,现在我们只要知道一点,这个Handler的dispatchMessage
函数是在loop中执行的,也就是Looper所在的线程中执行的(也可以说是Looper对应的线程),其实这里已经完成了我们所熟知的线程切换,Handler发送消息的线程已经切换到执行消息处理的Looper所在线程(熟知的主线程)了。
四、Handler
终于要说到我们经常使用的Handler类了,说了前面这么多,相信大家对Handler在消息循环中的地位也有了一定的认识。我们都知道真正消息处理的代码是写在Handler中的,但Looper还是真正的主角,毕竟Handler中的处理函数还是Looper调用的,可以说一切的获取Message、执行处理都是Looper调用,Handler只是在别的线程中“发出”一个Message而已,之后的执行通过对Looper的分析我们都知道是Looper的活。这里为什么说“发出”要加双引号更多的是因为不是真正意义的发出,通过下面的分析我们可以知道,其实真正的操作只是在Looper的MessageQueue中加入新的Message对象而已。
首先来看一看Handler的构造函数,也是我们创建Handler的调用之一:
(1)Handler构造函数
public Handler(){
...
mLooper = Looper.myLooper(); //获取Looper对象
if (mLooper == null) {
throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue; //获取消息队列
mCallback = null;
}
这里我们清晰的看到Handler的默认构造函数做的第一件事就是获取当前线程的Looper对象,如果没有Looper对象自然也创建不了默认的Handler对象(前面的结论在这里得到了印证);第二件事就是获取当前线程的Looper的MessageQueue,我们记住这个mQueue,实际上大家肯定明白为什么要这个mQueue,其实就是为了插入消息才获取的;第三个这个mCallback是干嘛的呢,我们往下看就知道了。
(2)sendMessage的系列函数
public final boolean sendMessage(Message msg){
return sendMessageDelayed(msg, 0);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis){
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(Message msg, long uptimeMillis){
boolean sent = false;
MessageQueue queue = mQueue;
if (queue != null) {
msg.target = this;
//把target设置为自己,这样我们就知道Message的target就是发送它的Handler
sent = queue.enqueueMessage(msg, uptimeMillis);
//队列的添加消息操作
}
else {
RuntimeException e = new RuntimeException(this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
}
return sent;
}
无论是哪种sendMessage方法,最终都会调用sendMessageAtTime
函数,我们来看看它做了哪些操作:
步骤一:获取消息队列,其实就是Handler初始化时获取到的
MessageQueue queue = mQueue;
步骤二:当队列不为空时(正常情况),做了两件事,第一件事把消息的target设置为自己,这也印证了我们之前讲到的msg.target对象;第二件事就是简单的队列添加操作,调用之前分析过的enqueueMessage
函数,关键代码如下:
if (queue != null) {
msg.target = this;
//1.把target设置为自己,这样我们就知道Message的target就是发送它的Handler
sent = queue.enqueueMessage(msg, uptimeMillis);
//2.队列的添加消息操作
}
(3)dispatchMessage函数
分析了前面那么多,终于要说到我们分析loop函数时调用的那句msg.target.dispatchMessage(msg);
中的关键函数dispatchMessage,让我们来看看代码,但大家一定要清楚,这句话是运行在Looper所属于的线程中的哟。
public void dispatchMessage(Message msg) {
//1.如果msg本身设置了callback,则直接交给这个msg的callback处理了
if (msg.callback != null) {
handleCallback(msg);
} else {
//2.如果该handler的callback有的话,则交给这个callback处理了
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
//3.在msg和自身都没有callback 的情况下,运行自己的handleMessage(是由我们重写的handleMessage,Handler本身的handleMessage什么也不会干)
handleMessage(msg);
}
其实我的注释已经把能说的都说了,我简单的解释一下:
步骤1:首先就是msg是否有callback,而这个callback我们可以看Message的源码中:
Runnable callback; // 处理消息的回调
可以看到这是一个Runnable对象,实际就是Handler的post函数所传递的参数,它的调用也非常简单,handleCallback
函数只做了一件事:
private static void handleCallback(Message message) {
message.callback.run();
}
实际上就是针对post方法来进行执行而已,这里延伸一下post方法,我们知道他也是Handler提交任务的一种方式,具体过程如下:
handler.post(new Runnable(){
@Override
public void run() {
//do something
}});
那么这个post如何实现呢?
public final boolean post(Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
又回到了我们刚才的sendMessageDelayed
函数,相信大家对于getPostMessage是干嘛的也应该有一定想法了,不就是创建Runnable对应的Message嘛,对,我们来看一下实现:
private final Message getPostMessage(Runnable r) {
Message m = Message.obtain(); //新建一个Message
m.callback = r; //设置Runnable对象
return m;
}
这下知道了这个post是干嘛的了吧,我们也可以知道上面的msg的callback是怎么来的了。
步骤2:检查Handler本身是否有callback,也就是我们之前在初始化函数中说到的mCallback对象,它是一个回调接口,定义如下:
public interface Callback {
public boolean handleMessage(Message msg);
}
那他是干嘛的呢?实际上他就是Handler使用的另外一种方式,我们平时都是使用派生Handler的子类,重写handleMessage
函数来实现自己的Handler;而另外一种方式就是这个Callback接口,通过传入实现的Callback接口就可以实现Handler处理的自定义,它的另一个构造函数如下所示:
public Handler(Callback callback) {
...
mLooper = Looper.myLooper(); //获取Looper对象
if (mLooper == null) {
throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
}
这就是通过实现Callback来进行创建Handler的过程,只不过多了一个mCallback的设置而已,如果你设置了callback,那么就可以直接进行消息的处理。
步骤3:在上面两步都没有执行的情况下,进行消息处理,这里就会调用我们熟悉的handleMessage
函数了,你的重写方法真正的得到了执行:
handleMessage(msg);
这样我们就清楚了整个任务调用流程。
五、主线程消息循环的开启
上面我们也说了,之所以你可以不用Looper就创建Handler的原因就是因为你在主线程中进行的操作,而主线程会自己创建Looper循环,那具体在哪里呢,就在主线程的入口函数main中:
public static void main(String[] args) {
...
Looper.prepareMainLooper();
...//省略中间代码
Looper.loop();
}
那这个prepareMainLooper
函数是怎么实现的呢?我们来看一看:
public static final void prepareMainLooper() {
prepare();
setMainLooper(myLooper());
...
}
private synchronized static void setMainLooper(Looper looper) {
mMainLooper = looper;
}
我们可以看出主要工作就是进行Looper的准备并设置MainLooper对象。
总结:通过整体的流程分析和各个类的关键函数分析,我们已经知道了Handler线程切换的秘密了,其实Handler并不是真正的主角,真正重要的是Looper和MessageQueue,它们才是线程切换的真正主角,但我前面提到的MessageQueue我也说了,它有着非常重要的作用,具体的作用就离不开它的两个本地方法,其实在C++层,MessageQueue做了更多的操作来进行处理,这个底层部分我们在下一篇分析中来慢慢看。不过通过这第一篇的分析,我们已经对消息处理机制有了总体的了解,起码知道Handler的实现比使用要复杂的多。
如果觉得我的文章里有任何错误,欢迎评论指正!如果觉得写得好也欢迎大家留言或者点赞,一起进步、一起学习!