1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > Android消息机制(Handler机制) - 线程的等待和唤醒

Android消息机制(Handler机制) - 线程的等待和唤醒

时间:2021-10-03 07:47:53

相关推荐

Android消息机制(Handler机制) - 线程的等待和唤醒

我们都知道,Android的Handler机制,会在线程中开启消息循环,不断的从消息队列中取出消息,这个机制保证了主线程能够及时的接收和处理消息。

通常在消息队列中(MessageQueue)中没有消息的时候,会调用MessageQueue的native方法,让主线程wait,以避免主线程在for循环中,什么也没干,白白的浪费CPU资源。

这方面的内容就不再详细描述了,感兴趣的可以看这篇博客,写的非常清晰明了。

Android消息机制-Handler

这篇文章主要还是想聊聊,线程等待和唤醒的时机的问题。

1.异步消息和同步消息

当MessageQueue中没有消息时,主线程就会进入wait状态。这句话,其实并不是那么严谨的,这是因为Message不仅有我们常用的同步消息,还有异步消息。

在Message类中,有一个标志位,来标明 该Message是异步的,还提供了对应的方法来判断Message是不是属于异步消息。

/** If set message is asynchronous *//*package*/ static final int FLAG_ASYNCHRONOUS = 1 << 1;..................................../*** Returns true if the message is asynchronous, meaning that it is not* subject to {@link Looper} synchronization barriers.** @return True if the message is asynchronous.** @see #setAsynchronous(boolean)*/public boolean isAsynchronous() {return (flags & FLAG_ASYNCHRONOUS) != 0;}

异步消息和我们常用的同步消息都是通过MessageQueue的enqueueMessage方法入队,next方法出队。MessageQueue对这两种信息的不同处理,取决于特殊的barrier机制。

只要是Message在走到最后的enqueueMessage方法,成功入队之前,一定要保证target不为null,(target是Message类中的其中一个属性,一般指明Message需要在哪个Handler中执行)。

当要使用异步消息时,我们必须先调用MessageQueque中的postSyncBarrier方法,将barrier插入到

MessageQueque的队头。

barrier是一个特殊的Message,他的target为null。当MessageQueue的队头被设置为barrier的使用,MessageQueue将会忽略队列中的同步消息,只会处理队列中的异步消息

通过barrier机制,保证了异步消息的执行优先性。

介绍完了异步消息的机制,我们再来看消息队列中等待和唤醒的具体操作

线程的等待

线程的等待是通过调用MessageQueue的nativePollOnce()方法实现的,该方法只在Message的next方法。同时MessageQueue中还有一个mBlocked变量,来指示线程是否被阻塞。

// Indicates whether next() is blocked waiting in pollOnce() with a non-zero timeout.//指示 next() 是否在 pollOnce() 中以非零超时阻塞等待。 private boolean mBlocked;............private native void nativePollOnce(long ptr, int timeoutMillis);

nativePollOnce()是native方法,是用c++实现的,他传入两个参数,第一个可以理解为线程标识符,可以

第二个传入的参数就是线程睡眠的时间,比如传入1,则表示在1ms后唤醒线程。如果传入的int为-1,则表示线程无限等待。

对于next方法流程的解析,已经有写的非常好的文章了,而且这个方法的流程也不太复杂,所以这里只选取与调用nativePollOnce()方法,相关的代码片段。

