IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android消息机制(Handler机制) - 线程的等待和唤醒 -> 正文阅读

[移动开发]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;//默认是0
        for (;;) {//开启循环
            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,表示正在执行退出程序,直接return
                return;
            }
            mQuitting = true;//mQuitting置为true

            if (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方法为wake
                nativeWake(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.next
                prev.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的存在,异步消息不可能位于队头)

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-12-21 17:37:05  更:2021-12-21 17:37:07 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 10:51:46-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码