前言
在Android中,经常会遇到线程间通信的场景,下面就说说Android中最重要的异步消息机制Handler
异步消息机制Handler
Handler是Android中最重要的异步消息机制,总共由四部分组成:Handler,Message,MessageQueue,Looper
1、主线程创建 Handler 对象(如果在子线程创建,必须保证调用了Looper.prepare()),并重写 handleMessage() 方法。
2、子线程创建 Message 对象,通过第一步创建的Handler 发送消息,handler.sendMessage(message),handler将消息发送到MessageQueue中。
3、Looper 通过loop()循环从 MessageQueue 中取出待处理消息。
4、looper将取出的message分发回 Handler 的 handleMessage() 方法中处理。
Looper
创建 Looper 的方法是调用 Looper.prepare() 方法。注意:在 ActivtyThread 中的 main 方法为我们 prepare 了
java
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
//...
Looper.prepareMainLooper(); //初始化Looper以及MessageQueue
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop(); //开始轮循操作
throw new RuntimeException("Main thread loop unexpectedly exited");
}
Looper.prepareMainLopper()
java
public static void prepareMainLooper() {
prepare(false);//调用prepare(), 消息队列不可以quit
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been
prepared.");
}
sMainLooper = myLooper();
}
}
Looper.prepare(boolean quitAllowed)
java
public static void prepare() {
prepare(true);//消息队列可以quit
}
// 私有的构造函数
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {//不为空表示当前线程已经创建了Looper
throw new RuntimeException("Only one Looper may be created per thread");
//每个线程只能创建一个Looper
}
// ThreadLocal 多线程中重要的知识点,线程上下文的存储变量
sThreadLocal.set(new Looper(quitAllowed));//创建Looper并设置给sThreadLocal,这样get的时候就不会为null了
}
**注意:**一个线程只要一个Looper。如何保证只有一个?
这里用到了ThreadLocal。一句话理解ThreadLocal,threadlocl是作为当前线程中属性ThreadLocalMap集合中的某一个Entry的key值Entry(threadlocl,value),虽然不同的线程之间threadlocal这个key值是一样,但是不同的线程所拥有的ThreadLocalMap是独一无二的,也就是不同的线程间同一个ThreadLocal(key)对应存储的值(value)不一样,从而到达了线程间变量隔离的目的,但是在同一个线程中这个value变量地址是一样的。所以利用ThreadLocal可以保证一个线程只有一个Looper。
为了保证 ThreadLocalMap.set(value) 时,value 不会被覆盖(即Looper不会改变),会先进行上面代码的 if 操作,if (sThreadLocal.get() != null) , 不为空表示当前线程已经创建了Looper,然后直接抛异常结束prepare。如果 if 不成立,则 new Looper()。
MessageQueue
MessageQueue的创建是在Looper构造函数中
java
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);//创建了MessageQueue
mThread = Thread.currentThread(); //当前线程的绑定
}
MessageQueue(boolean quitAllowed) {
//mQuitAllowed决定队列是否可以销毁 主线程的队列不可以被销毁需要传入false, 在MessageQueue的 quit()方法
//省略...
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
Looper.loop()
在 ActivtyThread的main 方法中 Looper.prepareMainLooper() 后 Looper.loop() 开始轮询
java
public static void loop() {
final Looper me = myLooper();//里面调用了sThreadLocal.get()获得刚才创建的Looper对象
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}//如果Looper为空则会抛出异常
final MessageQueue queue = me.mQueue;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) {
//这是一个死循环,从消息队列不断的取消息
Message msg = queue.next(); // might block
if (msg == null) {
//由于刚创建MessageQueue就开始轮询,队列里是没有消息的,等到Handler sendMessage enqueueMessage后
//队列里才有消息
// No message indicates that the message queue is quitting.
return;
}
// This must be in a local variable, in case a UI event sets the logger
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
msg.target.dispatchMessage(msg);//msg.target就是绑定的Handler,详见后面Message的部分,Handler开始
//后面代码省略.....
msg.recycleUnchecked();
}
}
loop方法开启后,不断地从MessageQueue中获取Message,对 Message 进行 Delivery 和 Dispatch,最终发给对应的 Handler 去处理(通过msg.target找到对应的handler)。
**说明:**MessageQueue队列中是 Message,在没有 Message 的时候,MessageQueue借助Linux的ePoll机制,阻塞休眠等待,直到有Message进入队列将其唤醒。
Handler
handler通过发送和处理Message和Runnable对象来关联相对应线程的MessageQueue。
最常见的创建 handler 的方式:
java
//第一种方式
Handler handler=new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
//第二种方式(looper作为参数传入)
Handler handler=new Handler(Looper looper)
第一种方式在内部调用 this(null, false);
java
public Handler(Callback callback, boolean async) {
//前面省略
mLooper = Looper.myLooper();//获取Looper,**注意不是创建Looper**!
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;//创建消息队列MessageQueue
mCallback = callback; //初始化了回调接口
mAsynchronous = async;
}
Looper.myLooper() ;
java
//这是Handler中定义的ThreadLocal ThreadLocal主要解多线程并发的问题
// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
sThreadLocal.get() will return null unless you've called prepare(). 这句话告诉我们 get 可能返回 null 除非先调用 prepare()方法创建 Looper 。在前面已经介绍了
Message
message的创建可以直接 new Message() ,但是有更好的方式 Message.obtain()。因为可以检测是否有可以复用的 Message,复用避免过多的创建、销毁 Message 对象,达到优化内存和性能的目的。
java
public static Message obtain(Handler h) {
Message m = obtain();//调用重载的obtain方法
m.target = h;//并绑定的创建Message对象的handler
return m;
}
public static Message obtain() {
synchronized (sPoolSync) {//sPoolSync是一个Object对象,用来同步保证线程安全
if (sPool != null) {
//sPool是就是handler dispatchMessage 后 通过recycleUnchecked回收用以复用的Message
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
创建 Message 的时候通过 Message.obtain(Handler h) 这个构造方法将message和handler绑定。当然也可以在 Handler 中的 enqueueMessage() 绑定。
Handler 发送消息(消息入队)
Handler 发送消息的重载方法很多,我们用得比较多的方法就是post、sendMessage、sendMessageDelay方法,先挨个看看
post方法
java
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;
}
post接受一个Runnable类型的参数,并将其封装为一个Message对象,并且将Runnable参数赋值给msg的callback字段,这里要记住,后面有用------Runnable什么时候执行的呢?
最后调用的就是sendMessageDelayed
sendMessage方法
java
public final boolean sendMessage(@NonNull Message msg) {
return sendMessageDelayed(msg, 0);
}
最后调用的也是sendMessageDelay,第二个参数是0。
sendMessageDelay方法
可见无论是post还是sendMessage方法,最后都走到了sendMessageDelayed
java
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
// 这里注意第二个参数 @param updateMillis 是一个具体的时间点。
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
//......
return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg, long uptimeMillis) {
// 【关键点6】这里要注意target指向了当前Handler
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
// 【关键点7】调用到了queue#enqueueMessage方法
return queue.enqueueMessage(msg, uptimeMillis);
}
最终会走到handler中的enqueueMessage方法,然后走到quene.enqueneMessage(msg, uptimeMillis),将message放入了消息队列,那么我们来看看,是如何放入队列的吧。
java
boolean enqueueMessage(Message msg, long when) {
//......
// 【关键点8】对queue对象上锁
synchronized (this) {
//......
msg.markInUse();
msg.when = when; // msg的when时刻赋值
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
// 翻译:新的头结点,如果queue阻塞,则wakeup唤醒
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 (;;) { // for循环,break结束时when < p.when,说明按照when进行排序插入,或者尾节点
prev = p;
p = p.next;
if (p == null || when < p.when) {
break; // 【关键点9】 找到插入位置,条件尾部或者when从小到大的位置
}
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); // native方法,唤醒
}
}
return true;
}
这里面将msg放到消息队列中,可以看到这个队列是一个简单的单链表结构,按照msg的when进行的排序,并且进行了synchronized加锁,确保添加数据的线程安全。之所以采用链表的数据结构,原因是链表方便插入。
初看源码的时候,应该忽略掉wakeup这些处理,关注msg是如何加入队列即可。
到这里,我们了解了message是如何加入消息队列MesssageQueue。但是消息什么时候执行,以及post(Runnable)中的Runnable什么时候才执行。
消息出队执行
前面提到的Looper.loop()中,主要调用me.mQueue.next()获取一个消息msg,注意这里可能阻塞。这里调用了msg.target.dispatchMessage(msg),这里msg.target可以回头看看前面Message创建赋值的地方。所以这里就将消息分发给了对应的Handler去处理了。待会儿再看Handler.dispatchMessage,我们接着看next方法是怎么取消息的。
java
Message next() {
// Return here if the message loop has already quit and been disposed.
// This can happen if the application tries to restart a looper after quit
// which is not supported.
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
//......
int nextPollTimeoutMillis = 0;
// 【关键点12】继续死循环
for (;;) {
//......
nativePollOnce(ptr, nextPollTimeoutMillis);
// 加锁
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis(); // 当前时间
Message prevMsg = null;
Message msg = mMessages; // msg 指向链表头结点
if (msg != null && msg.target == null) { // 这个if可以先忽略
// 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) {
// 【关键点13】如果当前时间小于头结点的when,更新nextPollTimeoutMillis,并在对应时间就绪后poll通知
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; // 断链处理,等待返回
//......
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
//......
}
//......
// 下面是IdleHandler的处理,还不是很了解
}
}
next方法中也是一个死循环,不断的尝试获取当前消息队列已经到时间的消息,如果没有满足的消息,就会一直循环,这就是为什么会next会阻塞的原因。
看完了next方法,获取到了msg,回到刚才的msg.target.dispatchMessage(msg),接着看Handler是如何处理消息的。
java
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
// 如果消息有CallBack则直接,优先调用callback
handleCallback(msg);
} else {
// 如果Handler存在mCallback,优先处理Handler的Callback
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
// 此方法会被重写,用户用于处理具体的消息
handleMessage(msg);
}
}
private static void handleCallback(Message message) {
message.callback.run();
}
dispatchMessage方法中优先处理Message的callback,再回头看看Handler.post方法应该知道callback是个啥了吧。
如果Handler设置了mCallback, 则优先判断mCallback.handleMessage的返回值,这个机制可以让我们做一些勾子,监听Handler上的一些消息。
handleMessage(msg)这个方法一般在创建Handler时被重写,用于接收消息。
总结
1、一个线程只能有一个Looper,如何确保?
答案:ThreadLocal
2、Handler.post(Runnable)会将Runnable封装到一个Message中。
3、MessageQueue采用单链表的实现方式,并且在存取消息时都会进行加锁。
4、Looper.loop采用死循环的方式,会阻塞线程。那么为什么主线程不会被阻塞?
因为Android是事件驱动的,很多的系统事件(点击事件、屏幕刷新等)都是通过Handler处理的,因此主线程的消息队列,会一直有消息的。
5、Handler是如何实现线程切换的?
Looper和MessageQueue和线程绑定的,也就是说这个消息队列中的所有消息,最后分给对应的Handler都是在创建Looper的线程。所以无论Handler在什么线程发送消息,最后都回到创建Looper的线程中执行。
6、Thread和Looper、MessageQueue是一对一的关系,Looper、MessageQueue对于Handler是一对多的关系。这里要注意,一个具体的Handler实例,肯定只关联一个Looper和queue的哟。
👀关注公众号:Android老皮!!!欢迎大家来找我探讨交流👀