Message next() {.........................int nextPollTimeoutMillis = 0;//默认是0for (;;) {//开启循环if (nextPollTimeoutMillis != 0) {Binder.flushPendingCommands();}nativePollOnce(ptr, nextPollTimeoutMillis);//每次在循环的开始,就根据传入的nextPollTimeoutMillis的值来控制线程synchronized (this) {// Try to retrieve the next message. Return if found.//试图取消息final long now = SystemClock.uptimeMillis();Message prevMsg = null;Message msg = mMessages;if (msg != null && msg.target == null) {//判断是否是屏障,即需不需要处理异步消息// Stalled by a barrier. Find the next asynchronous message in the queue.又是异步消息的处理,do {prevMsg = msg;msg = msg.next;} while (msg != null && !msg.isAsynchronous());//循环找异步消息,如果没找到,那么msg变成null}if (msg != null) {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不为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 {//队列为空,没有消息,队列将会无限等待 ps:也可能是没找到异步消息// No more messages.nextPollTimeoutMillis = -1;}...............................................

可以看到在next()方法中,首先会检查队列中的队头Message,若队头Message不为null,且Message的target为null,则说明队列中设置了barrier,需要优先处理异步消息。

紧接着程序会遍历队列,寻找异步消息,如果没有找到异步消息,则把Message置为null。

如果队头Message为null,说明消息队列中没有任何消息,或者是在设置Barrier后,队列中却没有任何一个异步消息,此时会将nextPollTimeoutMillis 的值设为-1,在下一次循环开始的时候,调用nativePollOnce(),传入nextPollTimeoutMillis ,线程进入wait状态。

排除了这两种情况,next方法会根据取到的Message的when的值(上面是该Message需要执行的时间),设置nextPollTimeoutMillis ,同样也在下一次循环的开始,调用nativePollOnce()。

线程的唤醒

下面是唤醒线程的过程。线程的唤醒,是通过MessageQueue中的nativewake方法实现的。

他在MessageQueue中的quit(),removeSyncBarrier()和enqueueMessage()方法中被调用。

private native static void nativeWake(long ptr);

接下来,我们就开始逐一分析nativeWake()方法的调用原因和时机。

quit()

先来看quit()方法,他的作用是清空消息队列,退出消息循环。可以根据传入的参数来决定是否安全退出(确保队列中的消息执行完)。

void quit(boolean safe) {if (!mQuitAllowed) {//不允许退出throw new IllegalStateException("Main thread not allowed to quit.");}synchronized (this) {//保证线程安全if (mQuitting) {//mQuitting为true,表示正在执行退出程序,直接returnreturn;}mQuitting = true;//mQuitting置为trueif (safe) {removeAllFutureMessagesLocked();//安全清空消息} else {removeAllMessagesLocked();//直接清空}// We can assume mPtr != 0 because mQuitting was previously false.//意思是,可以假定这里调用nativeWake方法是合法的nativeWake(mPtr);//唤醒线程}}

可以看到在quit()方法的最后部分,调用了nativeWake方法。为什么要在quit()的最后唤醒线程呢?

我个人的想法是,quit()方法只是退出消息循环,并不是要杀死线程,我们不希望线程在退出循环后可能会处于wait状态。

在清空消息队列后,由于Loop仍在不断调用next方法,线程很可能处于wait状态(看上面对next方法的分析),所以这时需要唤醒线程。

removeSyncBarrier()

removeSyncBarrier()方法负责移出队列中的barrier,在官方注释里也写的很清楚

如果队列不再被barrier阻塞,则会唤醒队列

下面我们看他的具体实现:

public void removeSyncBarrier(int token) {// Remove a sync barrier token from the queue.// If the queue is no longer stalled by a barrier then wake it.synchronized (this) {//保证线程安全Message prev = null;Message p = mMessages;//p指向队头while (p != null && (p.target != null || p.arg1 != token)) {prev = p;p = p.next;}if (p == null) {//没找到barrier,抛异常throw new IllegalStateException("The specified message queue synchronization "+ " barrier token has not been posted or has already been removed.");}final boolean needWake;if (prev != null) {//barrier不在队头,属于特殊情况prev.next = p.next;needWake = false;//不唤醒} else {mMessages = p.next;//barrier的下一个结点needWake = mMessages == null || mMessages.target != null;//除非barrier的下一个结点,还是barrier,不然needwake就为true}p.recycleUnchecked();//回收p// If the loop is quitting then it is already awake.// We can assume mPtr != 0 when mQuitting is false.if (needWake && !mQuitting) {//如果正在退出消息循环,那也没必要wake了,quit方法为wakenativeWake(mPtr);}}}

removeSyncBarrier()方法中的wake逻辑其实并不复杂,如果有barrier位于队头,barrier的下一条Message不属于barrier(target不为null),且排除调用quit()方法,正在进行退出操作的特殊情况,就会调用nativeWake()方法,唤醒线程。

其实当初看到这里的时候,还有一点小疑惑。如果barrier位于队头,且barrier的下一条Message为null的时候,说明消息队列为空,其实没有必要唤醒线程。后来一想,可能是为了符合 **“单一职责”**的原则吧!removeSyncBarrier()就只是负责移除barrier,消除barrier的影响,再去判断消息队列是否为空,确实是在做超出职责的事情了。

enqueueMessage()

enqueueMessage()负责将消息加入消息队列,在这个方法里在一些情况下也会调用nativeWake()方法,唤醒线程。

下面的代码就只展示方法中和唤醒线程有关的逻辑:

........................................................msg.markInUse();//标记已经使用msg.when = when;Message p = mMessages;//头结点boolean needWake;if (p == null || when == 0 || when < p.when) {//传入的Message是头节点// New head, wake up the event queue if blocked.msg.next = p;mMessages = msg;needWake = mBlocked;//如果队列阻塞了话,needwake就为true} 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 (;;) {//根据when,将message放到队列中的相应位置prev = p;p = p.next;if (p == null || when < p.when) {break;}if (needWake && p.isAsynchronous()) {//再次检测,排除要插入的异步消息前面有异步消息的情况needWake = false;}}msg.next = p; // invariant: p == prev.nextprev.next = msg;}// We can assume mPtr != 0 because mQuitting is false.if (needWake) {nativeWake(mPtr);//唤醒队列}

其实在enqueueMessage()方法中,官方注释也写的很清楚了。

当传入的Message为队头消息时,会根据mBlocked变量的指示,如果线程处于wait,则唤醒线程。

否则,只有在传入的Message为队列中处于最前面的异步消息时,才会唤醒队列。

总结

Handler机制中线程wait的情况:

1.MessageQueue为空,没有消息,则会无限等待

2.MessageQueue中设置了barrier,可在消息队列中却没有找到异步消息,也会无限等待。

3.还没到需要处理的Message的指定处理时间,线程会等待一段时间后(计算处理出来的时间差),自动唤醒自己。

Handler机制中wake线程的情况:

1.调用quit(),退出消息循环的最后,会wake线程

2.移出队列中的barrier时,如果barrier正确位于队头,且下一条Message不属于barrier(target不为null)时,wake线程。(还需要排除正在quit()的情况)

3.在消息入队时,如果传入的Message为队头消息时,且线程处于wait,则会唤醒线程。

4.在消息入队时,如果传入的Message为异步消息,且位于队列的最前端,若此时线程处于wait状态,也会唤醒线程。

(注意:3和4是互斥的,由于barrier的存在,异步消息不可能位于队头)

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。