【17】Android 线程间通信(二) - Handler

概述

记得上篇文章我们留下的问题吗?如果还没有看过上一篇讲解Handler基本原理文章的同学可以补一下知识。Android 线程间通信(一) - Handler

回到正题,这次我们将一下上一篇文章留下问题中的几个,idleHandler & syncBarrier。

同步屏障

首先我们了解吗,我们平时通过Handler#sendMessage发送的属于什么消息?同步消息还是异步消息。既然这次讲到同步屏障,我们也可以大胆的猜想,我们平时发送的消息是同步消息,只有特殊情况下,插入这个同步屏障,才会走特殊的异步方式。
如果能够这样推导,说明在Handler这一块的了解已经比较深刻了。我们继续看一下源码。

	//MessageQueue.java
    @UnsupportedAppUsage
    Message next() {
        ...
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            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;
                //(1) msg不为空,且msg.target为空才会进入
                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());
                }
                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.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }
		...
    }

我们看代码中(1)注解的位置,msg不为空这个很好理解,消息需要有实体,但是target为空这个是为什么呢?上一片文章讲过,msg的target其实就是Handler。而下面官方的注解说明,被栏栅挡住了,在队列中查找下一个异步消息。然后开始从当前这个msg的下一个msg开始查找,知道下一个消息属于异步消息或者查找直到为空位置,跳出循环。

可见挡住同步消息的,正是这个msg.target=null的消息,那么它是怎么插入进来的呢?我们接着看。

	//MessageQueue.java
	
    @UnsupportedAppUsage
    @TestApi
    public int postSyncBarrier() {
        return postSyncBarrier(SystemClock.uptimeMillis());
    }

    private int postSyncBarrier(long when) {
        // Enqueue a new sync barrier token.
        // We don't need to wake the queue because the purpose of a barrier is to stall it.
        synchronized (this) {
            final int token = mNextBarrierToken++;
            final Message msg = Message.obtain();
            msg.markInUse();
            msg.when = when;
            msg.arg1 = token;

            Message prev = null;
            Message p = mMessages;
            if (when != 0) {
                while (p != null && p.when <= when) {
                    prev = p;
                    p = p.next;
                }
            }
            if (prev != null) { // invariant: p == prev.next
                msg.next = p;
                prev.next = msg;
            } else {
                msg.next = p;
                mMessages = msg;
            }
            return token;
        }
    }

MessageQueue中有一个方法叫做postSyncBarrier,可以理解为插入同步屏障。首先通过Message#obtain获取消息,这里是对消息对象的一个复用,在消息处理完之后,会把Message的target清除。然后标记为使用,接着就是消息的一些属性赋值,但唯独没有重新确定msg的target对象。可见如果messageQueue需要插入同步屏障,就是通过这个方法把屏障消息插入进去的。

插入同步屏障消息是直接通过MessageQueue进行插入

既然有插入同步屏障,那么也有移除屏障,让同步消息可以继续执行。

	//MessageQueue.java
    @UnsupportedAppUsage
    @TestApi
    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;
            while (p != null && (p.target != null || p.arg1 != token)) {
                prev = p;
                p = p.next;
            }
            if (p == null) {
                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) {
                prev.next = p.next;
                needWake = false;
            } else {
                mMessages = p.next;
                needWake = mMessages == null || mMessages.target != null;
            }
            p.recycleUnchecked();

            // If the loop is quitting then it is already awake.
            // We can assume mPtr != 0 when mQuitting is false.
            if (needWake && !mQuitting) {
                nativeWake(mPtr);
            }
        }
    }

还有个问题,就是同步屏障是挡住同步消息,那么怎么发送异步消息呢?我们看看Message的源码。

	// Message.java
    public void setAsynchronous(boolean async) {
        if (async) {
            flags |= FLAG_ASYNCHRONOUS;
        } else {
            flags &= ~FLAG_ASYNCHRONOUS;
        }
    }

有一个设置异步的方法,根据传入的bool值,flags和标志位做位运算。我们只要了解,当async为true,flags = 1,async为false,flags = 0。之前跳出next中的do…while循环中,判断异步消息的方式,就是msg#isAsynchironous。也就是说,setAsynchronous当传入true的时候,判断消息的条件就为真,此时发送的消息就是异步消息了。

	//Message.java
    public boolean isAsynchronous() {
        return (flags & FLAG_ASYNCHRONOUS) != 0;
    }

