Android中的消息机制
一.Looper、Handler、MessageQueue与Message的关系与相关概念
1.什么是Android消息处理机制?
“消息”是windows运行机制中一个基本而又重要的概念。消息是一个报告事件发生的通知,消息驱动是围绕消息的产生与处理展开的,并依靠消息循环机制来实现(百度百科)。与Windows系统一样,Android也是消息驱动型的系统。引用一下消息驱动机制的四要素:1
2
3
4
5
6
7 ①接收消息的“消息队列”
②阻塞式地从消息队列中接收消息并进行处理的“线程”
③可发送的“消息的格式”
④“消息发送函数”
与之对应,Android系统中对应实现了:1
2
3
4
5
6
7 ①接收消息的“消息队列” —— MessageQueue
②阻塞式地从消息队列中接收消息并进行处理的“线程” —— Thread+Looper
③可发送的“消息的格式” —— Message
④“消息发送函数”—— Handler的post()和sendMessage()
Android有大量的消息驱动方式来进行交互,比如Android的四大组件——Activity, Service, Broadcast, ContentProvider的启动过程的交互,都离不开消息机制。
2.Handler
Android的消息机制,很多时候我们也称之为“Handler机制”,可见Handler这个东西是相当重要了~~那么Handler是用来干什么的呢?刚刚笔者在刷知乎的时候看到有大神怼(教导)一个Android菜鸟的时候其中就提到了一点“…以为Handler就是用来更新UI的…”——好吧,真是垂死病中惊坐起——开始学Android那会笔者也曾经这么认为(捂脸)。
如果你也这么认为,那么从今天起你就要放弃这种狭隘的想法——Handler是Android消息机制的上层接口,因此我们在开发中与Handler打交道的机会最多。Handler并不是专门用来更新UI的,只是开发者常常用它来更新UI。
Handler的主要用于同一个进程间的线程通信,Handler用于更新UI的时候是”子线程与主线程通信”;当然,Handler也可以用于子线程之间通信。
Handler的消息机制主要是就指“Handler的运行机制”,Handler的运行机制时需要底层的MessageQueue和Looper支持的。
3.MessageQueue
MessageQueue翻译过来是”消息队列”的意思,实际上它内部的数据结构不是队列,而是单向链表;MessageQueue中储存了大量的消息,由于一个线程同一时间只能处理一条消息,所以我们建了一个链表,将我们需要处理的消息按顺序储存起来,然后一项一项的交给需要的线程处理,这就是MessageQueue存在的价值。
这里笔者想到一个问题——为什么MessageQueue要用链表而不用数组来作为数据结构呢?再经过网上查找博客+源码阅读,笔者认为是这样的——之前我们在一片文章中有讲过,单链表更适合做增删的操作,数组更适合做随机访问的操作。再MessageQueue中,我们不止要做随机访问(这里不是真的随机访问,消息的读取是根据计算出来的时间顺序来的,后文会讲)跟多的我们做的是插入和删除操作,MessageQueue.enqueueMessage()就是插入消息(消息的插入需要根据时间发送的时间顺序来确定,不存在头插还是尾插),而插入消息;而MessageQueue.next()则是读取消息,且读取的同时也伴随这删除的过程。试想一下,一个消息队列要循环起来,必然要频繁的进行插入/读取操作,假如采用数组的话,这两个操作的平均时间复杂度都是O(N/2),而链表为O(1),显然链表更合适。
4.Looper
Looper和MessageQueue的消息就像水泵和井(里边装的是水)的关系一样,我们有了消息(水),但是为了把水从井中抽取出来(循环起来),我们得有一个水泵作为动力,这个动力就是Looper。
如果我们在一个线程中调用Looper.prepare()…Looper.loop(),那么你的线程就成功升级为了一个Looper线程,这意味着你的线程有了一个消息泵(Looper)和一个消息队列(MessageQueue),此时你就可以调用Handler来进行线程间的通信了。
我们应用的UI线程也就是主线程,在应用启动的时候,系统会自动初始化一个Looper,也就是说,我们的UI线程默认是Looper线程。这也就是为什么主线程中直接调用Handler没什么事,但是再子线程中创建Handler需要哦手动调用Looper.prepare()…Looper.loop()的和原因。
5.Message
Message也就是消息,井中的水。一个Message包括了消息类型(what),消息内容(arg1,arg2),发送它的Handler(target),Runnable回调接口等:
1 | public int what; //数据类型 |
讲到这里,我们先上一张图加深一下大家对于这几个东西的直观认识:
二.子线程与主线程Handler通信原理(子线程是如何通过Handler更新UI的)
1.一些熟悉的场景
1 | private Handler handler = new Handler(){ |
上面代码是我们再Android开发中经常写的一段代码,其主要作用是再子线程中进行耗时操作,并通过Handler向主线程中发送消息,通知主线程做出相应的UI变化。注意这段代码中,Handler是再主线程中创建的,因此不需要手动调用Looper.prepare()添加Looper。
如果我们试图再子线程中创建一个Handler,如:
1 | Handler handler ; |
那么很显然,这个时候就会出bug了~~为了解决这个bug,我们需要手动再子线程中创建Looper:
1 | handler = new Handler(); |
为什么是这样呢?接下来从源码的解读分析
2.Looper
(frameworks/base/core/java/android/os/Looper)
(1)Looper.perpare()
1 | public final class Looper { |
首先我们看到Looper.prepare()方法中调用了prepare(ture)方法,其中true表示,该Looper是可以被终止的。因为我们是在子线程中创建的Looper,当子线程的消息处理完之后,理应把改Looper终止掉(MessageQueue.quit)。但是在主线程的Looper中:
1 | public static void prepareMainLooper() { |
prepareMainLooper就是给主线程添加Looper,可以看到,主线程中的prepare(false)中的参数false表示的是,主线程中的Looper不能被终止掉,毕竟它是整个应用的生命,需要时刻准备着处理或者正在处理应用中的各种消息。
好了我们接着看上面的prepare()中干了什么。这里出现了一个新的重量级的东西:sThreadLocal,它是ThreadLocal类的实例,关于ThreadLocal类是干什么的,我们在这里不做过多解释,我们只需要知道他是用于储存不同线程唯一对象的一个东西,即多个线程在ThreadLocal类中,通过ThreadLocal.set()保存了自己的变量之后,那么我们再各个子线程中调用ThreadLocal.get()方法,得到的仍然是当前线程之前存进去的那个值。各个线程存取各自的值,不会产生冲突。
知道了ThreadLocal的作用之后,我们在来看sThreadLocal.set(new Looper(quitAllowed));这句表示我们再ThreadLocal类中保存了一个Looper对象(new Looper()),根据上面对ThreadLocal类的介绍,如果我们再当前线程中调用ThreadLocal.get()方法,则会得到本线程之前保存的唯一的变量。因此:
1 | if (sThreadLocal.get() != null) { |
这个if表示的是,如果sThreadLocal.get() != null,说明当前线程中已经存在一个Looper,我们不能在一个线程中同时创建多个,所以此时会抛出异常。为了避免这种异常,我们可以在Looper.prepare()之前调用Looper.myLooper()类来返回当前线程中的Looper对象,判断为空之后,再调用prepare():
1 | public static Looper myLooper() { |
Ok,比比了这么多,我们接着看new Looper()中Looper中的构造函数中做了什么:
1 | private Looper(boolean quitAllowed) { |
这里出现了消息队列MessageQueue(quitAllowed),其中参数就是表示是否允许Looper退出的标示符。可以看到,在Looper中我们创建了一个MessageQueue实例:
1 | MessageQueue(boolean quitAllowed) { |
,此时我们的Looper就拥有了MessageQueue的对象引用。
(2)Looper.Loop()
在Looper.perpare()调用完即我们为线程准备好Looper之后,最后一步我们还需要调用Loop()让整个Looper循环起来,这样消息才能发送出去:
1 | public static void loop() { |
首先调用了我们刚说过的myLooper()来获取当前线程再ThreadLcoal类中储存的Looper,如果Looper为空则会抛出异常,提示当前线程没有Looper。然后inal MessageQueue queue = me.mQueue;是获取当前线程的Looper中的MessageQueue对象。
之后这个Looper便进入了一个无限循环的状态——for(;;),Message msg = queue.next();是一条一条遍历整个消息队列,拿出msg消息。
而msg.target.dispatchMessage(msg);这句中的msg.target实际上就是当前消息的目标Handler,也就是哪个线程中的Handler发送的消息,当然,这个发送它的Handler也要在自己所在的线程中接受这条消息。
msg.recycleUnchecked();这句是将这天消息放入Message类中的消息池中。
从上面我们基本上可以得出——Looper > MessageQueue > Message的关系,也就是说,每个Looper中维护了一个消息队列,而一个消息队列中则以链表的形式排列着一条条消息。
3.MessageQueue.next()
(frameworks/base/core/java/android/os/MessageQueue)
上面的Looper类中我们实例化了一个MessageQueue对象,并调用了MessageQueue.next()类方法:
1 | Message next() { |
首先:
1 | final long ptr = mPtr; |
这个mPtr我们之前在MessageQueue的构造方法中提到过:
1 | MessageQueue(boolean quitAllowed) { |
它通过mPtr = nativeInit();得到值,nativeInit()是一个native层的方法,根据注释来看,它主要是判断Looper目前的状态。if (ptr == 0) {表示的是当前线程的Looper已经退出或者被处理掉了,这种情况发生在“应用在退出之后试图重启Looper”的情况下,这种情况是不被允许的,因此此时return null;
上面这段中还要讲的就是单链表的操作:
1 | if (prevMsg != null) { |
首先,再synchronized (this)同步块一开始的时候,有一个全局变量mMessages,这实际上就是等待处理消息,这个变量很重要,之后我们会多次遇到。这里还有一个叫prevMsg的Message,用于保存找不到msg.target的消息(废消息)。OK,回到链表操作中来,if (prevMsg != null),很遗憾,有一个不合法的消息,此时上面已经经历了prevMsg = msg; msg = msg.next;这两步,加上prevMsg.next = msg.next;这一步,实际上就是prevMsg的后继引用跳过了msg,直接指向了msg的下一位(注意prevMsg是不合法的,但是它的下一位msg是合法的)。
再看else,说明没有不合法的消息,Message msg = mMessages;加上mMessages = msg.next;,这两步实际上和上面一样,也是工作指针后移,越过了msg。也就是msg出队,msg的下一条消息成为mMessages(待处理消息),否则就进入一个阻塞状态,一直等到有新的消息入队。
OK,上面if和else中越过的msg,就是我们要返回的正常消息,接着两句:msg.next = null;和return msg;就可以知道,前者是将msg的后继引用清空(将它从链表中删除),然后return。
通过上面的分析我们知道,MessageQueue.next()作用就是遍历链表,找出一个合法的msg,将它从链表中删除后返回,这实际上也就是一个消息出队的过程
4.Handler
(1)Handler.post/sendMessage将一个消息添加到消息队列中
上面我们已经讲完了Handler通信的两个重要的基础类——MessageQueue和Looper,接下来我们分析一下Handler是如何将一个消息发送出去的。
我们从Handler的构造函数开始看起:
1 | public Handler() { |
可以看到,Hnadler在发送消息的时候先获取当前线程的Looper,然后做一次looper的非空判断;接着获取了Looper中的MessageQueue对象。这样,我们的Handler已经和Looer以及MessageQueue取得了联系。
接着回到文章最开始的时候举的那个例子中,handler.sendMessage()方法:
1 | public final boolean sendMessage(Message msg){ |
上面几个方法的逻辑都比较清晰,可以看到,最终调用了MessageQueue的enqueueMessage()方法,其中第一个参数为msg,第二个参数uptimeMillis = SystemClock.uptimeMillis() + delayMillis,也就是开机到现在的时间(不包括睡眠时间)+我们设定的delay时间,接下来我们看看enqueueMessage()方法:
1 | boolean enqueueMessage(Message msg, long when) { |
MessageQueue是按照Message触发时间的先后顺序排列的,队头的消息是将要最早触发的消息。当有消息需要加入消息队列时,会从队列头开始遍历,直到找到消息应该插入的合适位置,以保证所有消息的时间顺序。
上面代码中①处的for()循环就是在循环遍历MessageQueue以找到合适的msg插位置,②处的代码实际上就是一个单链表插入的过程,我们可以把整个插入的代码连起来,这样更容易看出:
1 | prev = p; |
没骗你吧~~这就是把我们的要发送的消息msg插入到了链表中的p节点之前。
我们需要明确的一点是,这里的Handler.post/sendMessage方法是和上面的MessageQueue.next()方法是对应的,他是消息的入队操作。
(2)Looper.loop()中调用Handler.dispatchMessage()接受并处理消息
好了,讲到这里,我们可以看到,Handler.post或者Handler.sendMessage方法,最终是将他们要发送的消息添加到了消息队列中(Handler.post实际上也是调用了Handler.sendMessageDaley())。
那么接受消息在哪呢?好吧看标题你也知道,Looper.loop()方法中我们实现了消息的重写与接收。我们回过头去看Looper类,在该类中MessageQueue.next()这个消息出队的方法调用完之后,出现了msg.target.dispatchMessage(msg);这句代码,我们说过这句代码中的msg.target就是消息的目标Handler,于是我们回到Handler中看下这个方法:
1 | public void dispatchMessage(Message msg) { |
可以看到最终我们调用了一个空方法,为什么呢,因为消息的最终回调是由我们控制的,我们在创建handler的时候会在Handler所在的线程中(如果是子线程更新主线程UI的话,就在主线程中)重写handleMessage方法,然后根据msg.what进行消息处理(对照开始给出的Hanlder常见场景)。
我们梳理一下整个流程:
1 | ① handler = new Handler(); //创建Handler对象 |
Looper.loop()中的消息出队之后,将调用Handler的dispatchMessage,最终大我们在代码中重写的handleMessage用以自定处理:
1 | private Handler handler = new Handler(){ |
(3)Handler.post() & View.post() & Activity.runOnUiThread()
①Handler.post()
上面我们说了Handler.sendMessage()方法,并且在文章开头的实例中展示了Handler.sendMessage()的使用方法,下面我们来说一下Handler.post(),该方法的具体使用还是略微的有一点不同的:
1 | public final boolean post(Runnable r){ |
可以看到,Hander.post()方法还是调用了sendMessageDelayed方法,这跟之前的流程是一样的。这里我们要说的是sendMessageDelayed(getPostMessage(r), 0);中的getPostMessage(r)方法:
1 | private static Message getPostMessage(Runnable r) { |
在这个方法中将消息的callback字段的值指定为传入的Runnable对象。这个callback字段之前我们有遇到过——在Handler的dispatchMessage()方法中原来有做一个检查,如果Message的callback等于null才会去调用handleMessage()方法,否则就调用handleCallback()方法:
1 | public void dispatchMessage(Message msg) { |
这里的run也就是一开始的时候传入的run接口。也就是说,如果通过post(Runnable r)传递消息的话,我们直接就可以在post()方法中进行UI操作:
1 | handler = new Handler(); |
写法上简洁了很多,但是本质上都是一样的。
②View.post()
(source/android-24/android/view/View):
1 | public boolean post(Runnable action) { |
可以看到同样调用了Handler的post方法
1 | Activity.runOnUiThread() |
如果当前的线程不等于UI线程(主线程),就去调用Handler的post()方法,否则就直接调用Runnable对象的run()方法。
5.Message.obtain
上面我们需要解释的一个东西便是Message msg =Message.obtain();这句,Message的obtain()方法中维护了一个消息池,其最大容量MAX_POOL_SIZE = 50
1 | /** |
我们当然可以直接new Message()这样来创建消息,但是一般来讲我们还是应该调用Message.obtain()方法来返回一个消息实例,以避免Message对象的多次创建。
好了,到此为止,我们调用Handler发送消息更新UI的整个流程就说完了。
三.Activity启动过程中UI线程的MainLooper的创建
Actvity的启动流程我们之前在一片文章中有讲过,真正启动Activity的是ActiivtyThread类中的main()方法:
1 | public static void main(String[] args) { |
可以看到,我们在启动一个Activity之前,我们在ActivityThread.main()方法中,调用了Looper.prepareMainLooper()方法:
1 | public static void prepareMainLooper() { |
该方法同上面的Looper.prepare()方法一样,只不过这里准备的是主线程中的Looper,因此prepare(false);其中的参数false表示的是,该线程的Looper不能退出。之后调用myLooper则是获取主线程中的Looper对象,这些和上面都没什么区别。
我们回到ActivityThread.main()中,在准备完主线程的Looper之后,ActivityThread thread = new ActivityThread();创建一个ActivityThread实例。thread.attach(false);参数false表示这不是系统进程,是给普通应用程序使用的进程。
我们接着thread.attach(false);来看:
1 | private void attach(boolean system) { |
注意到mgr.attachApplication(mAppThread);这句,其中mAppThread是ApplicationThread的对象,mgr为IActivityManager接口类,而真正实现IActivityManager接口的是在ActivityManagerService(AMS)类中,关于AMS类中的代码我们这里就先不详细解释了,我们只需要知道,在它当中经过一系列的处理~~最终:
1 | app.thread.scheduleLaunchActivity(new Intent(r.intent), r, System.identityHashCode(r), |
又回调到了ActivityThread类中:
1 | //该方法在ApplicationThread(Binder线程)中调用 |
可以看到,这里将一系列应用的信息封装在ActivityClientRecord中之后,最终调用了sendMessage(H.LAUNCH_ACTIVITY, r);发送消息:
1 | private void sendMessage(int what, Object obj) { |
上面的“H”实际上就是一个Handler类:private class H extends Handler {,而这里的mH则是H的子类。接着往下看:
1 | public void handleMessage(Message msg) { |
可以看到,这里handleMessage收到了我们发送的LAUNCH_ACTIVITY也就是启动Activity的请求,实际上这里还有PAUSE_ACTIVITY,RESUME_ACTIVITY等一系列请求的处理。
之后handleLaunchActivity(r, null, “LAUNCH_ACTIVITY”);–>performLaunchActivity–>mInstrumentation.callActivityOnCreate一系列流程之后,我们的Activity就启动了,这个我们在之前的文章中有讲过,这里不再獒述。