Android Handler消息机制完全解析-IdleHandler和epoll机制(四)

news/2024/10/29 5:07:25/

Android 消息机制Handler完全解析(一)

Android 消息机制Handler完全解析(二)

Android Handler消息机制-消息屏障(三)
经过前面的学习相信大家对Handler已经有了比较全面且深入的认识,我在写第一篇的时候就说过Handler相关的知识远比我们想象的要多,到这里大家是不是略有体会呢?今天和大家一起学习的是IdleHandler和epoll机制,这两个可能平时用到的不多,尤其是epoll,但是它的原理以及思想我们要清楚,有些面试可能会问到,有一句话叫"有剑不用和没有剑是两码事"。废话不多说我们开始学习今天的内容,通过本篇博客你将学习到以下内容

1.IdleHandler是什么

2.为什么要有IdleHandler即产生的背景或者有什么好处

3.IdleHandler的原理

4.IdleHandler如何使用

5.什么是阻塞和非阻塞轮询

6.epoll机制在handler消息机制中是怎么工作的

1.IdleHandler是什么,IdleHandler的作用

首先什么是IdleHandler,IdleHandler是MessageQueue中的一个接口

java">public final class MessageQueue {public static interface IdleHandler {boolean queueIdle();}
}

Idle有空闲的意思,IdleHandler在什么情况下会执行呢?当线程的MessageQueue空闲的时候(MessageQueue中没有Message或者下一次要执行的Message的时间还未到)就会去执行IdleHandler中指定的任务,它常用来做一些不是特别紧急的任务,比如资源的回收、延时加载等。

2.IdleHandler原理

IdleHandler平时项目中用到的不多,今天主要来看看它的原理,即它是怎么做到MessageQueue空闲的时候才执行我们指定的任务的。前几篇在Handler消息机制的时候我们知道针对MessageQueue中消息主要有两个操作分别是enqueueMessage(向MessageQueue插入消息)和next(从MessageQueue中取消息),因为它是当MessageQueue空闲的时候执行所以肯定是取消息的时候发现当前没有要执行的消息,而去执行IdleHandler中的任务,我们来看看MessageQueue#next方法

java">public final class MessageQueue {private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();private IdleHandler[] mPendingIdleHandlers;public void addIdleHandler(@NonNull IdleHandler handler) {if (handler == null) {throw new NullPointerException("Can't add a null IdleHandler");}synchronized (this) {mIdleHandlers.add(handler);}}Message next() {...int pendingIdleHandlerCount = -1; // -1 only during first iterationint nextPollTimeoutMillis = 0;for (;;) {synchronized (this) {...// 当mMessages为null或者有延时消息但是执行的时机还未到,则统计下当前IdleHandler的个数if (pendingIdleHandlerCount < 0&& (mMessages == null || now < mMessages.when)) {pendingIdleHandlerCount = mIdleHandlers.size();}// 如果没有IdleHandler则继续下一次轮询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)];}// 将mIdleHandlers列表转换为数组mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);}// 遍历执行IdleHandlerfor (int i = 0; i < pendingIdleHandlerCount; i++) {final IdleHandler idler = mPendingIdleHandlers[i];mPendingIdleHandlers[i] = null; // release the reference to the handlerboolean keep = false;try {keep = idler.queueIdle();} catch (Throwable t) {Log.wtf(TAG, "IdleHandler threw exception", t);}// 如果queueIdle方法返回false则执行之后将其从mIdleHanders中移除if (!keep) {synchronized (this) {mIdleHandlers.remove(idler);}}}// 将空闲处理程序计数重置为0,这样我们就不会再次运行它们pendingIdleHandlerCount = 0;}}
}

可以看到这里首先定义了一个存放IdleHandler的列表mIdleHandlers,然后定义了一个addIdleHandler方法,此方法就是供我们开发者调用的,调用之后会把当前传递过来的IdleHandler对象添加到mIdleHandlers,接下来看下IdleHandler在next方法中什么时机下执行,在第22行的if语句判断可以看到当mMessages为null或者有延时消息但是执行的时机还未到时会将mIdlerHandlers.size()即IdleHandler的个数赋值给pendingIdleHandlerCount,如果pendingIdleHandlerCount>0说明当前有需要执行的IdleHandler,然后将mIdleHandlers转换为数组,并对其进行遍历执行。

这里有一点需要注意第47行keep = idler.queueIdle();会将queueIdle()方法的返回值赋值给keep,然后在第52行的if语句如果queueIdle方法的返回值为false则执行之后会将此IdleHandler对象移除。如果返回true的话则不移除,下次调用next方法如果条件满足仍然会执行

3.IdleHandler使用

IdleHandler的原理其实比较简单,接下来看看它的使用,这里就举个最简单的例子,在Activity中的onCreate方法中添加如下代码,运行就会打印相应日志

