一、Looper
每个线程只能有一个Looper,主线程创建Looper,在ActivityThread的main方法里
public static void main(String[] args) {
……
Looper.prepareMainLooper();
……
Looper.loop();
……
}
Looper通过prepare进行创建,构造函数中new出来mQueue,Looper创建后保存进了线程的ThreadLocalMap里。prepare方法只能调用一次,prepare之后调用loop()方法开始消息的遍历。
问1:prepare方法传进去的quitAllowed是干什么用的?
答1:标记Looper是否允许放弃,Looper的quit方法是调用mQueue的quit将消息移除,如果设置quitAllowed为false,则不能调用quit,比如主线程就不允许quit,quit有安全和非安全,非安全移除所有的消息,安全是移除(消息触发时间大于当前时间)的消息。quit后,mQueue就不能在添加消息了。
问2:ThreadLocal原理是什么?
答2:查看其set方法,发现是获取了当前线程的ThreadLocalMap对象,将ThreadLocal本身为key,Looper为value进行存储。
在查看ThreadLocal源码,内部有一个数组private Entry[] table
,具体的存放:
根据key(ThreadLocal)计算出数组索引值,然后将key和value封成Entry(弱引用)放入数组中,ThreadLocal本质是通过每个线程单独一份存储空间,牺牲空间来解决多线程冲突。细节可点击此处
二、Handler
Handler创建时,会创建Looper
public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
问3:Handler发送消息和接收消息进行分发。
答3:可以通过sendMessageXXX和postXXX方法来发送消息,使用post方法发送消息时,会在msg中传入一个runnable给callback。最终会调用enqueueMessage方法将消息放入消息队列中,会设置Handler为消息的target。
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
在Looper的loop()方法会遍历获取消息,调用消息target的dispatchMessage方法,即Handler的dispatchMessage方法,该方法即消息的分发:
public void dispatchMessage(Message msg) {
//1.首先判断如果消息自带callback,则执行runnable的run进行消息处理。
if (msg.callback != null) {
handleCallback(msg);
} else {
//2.如果消息没有callback,则判断Handler是否有mCallback(构造函数中传入)
//如果有,则执行callback里的handleMessage,且根据返回值判断是否继续
//执行Handler中的handleMessage,返回true则结束,返回false 则继续
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
//走到了此处,执行handleMessage
handleMessage(msg);
}
}
问4: 消息是有同步和异步的,具体区别表现在哪。
问5:正常情况下,消息都是同步,Handler设置了async为true时,则所有该Handler发送的消息都是异步,消息也可以单独设置为异步。区别主要还是看是否设置了同步消息屏障。
在MessageQueue中有postSyncBarrier 和 removeSyncBarrier 来发送和移除同步屏障消息,postSyncBarrier 不会唤醒线程, removeSyncBarrier 会唤醒线程(当队列里面有消息时。在遍历消息中,根据target是否为null来判断是否是同步屏障消息。同步屏障消息和普通消息一样可以根据时间来插入到消息队列中的适当位置,并且只会挡住它后面的同步消息的分发,postSyncBarrier方法是私有的,如果我们想调用它就得使用反射。
Handler的构造函数会初始化looper,如果在异步线程中直接创建一个Handler,在获取looper时会抛异常,所以需要先初始化一个looper,调用looper的prepare()方法即可,然后调用loop()方法开始循环消息。如果传入主线成的looper,Looper.getMainLooper()
, 这样就可以实现了异步线程中发送消息让主线程收到的了,最常见的就是利用异步线程做耗时的网络请求,请求回来后通知主线程更新UI。
MessageQueue是一个单链表,入列的消息是按照触发时间的先后顺序排列,可以看下enqueueMessage的源码,注释了一堆
enqueueMessage
boolean enqueueMessage(Message msg, long when) {
……
synchronized (this) {
//quit后,无法再添加消息
if (mQuitting) {
……
msg.recycle();
return false;
}
//标记消息正在被使用
msg.markInUse();
//设置消息触发时间
msg.when = when;
//mMessages:是下一个即将要出队列的消息
Message p = mMessages;
//needWake:是否要唤醒
boolean needWake;
if (p == null || when == 0 || when < p.when) {
//满足插入队列头部的情况,将刚插入的消息置为队列头
//1.p == null 表示消息队列为空,自然就是插入队列头
//2.when == 0 ,通过Handler的XXXAtFrontOfQueue方法,设置when == 0,将消息置为队列头
//3.时间比队列头消息的时间要早
msg.next = p;
mMessages = msg;
//mBlocked为false,没有睡眠,needWake为false表示不需要唤醒。
//mBlocked为true,正在睡眠,needWake为true,表示需要唤醒。
needWake = mBlocked;
} else {
//满足插入队列中部的情况
//正常情况下,插入消息队列中消息的不需要唤醒事件队列,
//除了有“屏障消息”在队列头,并且插入的消息是时间最早的异步消息
//mBlocked为false,没有睡眠,needWake为false表示不需要唤醒。
//mBlocked为ture,正在睡眠,并且p是没有目标发送对象(屏障消息),并且插入的消息是异步的,则需要唤醒
needWake = mBlocked && p.target == null && msg.isAsynchronous();
//根据when < p.when,查找当前msg插入的位置
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
//遍历到最后或者要插入消息的时间小于当前消息的时间
break;
}
//假如正在睡眠,且前面满足唤醒的条件。即needWake为true,
//如果msg前面的消息存在异步,则当前的msg就算为异步,则不需要唤醒。
//正常情况下,插入消息队列中消息的不需要唤醒事件队列,
//除了有“屏障消息”在队列头,并且插入的消息是时间最早的异步消息
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
//插入msg
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// 唤醒判断
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
问5:总结下当消息阻塞时,什么情况需要唤醒消息队列:
答5:
- 插入的消息在队列头部
- 消息队列的头部消息是屏障消息,且插入的消息是异步消息,且是队列中最早的异步消息。
第二点不太好理解,可以看下这两行代码(注释),
//
//如果队列头消息p是同步屏障消息,且插入消息msg是异步消息,就暂定唤醒
needWake = mBlocked && p.target == null && msg.isAsynchronous();
//下面假设needWake为true,也就是(头部是同步屏障消息,插入的消息是异步消息)
Message prev;
for (;;) {
prev = p; // prev 变成了同步屏障消息
p = p.next; // p变成了队列中的下一条
//when < p.when 表示插入的消息msg比p早,就是紧跟在prev(同步屏障消息)之后的消息,这样 needWake 就不变
if (p == null || when < p.when) {
break;
}----
//插入的消息msg比p晚,且p如果是异步消息,怎不用唤醒,所以插入的异步消息需要是最早的异步消息
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
//在遍历的过程中,发现队列中已经有异步消息,比要插入的异步消息要早触发,则不唤醒。
next
Message next() {
// 判断是否初始化,mPtr = nativeInit(); 初始化后,mPtr不为0,获取是否quit,quit后,mPtr 为0
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
//挂起的闲置Handler数,通过addIdleHandler 可以添加IdleHandler
//IdleHandler的优先级最低,在消息队列中没有正常的消息后,才开始处理IdleHandler
int pendingIdleHandlerCount = -1; //
//下次唤醒系统的时间
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
//调用native方法做些刷新命令
Binder.flushPendingCommands();
}
//这里就是开始“睡眠”, nextPollTimeoutMillis大于0,则“睡眠”直到过了nextPollTimeoutMillis时间后醒来
//enqueueMessage方法中调用的nativeWake也可以唤醒
//这里可以回答为什么不会造成anr,
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// 开始检查下一条消息,如果有,则返回
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
//mMessages是上次消息发送出去后,获取的next消息,即这次要发出去的消息
Message msg = mMessages;
if (msg != null && msg.target == null) {
//同步屏障消息,筛选异步消息
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非空,说明同步屏障消息出现,获取了第一个异步消息
//将这次本该发出去的消息最为这个异步消息的下一条消息
prevMsg.next = msg.next;
} else {
//更新下一次要发送的消息
//结果是这次要发送的消息是msg,下一次要发送的消息是mMessages
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// 没有更多消息了
nextPollTimeoutMillis = -1;
}
// quit后,所有挂起的闲置消息不处理
if (mQuitting) {
dispose();
return null;
}
//没有更多消息后,开始处理所有的闲置消息
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// 闲置消息,则设置为阻塞
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// 遍历所有的闲置消息,调用其queueIdle方法
for (int i = 0; i < pendingIdleHandlerCount; i++) {
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);
}
}
}
pendingIdleHandlerCount = 0;
nextPollTimeoutMillis = 0;
}
}
从上面的代码中可以看到一个问题,当同步屏障消息发送后,再发送一条延迟的消息,那么直到延迟消息的时间到了为止,都是在睡眠,让本应该执行的同步消息都不能执行,所有同步屏障消息后,异步消息一定不要是延迟的异步消息。
addIdleHandler可以解决页面卡顿的问题,在这里处理不要紧的操作
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
//处理不要紧的操作
return false;
}
});
Looper中有一个消息队列MessageQueue,通过loop()是对进行遍历,是一个for的死循环,
Q3:为什么这个死循环不会发生ANR
A3:所谓ANR是消息事件没有得到及时处理,如果没有消息,会调用了linux层的代码实现在适当时会睡眠主线程,当消息来了会进行唤醒,即Linux的epoll机制。
epoll机制
https://www.jianshu.com/p/7bc2b86c4d89
https://www.jianshu.com/p/02e4327b7e02