1.前言
关于Handler消息机制的博客实际上是非常多的了。
之前也是看别人的博客过来的,但是过了一段时间之后,一些细节也就忘了。
所以,就自己撸一篇,权当笔记,方便以后翻阅。
这篇文章主要是分析Handler消息机制原理以及收集一些面试题来讲解,熟悉的话可以不用看了。
本文源码基于android 27。
2.Android消息机制概述
2.1 本质
Android的消息机制本质就是一套消息发送,传递及处理的机制。
2.2 角色说明
角色 | 作用 |
---|---|
Handler (处理者) | 负责消息的发送及处理等等。 |
Message (消息) | 保存消息的内容,如保存一个消息类型(what )等等。 |
MessageQueue (消息队列) | 保存Message 的数据结构,是一个链表。 |
Looper (循环器) | 从消息队列中循环的取出消息然后把消息交给Handler 处理。 |
2.3 原理简介
消息机制在Android上的流程为:
应用启动时会在主线程中创建一个
Looper
(循环器),Looper
(循环器)内部则会创建一个MessageQueue
(消息队列);
然后
Looper
(循环器)就会不断的轮询MessageQueue
(消息队列)中是否有Message
(消息);我们可以通过
Handler
去发送一个Message
(消息),发送之后Message
(消息)就会进入到MessageQueue
(消息队列)中去,Looper
(循环器)通过轮询取出Message
(消息),然后交给相应的Handler
(处理者)去处理。
下面会通过分析源码来验证这个过程。
上面这个流程总结成一句话就是:
Looper
(循环器)通过不断的从MessageQueue
(消息队列)中取出Message
(消息),然后交给相应的Handler
(处理者)去处理。
是不是很简单呢?
2.4 Handler的应用--UI更新
对于消息机制,我们平常接触的最多的场景就是:
在子线程中进行一些数据更新等耗时操作,然后使用
Handler
在主线程中去更新UI。
为什么要怎么操作呢?这里有两个前提:
1.Android开发中规定了UI的更新只能在主线程中去操作,在子线程中更新会报错。
2.我们在主线程中创建的Handler
能够接受到同一个Handler
在子线程中发送的消息。
可以看到,在这种场景下我们使用Handler
的目的就是切换到主线程中去更新UI。而Handler
的使用方式是很简单的,这里就不写例子了。
那么,为什么更新UI只能在主线程中去操作呢?
这是因为Android中的UI控件不是线程安全的,因此在多线程中并发,可能会出现线程安全的问题,即访问UI控件可能会出现跟预期不一样的结果。那么为什么不使用锁机制呢?因为加锁会降低访问UI的效率,锁机制会阻塞某些线程的执行。因此,最简单高效的方法就是使用单线程模型来进行UI的访问了。
那么,为什么主线程中的Handler能接受到其他线程发来的消息呢?
这是后面源码分析的内容,这里暂且不表。
2.5 Handler的其他应用
上面UI更新实际上只是消息机制其中一个应用场景。
如果我们了解四大组件的启动停止等过程的话,就会发现,都是在一个名为H
的Handler
中处理状态切换等逻辑,这个H
是ActivityThread
的内部类。其本质就是切到主线程中去处理。
所以说,不要将Handler
仅仅局限于UI更新。
3.源码分析
本节主要深入源码对消息机制进行分析。对涉及到Looper
、MessageQueue
、Message
、Handler
等类进行逐一分析。
3.1 Looper类
3.1.1 Looper(循环器)的创建
Looper
的创建可以分为在主线程中创建以及在子线程中创建,我们分别来看下。
3.1.1.1 主线程中创建Looper
先来看下Looper
在主线程中是什么时候创建的。
3.1.1.1.1 ActivityThread的main()
应用启动时,会调用到ActivityThread
中的main()
方法,这个main()
方法是应用程序的入口。main()
里面会创建一个Looper
对象出来。我们来看下代码:
public static void main(String[] args) { //省略无关代码 //为主线程创建1个Looper对象 Looper.prepareMainLooper(); //创建主线程 ActivityThread thread = new ActivityThread(); thread.attach(false); //省略无关代码 //开启消息循环 Looper.loop(); }
可以看到,应用启动时就为主线程创建出一个Looper
对象,并且开启消息循环。
3.1.1.1.2 Looper的prepareMainLooper()
再来看下prepareMainLooper()
:
public static void prepareMainLooper() { //最终还是调用prepare() //参数false表示主线程中的消息循环不允许退出 prepare(false); //判断sMainLooper是否为null,否则抛出异常 //即prepareMainLooper()不能够被调用两次 //保证了主线程中只存在一个Looper对象 synchronized (Looper.class) { if (sMainLooper != null) { throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); } }
3.1.1.1.3 Looper的prepare()
再来看下prepare()
方法:
private static void prepare(boolean quitAllowed) { //ThreadLocal可以保存一个线程内的局部变量 //这里判断当前线程是否已经存在Looper对象,存在的话则抛异常 //因为一个线程只能创建一个Looper对象 if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } //当前线程没有创建Looper对象的话 //则新创建一个Looper对象 //并把这个Looper对象保存到ThreadLocal中 sThreadLocal.set(new Looper(quitAllowed)); }
prepare()
中就是创建一个Looper
对象并把Looper
对象保存到线程中的ThreadLocal
。
3.1.1.1.4 Looper的构造方法
再来看下Looper
的构造方法:
private Looper(boolean quitAllowed) { //创建消息队列 mQueue = new MessageQueue(quitAllowed); //记录当前线程. mThread = Thread.currentThread(); }
Looper
内部中就是创建了一个消息队列。
3.1.1.1.5 小结
应用启动时,主线程会创建一个Looper
对象出来,Looper
内部则创建消息队列。
3.1.1.2 子线程中创建Looper
在子线程中创建Looper
非常简单,直接看例子吧:
3.1.1.2.1 子线程中创建Looper例子
class LooperThread extends Thread { public Handler mHandler; public void run() { Looper.prepare(); mHandler = new Handler() { public void handleMessage(Message msg) { //TODO ... } }; Looper.loop(); }
调用一下无参数的prepare()
方法即可。
3.1.1.2.2 Looper的prepare()
public static void prepare() { prepare(true); }
最终还是调用有参数的prepare()
方法,有参数的prepare()
方法祥见上面的代码分析。true
代表这个Looper
是可以退出的。主线程中创建的Looper
则是不能退出的。这就是他们的区别。
3.1.2 Looper(循环器)的消息循环
Looper
是通过loop()
这个方法来进行消息循环的,我们来看下代码:
3.1.2.1 Looper的loop()
public static void loop() { //获得当前线程的Looper对象 //myLooper()实际上通过sThreadLocal.get()来获取的 final Looper me = myLooper(); //如果当前线程没有创建过Looper,则抛出异常 if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } //获得Looper对象中的消息队列 final MessageQueue queue = me.mQueue; //死循环 for (;;) { //从消息队列中取出一个消息 //如果消息队列没有消息的话,会阻塞在这里 Message msg = queue.next(); // might block //消息为null的话表示停止消息循环 //可以通过queue.quit()来停止,前提是通过prepare(true);来创建的 //主线程中不允许停止消息循环 if (msg == null) { // No message indicates that the message queue is quitting. return; } //... //分发消息去处理 //msg.target就是要处理的Handler,祥见后面分析 msg.target.dispatchMessage(msg); //... //回收消息 msg.recycleUnchecked(); } }
3.1.2.2 小结
Looper
中通过一个死循环从消息队列中取消息,一旦取到消息之后,就分发交给Handler
来处理。
3.1.3 Looper(循环器)的退出
public void quit() { mQueue.quit(false); } //安全退出 public void quitSafely() { mQueue.quit(true); }
退出Looper
有两个方法,如上。最终还是通过调用MessageQueue.quit(boolean safe)
方法来实现,只是传的参数不一样而已。这个在MessageQueue
那一小节再来分析。
3.2 Message类
Message
类用来保存消息的内容。我们先来看下Message
类会保存哪些消息:
3.2.1 Message类主要成员变量
成员变量 | 类型 | 含义 |
---|---|---|
what | int | 消息类型 |
obj | Object | 消息内容 |
when | long | 消息触发时间 |
target | Handler | 消息处理者 |
callback | Runnable | 回调方法 |
sPool | Message | 消息池 |
sPoolSize | int | 消息池大小 |
next | Message | 下一条消息 |
这里只列举了一部分,详细的可以去看Message
类的源码。
3.2.2 获取消息
Message
内部维护了一个消息池,我们可以通过obtain()
来从消息池中获取消息,而不是直接去new
,这样可以提高效率。
3.2.2.1 Message的obtain()
public static Message obtain() { synchronized (sPoolSync) { //如果消息池不为null,则从消息池中取出一条消息 if (sPool != null) { //从sPool中取出头结点 Message m = sPool; sPool = m.next; m.next = null; m.flags = 0; // 清除in-use标记 sPoolSize--; return m; } } //如果消息池为null,则直接new return new Message(); }
3.2.3 回收消息
3.2.3.1 Message的recycle()
public void recycle() { //判断消息是否正在使用 if (isInUse()) { if (gCheckRecycle) { throw new IllegalStateException("This message cannot be recycled because it " + "is still in use."); } return; } //调用recycleUnchecked() recycleUnchecked(); }
调用recycleUnchecked()
来回收。
3.2.3.2 Message的recycleUnchecked()
void recycleUnchecked() { //将消息标记为使用状态 //清空消息其他参数 flags = FLAG_IN_USE; what = 0; arg1 = 0; arg2 = 0; obj = null; replyTo = null; sendingUid = -1; when = 0; target = null; callback = null; data = null; synchronized (sPoolSync) { //如果没有达到消息池的最大容量,则将消息回收到消息池中去 //最大容量默认为50 if (sPoolSize < MAX_POOL_SIZE) { next = sPool; sPool = this; sPoolSize++; } } }
3.2.4 小结
Message
内部维护了一个消息池,这个消息池是以链表的形式存在的。通过消息池去获取Message
对象能够避免直接创建对象,可以起到一个提高效率的作用。
3.3 Handler类
通常,我们都会通过继承Handler
类来自定义一个Handler
子类,然后重写handleMessage()
方法来处理消息。同时,也是通过这个Handler
子类来进行发送消息的。
我们先来看下Handler
的构造方法。
3.3.1 Handler的构造方法
3.3.1.1 Handler的无参构造方法
public Handler() { this(null, false); }
最终会调用有参数的构造方法Handler(Callback callback, boolean async)
3.3.1.2 Handler的有参构造方法
先来看下上面提到的Handler(Callback callback, boolean async)
:
public Handler(Callback callback, boolean async) { //匿名类、内部类或本地类应该申明为static,否则会警告可能出现内存泄露 if (FIND_POTENTIAL_LEAKS) { final Class<? extends Handler> klass = getClass(); if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) && (klass.getModifiers() & Modifier.STATIC) == 0) { Log.w(TAG, "The following Handler class should be static or leaks might occur: " + klass.getCanonicalName()); } } //获得当前线程的Looper对象 //myLooper()实际上通过sThreadLocal.get()来获取的 mLooper = Looper.myLooper(); //如果mLooper为null则抛出异常 if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } //获得Looper对象中的消息队列 mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; }
下面列出所有Handler
的所有有参构造方法
public Handler(Callback callback); public Handler(Looper looper); public Handler(Looper looper, Callback callback); public Handler(boolean async); public Handler(Callback callback, boolean async); public Handler(Looper looper, Callback callback, boolean async);
可以看到:除了callback
和async
之外,还可以传递一个Looper
进来,即可以指定跟Handler
要绑定的Looper
,相关代码就不贴了,还是很简单的。
3.3.2 Handler发送消息
通常我们都是通过Handler
的sendMessage()
方法来发送消息的:
3.3.2.1 Handler的sendMessage()
public final boolean sendMessage(Message msg) { //调用sendMessageDelayed() return sendMessageDelayed(msg, 0); }
3.3.2.2 Handler的sendMessageDelayed()
public final boolean sendMessageDelayed(Message msg, long delayMillis) { if (delayMillis < 0) { delayMillis = 0; } //调用sendMessageDelayed() return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); }
3.3.2.3 Handler的sendMessageAtTime()
public boolean sendMessageAtTime(Message msg, long uptimeMillis) { //获得消息队列 MessageQueue queue = mQueue; //消息队列为空,则抛异常 if (queue == null) { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); return false; } //调用enqueueMessage(),消息入队 return enqueueMessage(queue, msg, uptimeMillis); }
3.3.2.4 Handler的enqueueMessage()
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { //把当前的Handler对象保存到消息中的target中去 //这样消息分发时才能找到相应的Handler去处理 msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } //把消息放到消息队列中去 return queue.enqueueMessage(msg, uptimeMillis); }
最终,发送消息就是将消息放到消息队列中去。
3.3.3 Handler发送Runnable
Handler
除了sendMessage(Message msg)
外,还可以发送一个Runnable
出来,这是通过其post()
方法实现的:
3.3.3.1 Handler的post()
public final boolean post(Runnable r) { return sendMessageDelayed(getPostMessage(r), 0); }
post()
方法里面同样也是通过sendMessageDelayed()
来发送消息,我们来看下getPostMessage(r)
这个方法:
3.3.3.1 Handler的getPostMessage()
private static Message getPostMessage(Runnable r) { //获取消息 Message m = Message.obtain(); //Runnable赋值给Message中的callback m.callback = r; //返回一个Message return m; }
所以,post()
最终还是将Runnable
对象包装成一个Message
来进行消息发送的。
3.3.4 分发消息
前面Looper
在消息队列中取到消息后就调用msg.target.dispatchMessage(msg);
来分发消息,这里的msg.target
就是Handler
。我们来看下dispatchMessage()
方法:
3.3.4.1 Handler的dispatchMessagee()
public void dispatchMessage(Message msg) { if (msg.callback != null) { //当msg.callback不为null时,会回调message.callback.run()方法 //即执行Runnable的run()方法 handleCallback(msg); } else { //当Handler存在Callback时,回调Callback的handleMessage(); if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } //调用Handler的handleMessage(msg) //即我们继承Handler重写的handleMessage(msg)方法 handleMessage(msg); } }
我们再来看下handleCallback()
这个方法:
3.3.4.2 Handler的handleCallback()
private static void handleCallback(Message message) { //执行Runnable的run()方法 message.callback.run(); }
3.3.4.3 小结
从上面的代码可以看到,分发消息的流程如下:
如果
msg.callback
不为null
,这个msg.callback
实际是个Runnable
对象,则调用这个Runnable
的run()
方法,结束;为null
的话就走到步骤2。如果
Handler
的成员变量mCallback
不为null
,则调用mCallback.handleMessage(msg)
,结束;为null
的话就走到步骤3。调用
Handler
的handleMessage(msg)
方法,结束。
3.4 MessageQueue类
我们再来看下MessageQueue
类,主要包括消息入队、取出消息和退出等。
3.4.1 MessageQueue构造方法
MessageQueue(boolean quitAllowed) { mQuitAllowed = quitAllowed; //通过native方法去初始化化消息队列 mPtr = nativeInit(); }
3.4.2 消息入队
3.4.2.1 MessageQueue的enqueueMessage()
boolean enqueueMessage(Message msg, long when) { //判断消息是否关联了Handler,若无则抛异常 if (msg.target == null) { 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(); //获取message的when时间(触发时间) msg.when = when; //获取消息队列里的消息 //Message是个链表结构 Message p = mMessages; boolean needWake; //如果消息队列里没有消息 //或者发生时间(when)在链表的头结点之前 if (p == null || when == 0 || when < p.when) { //将消息插入到链表的头结点,即放入队头 msg.next = p; mMessages = msg; //如果处于阻塞状态,则唤醒 needWake = mBlocked; } else { //如果消息队列中已存在消息且触发时间(when)在链表的头结点之后 //则插入到队列中间 //通常这里的needWake为false,即不需唤醒消息队列 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) {//如果需要唤醒 //在native层唤醒消息队列 nativeWake(mPtr); } } return true; }
可以看到,消息队列是根据消息触发时间来进行排队的,触发时间最早的消息将会排到队列的头部。当有新消息需要加入消息队列时,会从队列头开始遍历,直到找到消息应该插入的合适位置,以保证所有消息的时间顺序。
如果消息队列需要唤醒,则会在消息加入消息队列后对消息队列进行唤醒。
3.4.3 取出消息
3.4.3.1 MessageQueue的next()
Message next() { final long ptr = mPtr; if (ptr == 0) { //当消息循环已经退出,直接返回 return null; } //只有首次迭代为-1 int pendingIdleHandlerCount = -1; int nextPollTimeoutMillis = 0; //死循环 for (;;) { if (nextPollTimeoutMillis != 0) { Binder.flushPendingCommands(); } //这里会阻塞,直到nextPollTimeoutMillis超时或者消息队列被唤醒 nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; if (msg != null && msg.target == null) { //当target为空时,在消息队列中循环查找到下一条异步消息 do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()); } if (msg != null) { //当消息触发时间大于当前时间 if (now < msg.when) { //下一条消息还没准备好,设置一个超时唤醒 nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { // 获取一条消息 mBlocked = false; //消息队列中移除这条消息 if (prevMsg != null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; if (DEBUG) Log.v(TAG, "Returning message: " + msg); //消息标记为使用状态 msg.markInUse(); //返回消息 return msg; } } else { //没有消息 nextPollTimeoutMillis = -1; } //如果消息正在退出,返回null if (mQuitting) { dispose(); return null; } //如果当前是第一次循环时 //且当前消息队列为空时,或者下一条消息还没准备好时 //即当前处于空闲的状态 //那么就获取Idle Handler的数量 if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) { pendingIdleHandlerCount = mIdleHandlers.size(); } if (pendingIdleHandlerCount <= 0) { //没有idle handlers 需要运行,继续循环并等待。 mBlocked = true; continue; } if (mPendingIdleHandlers == null) { mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)]; } mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers); } //运行idle handlers //只有第一次循环时才会执行这些代码块 for (int i = 0; i < pendingIdleHandlerCount; i++) { final IdleHandler idler = mPendingIdleHandlers[i]; //释放handler的引用 mPendingIdleHandlers[i] = null; boolean keep = false; try { //执行idler的queueIdle() keep = idler.queueIdle(); } catch (Throwable t) { Log.wtf(TAG, "IdleHandler threw exception", t); } if (!keep) { synchronized (this) { //根据idler.queueIdle()的返回值来判断是否移除idler //即返回true的话能够重复执行 mIdleHandlers.remove(idler); } } } //重置IdleHandler的数量为0,这样就保证不会重复运行 //即只有第一次循环时会运行 pendingIdleHandlerCount = 0; //重置超时时间为0 //即当调用一个idle handler时, 一个新的消息能够被分发,因此无需等待即可回去继续查找还未被处理的消息 nextPollTimeoutMillis = 0; } }
取出消息时,如果没有消息或者超时时间还没到,则会处于阻塞的状态,直到超时时间过去或者消息队列被唤醒。当消息准备好时,才会返回消息出去。
另外,如果当前处于空闲的状态,则会执行IdleHandler
中的方法。
3.4.4 消息队列退出
3.4.4.1 MessageQueue的quit()
void quit(boolean safe) { //主线程的消息队列是不允许退出的,主要还是看mQuitAllowed的值 if (!mQuitAllowed) { throw new IllegalStateException("Main thread not allowed to quit."); } synchronized (this) { //如果正在退出,则直接返回,防止多次操作 if (mQuitting) { return; } //设置为正在退出中 mQuitting = true; //判断是否安全移除 if (safe) { //移除尚未触发的所有消息 removeAllFutureMessagesLocked(); } else { //移除所有的消息 removeAllMessagesLocked(); } nativeWake(mPtr); } }
如果是安全退出,那么只会移除尚未触发的所有消息,对于正在触发的消息并不移除;
如果不是安全退出,则直接移除所有的消息。
4.一些面试题
这里收集了一些常见的面试题来解答一下,大部分答案其实都能在上面的分析中找到的,所以嘛,要认真看代码。
这里只列出一部分题目,如果有好的题目也可以留言补充哈~
4.1 Q:Looper.loop()是个死循环,主线程为什么不会卡死?
对于一个线程,如果执行完代码之后就会正常退出。但是对于主线程,我们肯定是希望能够一直存活的,那么最简单的方法就是写个死循环让它一直在执行,这样就不会退出了。那么主线程为什么不会卡死呢?如果消息队列里面有消息,Looper
就取出来出来;如果没有消息就会阻塞,直到有新消息进来唤醒消息队列去处理,这一过程就是在这个死循环中处理的,所以说Looper
本身是不会卡死的。像Activity
的onCreate()
、onResume()
等生命周期实际上就是在这个死循环中执行的。如果我们在Activity
的onCreate()
、onResume()
中做一些耗时操作,可能就会发生掉帧,甚至出现ANR,这才是我们看到的卡顿卡死现象。
4.2 Q:一个线程中是否可以有多个Handler?如果有多个,分发消息是如何区分的?
是可以有多个的。我们使用Handler
发送Message
时,Message
中的target
变量会保存当前的Handler
引用,分发消息时就是靠这个target
来区分不同的Handler
。
作者:四月葡萄
链接:https://www.jianshu.com/p/b3cd218cfbb7