面试常问Handler 的问题合集

2024-09-02 20:48

本文主要是介绍面试常问Handler 的问题合集,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

问:Looper.prepare做了什么操作?

检查了当前线程是否已经有了Looper对象,如果已经有了Looper对象,那么就直接报错。保证每个线程只有一个Looper.

    private static void prepare(boolean quitAllowed) {if (sThreadLocal.get() != null) {throw new RuntimeException("Only one Looper may be created per thread");}sThreadLocal.set(new Looper(quitAllowed));}

他是利用ThreadLocal 实现的,保证每个线程只有一个Looper.

问:Looper、Handler、 MessageQeue 怎么关联起来的?

当调用了Looper.prepare 的时候,会创建一个Looper.在Looper 的构造方法里面,会创建一个MessageQeue. Looper 和 MessageQeue 就关联起来了。
当我们调用Looper.Loop 的时候,looper 就会不停的从MessageQeue 里面拿消息。

我们发送消息的时候,需要创建一个Handler, 创建Handler的时候,会获取当前线程的Looper,同时拿到MessageQeue,我们就可以使用当我们发送一个消息的时候,Message 里有一个成员变量target,会拿着当前Handler的引用。这样当Looper 获取到Message 的时候,就会调用target(Handler) 的 dispatchMessage 方法。

这样Looper Handler MessageQeue 就关联起来了。

问:怎么保证一个线程只有一个handler?

呵呵呵,给的问题都是错的。一个线程里面可以有多个handler,但是只有一个looper 和一个MessageQeue.
因为Looper 的构造方法是私有的,所以只有 prepare 方法才可以new Looper,但是这个方法只能调用一次,因为第二次调用会抛异常。原因是Looper 这个类里面有个static final 的变量,叫做sthreadLocal,调用prepare 的时候,会调用threadLocal.get(),如果返回值不为空,就会抛异常。
多个handler 无所谓啊,因为用handler 发送消息,msg 里面有一个target 指向发消息的handler,处理消息的时候,会调用msg.target disapatch msg.

问:MessageQeue内部怎么实现的消息队列?先有一个延迟消息,后来有个直接发的消息,为什么后来直接发的消息先执行?

想想也知道,肯定要排序啊。我的天。感觉handler 还有很多细节,没有看。native 的也没有看。MessageQeue 是链表实现的, 因为发送的消息可能被插入到任何位置,应对随机插入数据,最好的数据结构就是链表。消息插入的时候,会根据deley time 进行排序插入,所以,就算是先发了一个延迟消息,又发送了立刻执行的消息,先执行的是后面立即执行的消息。

问:MessageQeue 的数据结构是什么?

是链表,不是队列。

问:如果我修改系统时间,会影响sendEmptyMessageDelayed 吗?

不会,因为延迟是基于开机时间,而不是时钟。

    public final boolean sendMessageDelayed(Message msg, long delayMillis){if (delayMillis < 0) {delayMillis = 0;}return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);}

问:Handler sendMessage 和 postRunnable 有什么区别?

    public final boolean post(Runnable r){return  sendMessageDelayed(getPostMessage(r), 0);}private static Message getPostMessage(Runnable r) {Message m = Message.obtain();m.callback = r;return m;}

发送的时候,没有什么区别。只不过postRunnable 是会自动创建一个Message,而sendMessage 需要你去创建。

    /*** Handle system messages here.*/public void dispatchMessage(Message msg) {if (msg.callback != null) {handleCallback(msg);} else {if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}}handleMessage(msg);}}

Looper 取到消息后,会调用Handler 的dispatchMessage 处理消息,处理消息的时候,会判断callback是否是空,不是空直接执行callback.否则调用handleMessage方法处理消息。

问:为什么不同的线程,调用Loop.myLooper()就可以得到当前自己线程的Looper 对象? 而不是所有线程获取的是同一个Looper?

因为Looper.myLooper 返回值是从ThreadLocal 里面获取的,不同的线程有不同的值。

