面试常问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

相关文章

解决IDEA报错:编码GBK的不可映射字符问题

《解决IDEA报错:编码GBK的不可映射字符问题》:本文主要介绍解决IDEA报错:编码GBK的不可映射字符问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录IDEA报错:编码GBK的不可映射字符终端软件问题描述原因分析解决方案方法1:将命令改为方法2:右下jav

MyBatis模糊查询报错:ParserException: not supported.pos 问题解决

《MyBatis模糊查询报错:ParserException:notsupported.pos问题解决》本文主要介绍了MyBatis模糊查询报错:ParserException:notsuppo... 目录问题描述问题根源错误SQL解析逻辑深层原因分析三种解决方案方案一:使用CONCAT函数(推荐)方案二:

Redis 热 key 和大 key 问题小结

《Redis热key和大key问题小结》:本文主要介绍Redis热key和大key问题小结,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录一、什么是 Redis 热 key?热 key(Hot Key)定义: 热 key 常见表现:热 key 的风险:二、

IntelliJ IDEA 中配置 Spring MVC 环境的详细步骤及问题解决

《IntelliJIDEA中配置SpringMVC环境的详细步骤及问题解决》:本文主要介绍IntelliJIDEA中配置SpringMVC环境的详细步骤及问题解决,本文分步骤结合实例给大... 目录步骤 1:创建 Maven Web 项目步骤 2:添加 Spring MVC 依赖1、保存后执行2、将新的依赖

Spring 中的循环引用问题解决方法

《Spring中的循环引用问题解决方法》:本文主要介绍Spring中的循环引用问题解决方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录什么是循环引用?循环依赖三级缓存解决循环依赖二级缓存三级缓存本章来聊聊Spring 中的循环引用问题该如何解决。这里聊

Spring Boot中JSON数值溢出问题从报错到优雅解决办法

《SpringBoot中JSON数值溢出问题从报错到优雅解决办法》:本文主要介绍SpringBoot中JSON数值溢出问题从报错到优雅的解决办法,通过修改字段类型为Long、添加全局异常处理和... 目录一、问题背景:为什么我的接口突然报错了?二、为什么会发生这个错误?1. Java 数据类型的“容量”限制

关于MongoDB图片URL存储异常问题以及解决

《关于MongoDB图片URL存储异常问题以及解决》:本文主要介绍关于MongoDB图片URL存储异常问题以及解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐... 目录MongoDB图片URL存储异常问题项目场景问题描述原因分析解决方案预防措施js总结MongoDB图

SpringBoot项目中报错The field screenShot exceeds its maximum permitted size of 1048576 bytes.的问题及解决

《SpringBoot项目中报错ThefieldscreenShotexceedsitsmaximumpermittedsizeof1048576bytes.的问题及解决》这篇文章... 目录项目场景问题描述原因分析解决方案总结项目场景javascript提示:项目相关背景:项目场景:基于Spring

解决Maven项目idea找不到本地仓库jar包问题以及使用mvn install:install-file

《解决Maven项目idea找不到本地仓库jar包问题以及使用mvninstall:install-file》:本文主要介绍解决Maven项目idea找不到本地仓库jar包问题以及使用mvnin... 目录Maven项目idea找不到本地仓库jar包以及使用mvn install:install-file基

usb接口驱动异常问题常用解决方案

《usb接口驱动异常问题常用解决方案》当遇到USB接口驱动异常时,可以通过多种方法来解决,其中主要就包括重装USB控制器、禁用USB选择性暂停设置、更新或安装新的主板驱动等... usb接口驱动异常怎么办,USB接口驱动异常是常见问题,通常由驱动损坏、系统更新冲突、硬件故障或电源管理设置导致。以下是常用解决