前言
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
以上是根据我的一些理解,做的总结分享,旨在抛砖引玉,希望有更多的志同道合的朋友一起讨论学习,共同进步!
注:本文内容来自互联网,旨在为开发者提供分享、交流的平台。如有涉及文章版权等事宜,请你联系站长进行处理。