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
共同學(xué)習(xí),寫下你的評(píng)論
評(píng)論加載中...
作者其他優(yōu)質(zhì)文章