java">Looper.getMainLooper().getQueue().addIdleHandler(new MessageQueue.IdleHandler() {@Overridepublic boolean queueIdle() {Log.d(TAG, "getMainLooper");return false;}
});

关于IdleHandler的使用场景遵循一个原则:在不影响其他任务,在消息队列空闲状态下执行

1.系统源码中 比如在ActivityThread中,就有一个名叫GcIdler的内部类,实现了IdleHandler接口。

它在queueIdle方法被回调时,会做强行GC的操作

2.一些第三方框架Glide 和 LeakCanary 等很多第三方框架也使用到 IdleHandler
如果大家想深入了解可以在自己查下资料,网上有很多资料。

4.常见面试

1.IdleHandler 主要是在 MessageQueue 出现空闲的时候被执行,那么何时出现空闲?

MessageQueue 是一个基于消息触发时间的列表,所以列表出现空闲存在两种场景。

(1)MessageQueue为空,没有消息;

(2)MessageQueue 中最近需要处理的消息,是一个延迟消息(when>currentTime),需要滞后执行

2.IdleHandler 有什么用?

IdleHandler是Handler提供的一种在消息队列空闲时,执行任务的时机,当 MessageQueue 当前没有立即需要处理的消息时,会执行 IdleHandler;它常用来做一些不是特别紧急的任务,比如资源的回收、延时加载等。

3.MessageQueue 提供了 add/remove IdleHandler 的方法,是否需要成对使用

不是必须;IdleHandler.queueIdle() 的返回值,可以移除加入 MessageQueue 的 IdleHandler;

4.当 mIdleHanders 一直不为空时,为什么不会进入死循环?

只有在pendingIdleHandlerCount 为 -1 时,才会尝试执行 mIdleHander,pendingIdlehanderCount 在 next() 中初始时为 -1,执行一遍后被置为 0,所以不会重复执行

5.是否可以将一些不重要的启动服务,搬移到 IdleHandler 中去处理?

不建议;IdleHandler 的处理时机不可控,如果 MessageQueue 一直有待处理的消息,那么 IdleHander 的执行时机会很靠后

6.IdleHandler 的 queueIdle() 运行在那个线程?

queueIdle() 运行的线程,只和当前MessageQueue的Looper所在的线程有关;子线程一样可以构造Looper, 并添加IdleHandler

5.epoll机制

epoll机制涉及到linux底层的一些设计,因此大家不要深入了解,了解其原理即可。首先来看下百度百科的定义
epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。什么鬼,感觉每个字都认识怎么连到一起就不知道啥意思了呢?想了解epoll机制首先要搞清楚两个概念:“阻塞"和"非阻塞轮询”。我举一个现实生活中的例子,通过这个例子你可能很容易理解"阻塞"和"非阻塞轮询"这两个概念。

假如你女朋友要来你家玩,由于之前没有来过你家,你不知道她什么时候到小区,所以你需要每隔一分钟给她打个电话问她到哪里了,这种方式就是非阻塞轮询。还有一种方式就是你女朋友告诉你到了小区之后给你打电话,因此你可以去睡觉了,因为你女朋友到了小区后会主动通知你。

非阻塞轮询:你需要每个一分钟给她打个电话问她到哪里了

阻塞:你女朋友告诉你到了小区之后给你打电话
到这里大家是不是对非阻塞轮询和阻塞有了进一步的认识?非阻塞轮询中的轮询就是每隔一定的时间间隔去执行相应的操作即使是无用功也要执行,就好比你女朋友到你家有2个小时的路程,你从她一出门就每隔一分钟打一个电话问她到了没有。而阻塞不一样,阻塞就是你知道你女朋友要来你家,来到你家小区之后你要做的动作是去小区门口接她,因为你女朋友到了小区之后会给你打电话所以你不用担心,你可以放心的去睡觉等待这个事件的到来。

再来看两段伪代码,进一步理解这两个概念,假如现在有n个I/O事件,我们怎么同时处理多个流呢?

非阻塞轮询

java">while(true) {for (i in stream[]) {read stream data}
}

通过以上这种方式如果所有的流中都没有数据,那么只会造成CPU的空转,只会白白浪费CPU

java">while(true) {active_stream[] = epoll_wait()for(i in active_stream[]) {read stream data}
}

这个epoll_wait是干嘛的呢?就是说如果当前没有I/O流需要处理它会进入阻塞状态,等待事件的到来,后面的for循环不会执行,当有I/O流需要执行时它会把哪个流发生了怎样的I/O事件通知给active_stream,此时我们对这些流的处理都是有意义的,这样是不是大大提升了效率。说了这么多,epoll机制在Handler消息机制中是怎么体现的呢?我们来看下MessageQueue的构造方法、next方法、enqueueMessage方法的源码