问:为什么到了时间就处理消息?是有定时吗?

问:为什么message.obtain 可以获取message对象? 可以用到之前回收的message对象?

因为message 里面里有私有的静态变量,Message,保存了当前的回收了的message的头结点。因为是静态的,所以所有的message 公用一个message 静态变量。

问:Nativepollonce 为什么要使用c层去写?java 层不能完全实现吗?

因为java 层不好控制休眠。因为looper 是个死循环,当消息队列没有消息的时候,就会阻塞,释放cpu。(sleep 也可以啊。)当有消息的时候,会唤醒程序运行。

问:android looper 为什么不会阻塞主线程?

android 程序的主入口在activityThread 类的main 方法。里面做了几个简单的事情,随后启动了looper. 如果不启动looper 那么程序就会直接退出了。所以looper 不但不会导致阻塞,反而没有looper机制会导致程序结束。所有的其他的操作,比如16ms 刷新一帧,都是通过hanlder 的handlermessage 去完成的。所以整个android 就是在handler 里面完成所有的onResume,onPause,而程序不会退出。

主线程的死循环一直运行是不是特别消耗CPU资源呢?

其实不然,这里就涉及到Linux pipe/epoll机制,简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。 Gityuan–Handler(Native层)

当休眠的时候,send 进来的message 的延迟如果是0,那么都会唤醒这个线程。

    boolean enqueueMessage(Message msg, long when) {synchronized (this) {msg.markInUse();msg.when = when;Message p = mMessages;boolean needWake;//如果p = null 说明是新的一个消息//或者是一个立马执行的消息//或者发送的消息 小于当前头消息的时间if (p == null || when == 0 || when < p.when) {// New head, wake up the event queue if blocked.msg.next = p;mMessages = msg;needWake = mBlocked;} else {//非实时的//如果消息是异步的  那么可以去唤醒// 为什么p.target 要是空的呢?needWake = mBlocked && p.target == null && msg.isAsynchronous();Message prev;for (;;) {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);}}return true;}

handler 怎么实现的延迟多少秒去执行消息?

有一个队列,当发现下一个消息是延迟消息,那么调用nativePullOnce 的时候,传的第二个参数,就是下次Poll的时间,就是你的延迟时间。nativePollOnce(ptr, nextPollTimeoutMillis); 如果是-1表示一直阻塞下去等待消息。0表示完全不阻塞,就算没有线程去写入信息到文件操作符,也不会阻塞。只有当nextPollTimeoutMillis 为一个大于0 的数,他才会去阻塞,等待有线程写入消息。

如果MessageQueue 里面没有消息的话, 那么nativePollOnce 的第二个参数就是-1,表示一直休眠下去,等待下一个消息发进来。

如果在休眠期间,发送了一个消息就会里面调用nativeWake 唤醒线程。

如果我发送了一个延迟5s 的消息,那么nativePollOnce 会等待5s,这时候有一个线程发送了延迟3s 的消息,那么就会立马唤醒线程去读取消息。如果发现是延迟3s,就会继续调用nativepollOnce timeOut 参数设置为3s.

Message obtain () 怎么回收,怎么重复使用?

public static Message obtain() {synchronized (sPoolSync) {if (sPool != null) {Message m = sPool;sPool = m.next;m.next = null;m.flags = 0; // clear in-use flagsPoolSize--;return m;}}return new Message();
}

什么时候回收?

            msg.recycleUnchecked();

Looper.loop 方法里面的,当一条消息处理完之后,就会回收。

用静态的一个链表保存消息,处理完一个消息,就会回收,
如果你没有源码,可能看不到引用recycleUnchecked的地方。其实在Looper.loop 里面调用了。

setAsynchronous 的作用

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

Asynchronous的消息是必须和屏障一块使用的,当有屏障的时候,屏障后面的普通消息不会执行,只会执行异步消息。可以参考这篇文章:

https://www.cnblogs.com/wytiger/p/13031145.html

这篇关于面试常问Handler 的问题合集的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1131035

相关文章

MybatisGenerator文件生成不出对应文件的问题

《MybatisGenerator文件生成不出对应文件的问题》本文介绍了使用MybatisGenerator生成文件时遇到的问题及解决方法,主要步骤包括检查目标表是否存在、是否能连接到数据库、配置生成... 目录MyBATisGenerator 文件生成不出对应文件先在项目结构里引入“targetProje

C#使用HttpClient进行Post请求出现超时问题的解决及优化

《C#使用HttpClient进行Post请求出现超时问题的解决及优化》最近我的控制台程序发现有时候总是出现请求超时等问题,通常好几分钟最多只有3-4个请求,在使用apipost发现并发10个5分钟也... 目录优化结论单例HttpClient连接池耗尽和并发并发异步最终优化后优化结论我直接上优化结论吧,

Java内存泄漏问题的排查、优化与最佳实践

《Java内存泄漏问题的排查、优化与最佳实践》在Java开发中,内存泄漏是一个常见且令人头疼的问题,内存泄漏指的是程序在运行过程中,已经不再使用的对象没有被及时释放,从而导致内存占用不断增加,最终... 目录引言1. 什么是内存泄漏?常见的内存泄漏情况2. 如何排查 Java 中的内存泄漏?2.1 使用 J

numpy求解线性代数相关问题

《numpy求解线性代数相关问题》本文主要介绍了numpy求解线性代数相关问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 在numpy中有numpy.array类型和numpy.mat类型,前者是数组类型,后者是矩阵类型。数组

解决systemctl reload nginx重启Nginx服务报错:Job for nginx.service invalid问题

《解决systemctlreloadnginx重启Nginx服务报错:Jobfornginx.serviceinvalid问题》文章描述了通过`systemctlstatusnginx.se... 目录systemctl reload nginx重启Nginx服务报错:Job for nginx.javas

Redis缓存问题与缓存更新机制详解

《Redis缓存问题与缓存更新机制详解》本文主要介绍了缓存问题及其解决方案,包括缓存穿透、缓存击穿、缓存雪崩等问题的成因以及相应的预防和解决方法,同时,还详细探讨了缓存更新机制,包括不同情况下的缓存更... 目录一、缓存问题1.1 缓存穿透1.1.1 问题来源1.1.2 解决方案1.2 缓存击穿1.2.1

vue解决子组件样式覆盖问题scoped deep

《vue解决子组件样式覆盖问题scopeddeep》文章主要介绍了在Vue项目中处理全局样式和局部样式的方法,包括使用scoped属性和深度选择器(/deep/)来覆盖子组件的样式,作者建议所有组件... 目录前言scoped分析deep分析使用总结所有组件必须加scoped父组件覆盖子组件使用deep前言

解决Cron定时任务中Pytest脚本无法发送邮件的问题

《解决Cron定时任务中Pytest脚本无法发送邮件的问题》文章探讨解决在Cron定时任务中运行Pytest脚本时邮件发送失败的问题,先优化环境变量,再检查Pytest邮件配置,接着配置文件确保SMT... 目录引言1. 环境变量优化:确保Cron任务可以正确执行解决方案:1.1. 创建一个脚本1.2. 修

Python 标准库time时间的访问和转换问题小结

《Python标准库time时间的访问和转换问题小结》time模块为Python提供了处理时间和日期的多种功能,适用于多种与时间相关的场景,包括获取当前时间、格式化时间、暂停程序执行、计算程序运行时... 目录模块介绍使用场景主要类主要函数 - time()- sleep()- localtime()- g

SpringBoot项目删除Bean或者不加载Bean的问题解决

《SpringBoot项目删除Bean或者不加载Bean的问题解决》文章介绍了在SpringBoot项目中如何使用@ComponentScan注解和自定义过滤器实现不加载某些Bean的方法,本文通过实... 使用@ComponentScan注解中的@ComponentScan.Filter标记不加载。@C