源码分析基于Android-28
1.Handler存在意义是什么?
(1)解决线程间通讯的问题;
(2)简化wait、notify的使用;
(3)Android驱动机制,期望所有对ui 的操作都转到主线程,并且保证任务是有序执行的。app 的设计就是单线程处理ui的。
2.Handler、MessageQueue、Looper是什么关系?
(1)有个很重要的关键词:唯一性;
(2)每个线程都只有一个Looper,ThreadLocal保证了线程间单例;
(3)一个Looper对应一个MessageQueue;
(4)多个Handler可以对应同一个Looper。
3.Handler、MessageQueue、Looper的作用分别是什么?
(1)Handler 负责发送、处理消息,执行者;
(2)Looper 负责从MessageQueue中取消息,分发给相应的Handler来处理,分发者;
(3)MessageQueue负责存放消息,保存者,它是一个单链表不是我们通常用的集合;
![](https://img.haomeiwen.com/i20808175/fedc2fa7c30c6ee4.png)
4.源码分析
4.1Handler构造函数
如果使用无参的构造函数,最终会调用
Handler(Callback callback, boolean async)
主要工作:
(1)Looper.myLooper(),获取当前线程的Looper赋值给成员变量mLooper ;
Tips:如果在子线程构造Handler,没有调用Looper.prepare的情况下,将会抛出
new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()")
异常;
如果是在主线程构造则不会,因为主线程的Looper在ActivityThread.main方法中就已经创建了;
Looper的构造方法是private,必须通过Looper.prepare静态方法来创建,加上ThreadLocal就保证了Looper线程唯一;
(2)mLooper.mQueue,获取Looper的消息队列赋值给成员mQueue ;
(3)mCallback ,是Handler全局的callBack,默认情况下为空;
(4)mAsynchronous ,默认是false,也就是对应普通消息;
(5)经过(1)(2)两个步骤,Handler就和当前线程的Looper、MessageQueue关联到一起了。
public Handler(Callback callback, boolean async) {
......
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
看下Looper是如何构造出来的,
Looper.prepare()
主要工作:
(1)通过sThreadLocal判断当前线程是否已经创建了Looper,是则抛出异常,否则创建Looper并保存到sThreadLocal中,Looper的创建伴随着MessageQueue创建,这里通过ThreadLocal保证了Looper是线程唯一;
(2)注意:主线程的Looper是不能退出的,所以传入的参数quitAllowed为false,而子线程的是允许退出,一旦Looper退出,也就是for循环结束,那么线程也就结束了;
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
看下发送消息的逻辑,基本上Handler所有跟发送消息相关的方法,最终都调到
4.2Handler.enqueueMessage()
主要工作:
(1) msg.target = this,给msg的target赋值,当该消息需要被处理时,将由target来处理;
(2)mAsynchronous,在Handle的构造函数提到,该变量默认情况为false,消息为普通消息,这有别于异步消息;
(3)queue.enqueueMessage,将消息放入队列;
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
MessageQueue.enqueueMessage()
主要工作:
(1)判断msg是否有target,没有则抛出异常;
(2)判断msg是否已经被使用,被使用则抛出异常;
(3)存在多个子线程往主线程队列发消息的情况,synchronized 关键字保证了后续的处理是线程安全的;
(4)判断MessageQueue是否已经退出了,已经退出了则返回;
(5)Message p = mMessages,使得p都指向单链表头部消息;
(6) if (p == null || when == 0 || when < p.when) ,p == null,表示MessageQueue为空,when == 0,表示要插入的msg插入查到队列的最前面,when < p.when,表示msg执行时间小于头部消息执行时间,满足以上三个条件中的一个,则将消息插入链表头部,并让mMessages 指向该msg,如果线程原本是block的,后续则唤醒;
(7)上述条件不满足,则遍历MessageQueue,把msg插入到合适的位置,MessageQueue是按执行时间从小到大排序的,如果线程原本就是休眠,且队头是消息屏障,且消息本身是异步消息,且是最早的一条,则需要唤醒,为什么是最早一条才有可能需要唤醒呢?因为还没轮到你执行呢;
(8)如果线程需要被唤醒,则通过nativeWake唤醒线程,nativeWake 就是往管道eventFd写了一个数,监听者是直监听这个eventFd。
Tips:为什么没有采用wait、notify方法呢?旧的版本是采用这两个方法来唤醒线程的,因为后面设计到native的处理,所以就通过native层来唤醒线程。
boolean enqueueMessage(Message msg, long when) {
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) {
......
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
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;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
![](https://img.haomeiwen.com/i20808175/257d9843347120e3.png)
取消息
4.3 Looper.loop()
主要工作:
(1)queue.next,从MessageQueue中取出消息,如果MessageQueue为空,则queue.next一直阻塞不会返回,如果MessageQueue退出了,则queue.next返回null;
(2)msg.target.dispatchMessage(msg),把msg丢给相应的target来处理;
(3)msg.recycleUnchecked,把消息放到复用池中,重复使用。
Tips:主线程的Looper是在ActivityThread.main方法中启动的,而子线程需要手动调用,并且主线程的Looper是不能退出的;
public static void loop() {
......
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
......
try {
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
}
......
msg.recycleUnchecked();
}
}
MessageQueue.next()
主要工作:
(1)当ptr 等于0,则表示退出了MessageQueue;
(2)第一次循环,nextPollTimeoutMillis 为0,则nativePollOnce立即返回,线程不休眠,直接执行下面的代码取链表头消息;nativePollOnce会调到native层epoll_wait监听管道内的事件,当管道内的事件fd与期望的fd一致,则唤醒线程;
(3)考虑一个线程发消息跟一个线程取消息,synchronized 关键字保证了取消息是线程安全的;
(4) if (msg != null && msg.target == null) ,条件满足,则表示队头为消息屏障,后面的普通消息延迟执行,取第一个异步消息优先执行,常用在优先执行绘制任务;
(5)上述(4)不成立条件,可能无消息或队头不是消息屏障,则取头消息;
(6)上述(4)(5)获取到的消息不为空(无论是普通消息还是异步消息),消息执行时间到了(now < msg.when条件不成立),则返回msg,退出该循环,并把mBlocked 置为false,否则设置休眠时间nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE),线程进入休眠等待超时唤醒;
(7)上述(4)(5)获取到的消息为空,nextPollTimeoutMillis则赋值为-1,线程需要一直休眠,直到被唤醒;
(8)如果MessageQueue没有消息或消息没有到要执行的时间,mIdleHandlers的消息将得到执行机会;mIdleHandlers集合保存延迟执行的消息,常用在延迟初始化第三方sdk;
(8)如果mIdleHandlers为空,则mBlocked 置为true,并进入下一次循环;
(9)如果mIdleHandlers不为空,执行 idler.queueIdle方法,mIdleHandlers处理完,则nextPollTimeoutMillis赋值为0,并进入下一次循环;
Tips:最后一步为什么休眠时间为0呢?可能在执行延迟任务的时候,有一个任务已经放入了队列或者延迟任务执行比较久,前面设置timeout已经到了,所以需要立马进入下一次的循环。
Tips:计算时间采用了SystemClock.uptimeMillis而不是我们常用的System.currentTimeMillis(),如果系统时间修改了,System.currentTimeMillis()获取时间不准,前者获取从开机到目前的时间,不包括休眠时间,相对比较准。
Tips:一个取消息周期,mIdleHandlers的消息可以得到一次执行机会,因为不能把资源浪费在Idle消息,普通消息要得到被执行的机会;
Message next() {
final long ptr = mPtr;
if (ptr == 0) {//(1)
return null;
}
......
int nextPollTimeoutMillis = 0;
for (;;) {//(2)
......
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {//(3)
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {//(4)
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {//(5)
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
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 {//(6)
// No more messages.
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
// If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {//(7)
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {//(8)
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
for (int i = 0; i < pendingIdleHandlerCount; i++) {//(9)
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;//(10)
}
}
![](https://img.haomeiwen.com/i20808175/cbf8444efaf51305.png)
处理消息
Handler.dispatchMessage()
主要工作:
(1)如果是调用sendMessage(Message msg)发送消息,则msg的callback为空,如果是调用post(Runnable r)发送消息,则msg的callback为Runnable,handleCallback(msg)则是回调Runnable的run方法;
(2)如果Handler有全局mCallback,则触发mCallback.handleMessage处理消息,并且返回值为true的话,则后续的Handler.handleMessage不会被触发:
(3)如果Handler没有全局mCallback,默认情况下没有,或者mCallback.handleMessage返回false,则触发handleMessage进行消息处理;
(4)这里有一点责任链设计模式的影子;
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
![](https://img.haomeiwen.com/i20808175/abce6e48f8c679f7.png)
补充Message成员变量:
// 用户自定义,主要用于辨别Message的类型
public int what;
// 用于存储一些整型数据
public int arg1;
public int arg2;
// 可放入一个可序列化对象
public Object obj;
// Bundle数据
Bundle data;
// Message处理的时间。相对于1970.1.1而言的时间
// 对用户不可见
public long when;
// 处理这个Message的Handler
Handler target;
// 使用Handler post方法发送消息时,对应的Runnable就是callback
Runnable callback;
// MessageQueue是一个链表,next表示下一个
Message next;
常见面试题:
1.谈谈Android的消息机制
从它是什么东西、解决了什么问题、发消息、取消息、分发消息、休眠唤醒是如何实现的这几个方面回到。
2.post(Runnable r)和sendMessage(Message msg)区别?
前者会构造一个Message,然后给callback赋值为r,后者没有,两者最终都会调到enqueueMessage方法中。
2.Looper.loop 是一个死循环,为什么不会出现ANR或者卡顿呢?
卡顿本质:主线程执行耗时任务或者频繁GC,导致无法执行绘制任务,上一帧重复绘制。
ANR本质:没有在规定时间内完成任务。
正因为这个死循环才保证发往主线程的任务及时响应,例如绘制任务,既然绘制任务能够及时响应,那就不会出现卡顿或者ANR,而且没有消息需要处理,线程会休眠让出cpu。
3.消息延迟是如何实现的
MessageQueue是一个单链表,按Msg的when从小到大进行排序,这样就实现了延迟消息的处理,而且计算时间采用了SystemClock.uptimeMillis而不是我们常用的System.currentTimeMillis(),如果系统时间修改了,System.currentTimeMillis()获取时间不准,前者获取从开机到目前的时间,不包括休眠时间,相对较为准。
4.当有消息正在被处理,新插入队头的消息如何处理?
当前消息被处理完,会从头开始执行消息。因为在MessageQueue.enququeMessage中,当新消息需要插入队头,mMessages会指向新消息,那么在MessageQueue.next中,就会取mMessages消息就行处理。
5.MessageQueue如何保证线程安全?
在MessageQueue.enqueueMessage以及next方法都加了锁,保证线程间安全;
6.如何保证发消息、取消息不死锁?
情况1,取消息next获得了锁,那么enqueueMessage就处于等待,
取到了消息需要处理就返回,释放锁,退出for循环,那么enqueueMessage就可以获得锁,没有取到消息或者消息没有达到执行执行,虽然不退出for循环,但是会释放锁而且进入nativePollOnce等待,那么enqueueMessage就可以获得锁,反之enqueueMessage成功插入消息,也是会释放锁的。
6.为什么Looper.loop以及MessageQueue.next都有一层for循环,那么岂不是两层循环?
Looper.loop保证消息分发以后,继续从MessageQueue取消息;
MessageQueue.next,保证被唤醒继续取消息进行分发,直到有效消息。
7.Handler如何实现线程切换?
这个题目感觉有点废话,Handle就是实现线程切换的啊,有什么好问的??但是仔细想想,从代码层面是如何实现呢?关键就在于Looper.loop方法是在哪里执行的?以主线程Looper为例,主线程的Looper是在ActivityThread.main方法创建,也是在它里面执行的,那么就是说Looper.loop是在主线程执行的,从而保证了Message.next取消息分发是在主线程。那么,当子线程存在Handler拷贝,利用拷贝往Handler对应的MessageQueue发送消息,这样就现实了线程间切换,MessageQueue是线程共享资源。
8.Handler的唤醒机制
是Linux的唤醒机制,等待方通过nativePollOnce会调到native层epoll_wait监听管道内的事件,当管道内的事件fd与期望的fd一致,则唤醒线程。
以上分析有不对的地方,请指出,互相学习,谢谢哦!
网友评论