前言
Android的消息机制主要是指Handler的运行机制。Handler的运行需要底层的MessageQueue消息队列和Looper消息循环的支撑。简单来说,Android的消息机制就是:Handler给MessageQueue添加消息,然后Looper无限循环读取消息,再调用Handler处理消息。下面将从细节方面进行详细描述:
- 为什么会有消息机制
- 消息机制概述
- 消息机制源码分析
一、为什么会有消息机制
在Android的UI主线程中,不能执行超出5秒的耗时任务,并且所有View和ViewGroup都只能在UI主线程中运行。如果View或者ViewGroup在工作线程中运行,将会抛出【Only the original thread that created a view hierarchy can touch its views】异常,所以在android中会通过消息机制来解决线程和线程之间的通信问题。
二、消息机制概述
消息机制由以下四个部分组成:
- Message消息(数据载体)
- Handler消息处理器
发送消息
处理消息 - MessageQueue消息队列(存储消息)
- Looper轮循器
去MessageQueue取消息
分发给Handler处理
Message、MessageQueue、Looper、Handler之间的关系(即Handler机制原理)
Activity只要一创建Thread里面就多了一个looper(消息轮循器),在主线程里面要定义一个内部类handler,handler的初始化在主线程,有很多子线程要更新UI,Thread拿到主线程的handler,调用sendMessage方法,发送消息,把消息放到消息队列里面,只要消息进到消息队列,looper就会把消息取出来,调用里面的loop方法,取出来的消息就交给handler里面的handlerMessage方法,处理消息.
之所以这样做的原因是因为避免多线程并发更新UI线程所产生的问题的,如果我们允许其他子线程都可以更新界面,那么势必会造成界面的错乱(因为没有加锁机制),如果我们加锁,又会影响速度,而使用Handler机制,所有更新UI的操作,都是在主线程消息队列中轮询去处理的。
Message
Message消息,又叫task任务。封装了任务携带的信息和处理该任务的Handler。
Message的用法:
- 可以通过Message.obtain()来从消息池中获得空消息对象
- 如果message只需要携带简单的int信息,使用Message.arg1和Message.arg2来传递信息,这比用Bundle更省内存
- 用message.what来标识信息,以便用不同方式处理message。
Handler
什么是Handler?Handler扮演了往MessageQueue中添加消息和处理消息的角色(只处理由自己发出的消息),即通知MessageQueue它要执行的任务(sendMessage),并在loop到自己的时候执行该任务(handMessage),整个过程是异步的。Handler创建时会关联一个looper,默认的构造方法会关联当前线程的looper。
handler必须关联一个looper才会起作用,Android UI主线程关联了一个Loop线程,但是我们自定义的线程必须开启Loop。
Handler发送消息
Handler能发送两种消息:一种是Runnable对象,一种是Message对象。但其实post发出的Runnable对象最后都被封装成message对象了。
具体:Handler创建完毕后,其内部的Looper以及MessageQueue就可以和Handler一起协同工作了,然后通过Handler的post方法将一个Runnable投递到Handler内部的Looper中去处理,也可以通过Handler的send方法发送一个消息,这个消息也会在Looper中去处理。其实post方法最终也是通过send方法来完成的。
- send方法发送消息(需要回调才能接收消息)
sendMessage()立即发送Message到消息队列
sendMessageAtFrontOfQueue() 立即发送Message到队列,而且是放在队列的最前面
sendMessageAtTime() 设置时间,发送Message到队列
sendMessageDelayed() 在延时若干毫秒后,发送Message到队列
send方法的工作过程:当Handler的send方法被调用时,它会调用MessageQueue的enqueueMessage方法将这个消息放到消息队列中,然后Looper发现有新消息到来时,就会处理这个消息,最终消息中的Runnable或者Handler的handleMessage方法就会被调用。注意:Looper是运行在创建Handler所在的线程中的,这样Handler中的业务逻辑就被切换到创建Handler所在的线程中去执行了。
- post方法发送消息(直接绑定handler当前线程执行,需要Runnable对象)
post() 立即发送Message到消息队列
postAtFrontOfQueue() 立即发送Message到队列,而且是放在队列的最前面
postAtTime() 设置时间,发送Message到队列
postDelayed() 在延时若干毫秒后,发送Message到队列
Activity中有一个方法runOnUiTheread()实际上就是Handler的post方法。
MessageQueue
消息队列,它的内部存储了一组消息,以队列的形式对外提供插入和删除的工作,但是它的内部存储结构并不是真正的队列,而是采用单链表的数据结构来存储消息列表。
Looper
消息循环。由于MessageQueue只是一个消息存储单元,它不能去处理消息,而Looper就填补了这个功能,Looper会以无限循环的形式去查找是否有新消息,如果有的话就处理消息,否则就一直等待着。
Looper中有一个特殊的概念:ThreadLocal,它的作用是可以在每个线程中存储数据。Handler创建的时候采用当前线程的Looper来构造消息循环系统,那么Handler内部如何获取到当前线程的Looper呢?这就要使用ThreadLoacal了,ThreadLocal可以在不同的线程中互不干扰地存储并提供数据,通过ThreadLoacal可以轻松获取每个线程的Looper。
注意:线程默认没有Looper的,如果需要使用Handler就必须为线程创建Looper。我们经常提到主线程,也叫UI线程,它就是ActivityThread,ActivityThread被创建时就会初始化Looper,所以在主线程中默认可以使用Handler。
三、消息机制源码分析
Message消息
两种创建方式:
Message msg1=Message.obtain();
Message msg2=new Message();
从源码上看:
/**
*空的构造方法
*/
public Message() {
}
Handler
伪代码如下:
new Handler(){
handleMessage(Message msg){
//处理消息
}
};
下面从源码角度看,new Handler做了哪些操作:
Handler的构造方法
public Handler() {
...
//获取Looper(是在ActivityThread里面设置的Looper),只要一new Handler就会从当前主线程去拿Looper,在子线程去new会报错
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
//设置了一个队列
mQueue = mLooper.mQueue;
mCallback = null;
}
主线程设置Looper,在ActivityThread类里面
public static final void main(String[] args) {
...
//第一步:主线程创建Looper
//点进去,ActivityThread是运行在主线程的,调用了prepareMainLooper()这里面的方法,给当前线程set一个Looper,我们在
//new Handler的时候,去Looper.myLooper,拿到的是主线程的Looper,这就是我们主线程的Looper何时去创建的。
Looper.prepareMainLooper();
if (sMainThreadHandler == null) {
sMainThreadHandler = new Handler();
}
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
////主线程创建Looper完成以后,就调用Looper的loop方法
Looper.loop();
...
Looper
/**
*既然知道它是从prepare方法里面放的,就要知道哪个地方调用prepare方法
*/
public static final void prepare() {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
//第三步:在主线程中设置Looper。new Looper()里面同时创建了一个MessageQueue,这样就知道消息队列在哪new出来的
sThreadLocal.set(new Looper());
}
public static final void prepareMainLooper() {
//第二步: 调用prepare()方法
prepare();
setMainLooper(myLooper());
if (Process.supportsProcesses()) {
myLooper().mQueue.mQuitAllowed = false;
}
}
主线程调用Looper.loop()方法,主线程就会阻塞,是一个死循环,使用管道(Pipe),管道是Linux中的一种进程间通信的方式。管道的原理:使用了特殊的文件,文件里面有两个文件描述符(一个是读取,一个是写入)。
应用场景:当Linux下有两个进程需要通信时,主进程拿到读取的描述符等待读取,没有内容就阻塞,然后另一个进程拿到写入描述符去写内容,唤醒主进程,主进程拿到读取描述符读取到的内容,继续执行。
管道在Handler的应用场景:Handler在主线程中创建,Looper会在死循环里等待取消息,一种是没取到消息,就阻塞;一种是主线程一旦被子线程唤醒,取到消息,就把Message交给Handler去处理。子线程是用Handler去发送消息,拿写入描述符去写消息,写完之后就唤醒主线程。
public static final void loop() {
...
//死循环
while (true) {
//每次从队列中去取下一个数据(消息),要看什么时候给队列赋值,(在Handler创建的时候,就已经给它设置了队列)
Message msg = queue.next(); // might block,取消息,如果没有消息,会阻塞
//if (!me.mRun) {
// break;
//}
if (msg != null) {
if (msg.target == null) {
// No target is a magic identifier for the quit message.
return;
}
if (me.mLogging!= null) me.mLogging.println(
">>>>> Dispatching to " + msg.target + " "
+ msg.callback + ": " + msg.what
);
//msg.target其实就是一个Handler,Handler就会调用dispatchMessage方法。
msg.target.dispatchMessage(msg);
...
}
}
}
Handler发送消息代码:
public boolean sendMessageAtTime(Message msg, long uptimeMillis)
{
...
if (queue != null) {
//this是当前的Handler类,这样每个Message里面有一个target,target值就是当前的Handler
//把message的target置为当前发送的Handler,以便Looper取到message后根据target把message分发给相对应的Handler
msg.target = this;
//往队列里面添加Message
sent = queue.enqueueMessage(msg, uptimeMillis);
}
...
return sent;
MessageQueue.enqueueMessage代码
final boolean enqueueMessage(Message msg, long when) {
...
//when代表发送的时间
msg.when = when;
//Log.d("MessageQueue", "Enqueing: " + msg);
Message p = mMessages;
//when=0,不延迟,当前发送的message消息需要马上处理(当前只有一个Message),needWake置为true
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
//需要唤醒
needWake = mBlocked; // new head, might need to wake up
} else {//当前有很多Message,如果当前Message排在其他message后面,当前Message不用优先处理,不用唤醒主线程,needWake置为false
Message prev = null;
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
msg.next = prev.next;
prev.next = msg;
needWake = false; // still waiting on head, no need to wake up
}
}
//是否唤醒主线程,如果要唤醒,调用底层的jni方法去唤醒
if (needWake) {
nativeWake(mPtr);
}
return true;
}
Handler.dispatchMessage()方法:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
//如果有runnable就调用这个
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
//如果没有runnable就调用handleMessage方法,把Message交给Handle处理
handleMessage(msg);
}
}
提问:Handler与Looper的对应关系,是多对一。那么每个handler发送的消息,怎么保证不会处理别的handler发送的消息呢?
Handler底层用到了管道的通信方式,Handler在sendMessage的时候,会把Message的target置为Handler,在调用Looper的loop方法的时候,找到每个message,调用message的target,然后去dispatchMessage,这样Looper就会根据每个message找到需要相对应处理message的handler,因为在一个应用里面可能用到多个Handler,所以这个地方通过message的target去区分每个handler需要处理它自己发送的message。
小结:
大致流程:
在主线程创建Handler的时候,就可以拿到主线程的Looper,主线程创建完Looper之后,就会执行Looper中的loop方法,loop方法就会从MessageQueue消息队列中一个一个去拿Message消息,它是一个死循环。死循环里面使用了管道的通信方式,管道里面就会拿到文件描述符,一个是往里面存,一个是往里面写。存的时候,是通过上层的Handler,去sendMessage发送消息,调用MessageQueue的enqueueMessage方法,这样就拿到写入的描述符往里面写了,在loop方法里面有queue.next()方法,这个方法会拿到读取的描述符,当它没有读到消息时就阻塞,读到有消息的时候就调用dispatchMessage方法,dispatchMessage方法就会调用handleMessage方法处理消息
下面是Handler的流程图,描述了Handler消息机制:
image.png
最后
我们可以把Handler消息机制理解为生产消费模型:
- Handler是生产者,主要往MessageQueue中存放消息;
- Looper为消费者,主要从MessageQueue中消费消息;
以上是根据我的一些理解,做的总结分享,旨在抛砖引玉,希望有更多的志同道合的朋友一起讨论学习,共同进步!
网友评论