java">public final class MessageQueue {private long mPtr; // used by native codeprivate native static long nativeInit();@UnsupportedAppUsageprivate native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/private native static void nativeWake(long ptr);MessageQueue(boolean quitAllowed) {mQuitAllowed = quitAllowed;mPtr = nativeInit();}Message next() {final long ptr = mPtr;...int nextPollTimeoutMillis = 0;for (;;) {...//阻塞操作,当等待nextPollTimeoutMillis时长,或者消息队列被唤醒nativePollOnce(ptr, nextPollTimeoutMillis);synchronized (this) {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);} } // 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;}}boolean enqueueMessage(Message msg, long when) {...// We can assume mPtr != 0 because mQuitting is false.if (needWake) {// 唤醒nativeWake(mPtr);}}return true;}
}

上面对next方法的源码做了简化,可以看到在MessageQueue定义了一些native相关的方法,并且在MessageQueue中的构造方法中调用了nativeInit方法,并将返回值赋值给mPtr这个值可以理解为epoll的一个句柄或者标签。然后第18行可以看到调用了nativePollOnce传递了两个参数一个是ptr,一个是nextPollTimeoutMillis即阻塞的时间,进入阻塞之后它下面的流程就不会执行从而不会造成CPU的浪费(此时没有可执行的消息,下面的代码流程没有意义,对比下上面的现实生活中的例子)。除了等待nextPollTimeoutMillis时长之外,在enqueueMessage中如果需要唤醒会执行nativeWake方法进行唤醒停止阻塞。
好了,今天关于IdleHandler以及epoll机制的讲解就到这里了,这也是关于Handler的第四篇文章了,大家觉得有帮助的话帮忙点点赞。


http://www.ppmy.cn/news/1542778.html

相关文章

如何将 HashiCorp Vault 与 Node.js 集成:安全管理敏感数据

在处理密码、API 密钥或个人用户信息等敏感数据时,安全存储它们至关重要。在源代码中硬编码机密或将其保存在纯文本文件中是一种危险的方法。这就是 HashiCorp Vault 发挥作用的地方。 Vault 是一个用于管理机密(例如凭证、API 密钥和敏感配置)的开源工具。 在本教程中,我将…

iview TreeSelect 异步加载子节点 编辑时不能正常回显

问题描述 因为分类数据太多&#xff0c;首次加载时间太长&#xff0c;和业务沟通改成异步加载子节点&#xff0c;解决了加载缓慢的问题&#xff0c;但编辑的时候回显就不能显示了&#xff0c;因为赋值的时候&#xff0c;下拉框数据中还没有加载已选中的项。该怎么办呢… 解决…

深度学习(五):语音处理领域的创新引擎(5/10)

一、深度学习在语音处理中的崛起 在语音处理领域&#xff0c;传统方法如谱减法、维纳滤波等在处理复杂语音信号时存在诸多局限性。这些方法通常假设噪声是平稳的&#xff0c;但实际噪声往往是非平稳的&#xff0c;导致噪声估计不准确。同时&#xff0c;为了去除噪声&#xff0…

Cursor零基础小白教程系列 - 创建你的第一个Cursor 项目

最适合小白零基础的Cursor教程 网站lookai.top相同作者&#xff0c;最新文章会在网站更新&#xff0c;欢迎收藏书签 创建你的第一个Cursor 项目 实操视频 概述 开始使用Cursor进行编程的第一步是创建或导入一个项目。本指南将帮助您了解如何在Cursor中创建新项目、导入现有项…

vscode 安装教程

双击vscode 安装包 同意&#xff0c;下一步 可以使用默认安装路径&#xff0c;也可以优化为这个 全选 取消勾选&#xff0c;点完成 在桌面创建一个空文件夹&#xff0c;拖动到vscode图标上 点击这个图标创建文件&#xff0c;注意必须以.py 结尾&#xff01;&#xff0…

操作系统(6) (POSIX--Linux线程编程---c语言相关语法/pthread_t/create/join应用)

本文章基于操作系统(5) (POSIX--Linux线程编程pthread_t/create/join/exit)-CSDN博客​​​​​​ https://blog.csdn.net/2301_80176774/article/details/143078984创作,主要讲解c语言相关语法/pthread_t/create/join应用. 目录 1. 线程与pthread库 2. 代码结构与解释 3…

【SQL】SQL函数

&#x1f4e2; 前言 函数 是指一段可以直接被另一段程序调用的程序或代码。主要包括了以下4中类型的函数。 字符串函数数值函数日期函数流程函数 &#x1f384; 字符串函数 ⭐ 常用函数 函数 功能 CONCAT(S1,S2,...Sn) 字符串拼接&#xff0c;将S1&#xff0c;S2&#xff0…

win10怎么卸载软件干净?电脑彻底删除软件的方法介绍,一键清理卸载残留!

电脑上经常会下载各种各样的软件来协助我们办公&#xff0c;不同的软件能够满足不同的需求。 但是不少软件可能使用频率没有那么高&#xff0c;甚至完全不使用。这个时候就需要将这些不常用的电脑软件卸载掉了&#xff0c;卸载软件能够释放一定的存储空间&#xff0c;提高电脑…