Handler & Looper & MessageQueue关系简述
- 一个线程至多有一个looper;一个looper有一个mq;一个mq对应多个message;一个message对应多个handler。
- 消息类型:同步、异步、同步屏障消息。
- 无限循环:在队列中没有消息时,当前线程进入阻塞状态,当有消息进来时才唤醒该线程,因此不会占用cpu的资源。
消息分发的优先级:
- Message的回调方法:message.callback.run(),优先级最高
- Handler的回调方法:Handler.mCallback.handleMessage(msg)
- Handler的默认方法:Handler.handleMessage(msg)
//1.直接在runnable中处理任务
handler.post {
}
//2.使用Handler.Callback来处理消息
val handler:Handler = object :Handler(Callback {
return@Callback true
})
//3.使用handleMessage处理消息
val handler = object :Handler(){
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
}
}
-
为什么主线程中创建Handler没有绑定looper,也能发送消息呢?
- 在ActivityTread.main()方法中调用了Looper.prepareMainLooper()方法创建了主线程的Looper,然后调用了loop()开启消息队列轮询
- 通常我们认为ActivityTread就是主线程,实际上它并不是一个线程,而是主线程操作的管理者
class Looper{
//相当于map,key时线程,value是looper
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
//一个线程只能有一个Looper,prepare不能重复声明
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));
}
public static @Nullable Looper myLooper() {
//获取当前线程的looper
return sThreadLocal.get();
}
}
//使用post方法消息入队,最后会把runnable包装成一个message
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
//使用享元设计模式,提高内存的利用率
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
//当消息使用完,都会调用这个方法回收消息
void recycleUnchecked() {
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details.
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = UID_NONE;
workSourceUid = UID_NONE;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
消息入队,最后都会调用到enqueueMessage方法中,enqueueMessage方法的执行逻辑分为两个部分:
1. 如果新消息的被执行时间when比队列中任何一个消息都早,则插入队头,唤醒Looper。
2. 如果当前队头是同步屏障消息,并且新消息是异步消息,则唤醒Looper。
-
postSyncBarrier同步屏障消息(该方法无法被开发者调用)
-
message.target=null,这类消息不会被真的执行,它起到了flag标记的作用,mq在遍历消息队列时,如果队头是同步屏障消息,那么会忽略同步消息,优先让异步消息得到执行。这就是它的目的,一般异步消息和同步消息会一同使用。
-
异步消息&同步屏障 使用场景
- ViewRootImpl接收屏幕垂直同步消息事件用于驱动UI绘制
- ActivityThread接收AMS的事件驱动生命周期
- InputMethodManager分发软键盘输入事件
- PhoneWindowManager分发电话页面各种事件
-
-
消息分发
消息分发核心方法在于loop方法,loop方法里面会的调用的linux里的epoll机制。handler最终都是调到adk(c/c++)层。epoll会把哪个流发生了怎样的I/O事件通知我们。此时我们对这些流的操作都是有意义的。
ThreadLocal
- ThreadLocal提供了线程独有的局部变量存储能力,可以在整个线程存活的过程中随时取用。
- 注意ThreadLocal没有线程安全的能力。
多个线程操作上述的local对象的usr对象,进行数据修改之后,user变量会更改。
面试经典问题