默认情况下flags = 0,为同步消息

这样一来,同步屏障的作用和使用方式,我们就彻底搞懂了。

IdleHandler

空闲Handler,和字面意思一样。Handler是消息处理的句柄,那么IdleHandler,就是处理空闲消息的句柄。在MessageQueue#next源码中,我们继续看一下。

	//MessageQueue#next
                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }

                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // Run the idle handlers.
            // We only ever reach this code block during the first iteration.
            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);
                    }
                }
            }

            // Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0;

            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.
            nextPollTimeoutMillis = 0;
        }

next方法下面有这样一段代码,什么时候会执行下去呢?就是当msg=null的时候,因为当msg不为空,说明取出了msg,并返回给Looper进行后续分发了。msg为null,说明此时消息队列中取不出消息了,就会从mIdlehandlers这个集合中,获取集合大小,并且把集合转换成数组mPendingIdleHandlers。

接着通过一个for循环,取出每一个idler并执行它的queueIdle,这个queueIdle是一个接口,可见是一个接口回调的操作,回调给用户去操作了。

	//MessageQueue.java
    public void addIdleHandler(@NonNull IdleHandler handler) {
        if (handler == null) {
            throw new NullPointerException("Can't add a null IdleHandler");
        }
        synchronized (this) {
            mIdleHandlers.add(handler);
        }
    }

通过MessageQueue的addIdleHandler接口,我们可以向MessageQueue维护的集合中添加这种空闲Handler,当消息队列MessageQueue的消息队列中没有任务可以执行之后,就会执行这些空闲的消息。

可以发现,IdleHandler适合处理那些优先级比较低的事情,而不会影响到用户界面的响应性。避免在主线程繁忙的时候执行耗时任务,从而保持应用的流畅性。

总结

1、MessageQueue可以发送同步屏障消息
2、Message可以通过设置确认发送的消息为同步还是异步
3、同步屏障消息的Handler为空
4、MessageQueue可以添加IdleHandler来处理优先级较低的任务

这下应用层Handler的东西我们都了解了。下一篇文章接下来将通过native层来分析一下Handler这个线程间通信的方式,底层到底还做了什么。

相关推荐

  1. 17Android 线通信() - Handler

    2024-07-18 08:14:02       19 阅读
  2. 18Android 线通信(三) - Handler

    2024-07-18 08:14:02       18 阅读
  3. Android中线通信-Handler

    2024-07-18 08:14:02       49 阅读
  4. C# BlockingCollection实现线通信

    2024-07-18 08:14:02       53 阅读
  5. C# Channel实现线通信

    2024-07-18 08:14:02       47 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-18 08:14:02       67 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-18 08:14:02       71 阅读
  3. 在Django里面运行非项目文件

    2024-07-18 08:14:02       58 阅读
  4. Python语言-面向对象

    2024-07-18 08:14:02       69 阅读

热门阅读

  1. k8s学习——创建测试镜像

    2024-07-18 08:14:02       21 阅读
  2. 查询Mysql数据库所有数据库所占磁盘空间大小

    2024-07-18 08:14:02       20 阅读
  3. 大语言模型系列-Transformer

    2024-07-18 08:14:02       18 阅读
  4. 获取客户端(前端)IP地址

    2024-07-18 08:14:02       20 阅读
  5. Excel表格导出

    2024-07-18 08:14:02       19 阅读
  6. 将一个tensor可视化

    2024-07-18 08:14:02       22 阅读
  7. Tomcat长连接源码解析

    2024-07-18 08:14:02       20 阅读
  8. 华为欧拉openEuler24.03 rpm安装 MySQL8.4

    2024-07-18 08:14:02       24 阅读
  9. 深入解析Apache Hive架构

    2024-07-18 08:14:02       23 阅读
  10. strncpy 和 snprintf 的区别

    2024-07-18 08:14:02       22 阅读
  11. Kafka系列之:Kafka存储数据相关重要参数理解

    2024-07-18 08:14:02       18 阅读
  12. Oracle(8)什么是Oracle实例(Instance)?

    2024-07-18 08:14:02       22 阅读
  13. python 迭代器介绍 map() 函数

    2024-07-18 08:14:02       19 阅读
  14. Linux chmod 命令简介

    2024-07-18 08:14:02       24 阅读