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

相关文章

解决pandas无法读取csv文件数据的问题

《解决pandas无法读取csv文件数据的问题》本文讲述作者用Pandas读取CSV文件时因参数设置不当导致数据错位,通过调整delimiter和on_bad_lines参数最终解决问题,并强调正确参... 目录一、前言二、问题复现1. 问题2. 通过 on_bad_lines=‘warn’ 跳过异常数据3

解决RocketMQ的幂等性问题

《解决RocketMQ的幂等性问题》重复消费因调用链路长、消息发送超时或消费者故障导致,通过生产者消息查询、Redis缓存及消费者唯一主键可以确保幂等性,避免重复处理,本文主要介绍了解决RocketM... 目录造成重复消费的原因解决方法生产者端消费者端代码实现造成重复消费的原因当系统的调用链路比较长的时

深度解析Nginx日志分析与499状态码问题解决

《深度解析Nginx日志分析与499状态码问题解决》在Web服务器运维和性能优化过程中,Nginx日志是排查问题的重要依据,本文将围绕Nginx日志分析、499状态码的成因、排查方法及解决方案展开讨论... 目录前言1. Nginx日志基础1.1 Nginx日志存放位置1.2 Nginx日志格式2. 499

kkFileView启动报错:报错2003端口占用的问题及解决

《kkFileView启动报错:报错2003端口占用的问题及解决》kkFileView启动报错因office组件2003端口未关闭,解决:查杀占用端口的进程,终止Java进程,使用shutdown.s... 目录原因解决总结kkFileViewjavascript启动报错启动office组件失败,请检查of

SpringBoot 异常处理/自定义格式校验的问题实例详解

《SpringBoot异常处理/自定义格式校验的问题实例详解》文章探讨SpringBoot中自定义注解校验问题,区分参数级与类级约束触发的异常类型,建议通过@RestControllerAdvice... 目录1. 问题简要描述2. 异常触发1) 参数级别约束2) 类级别约束3. 异常处理1) 字段级别约束

Python错误AttributeError: 'NoneType' object has no attribute问题的彻底解决方法

《Python错误AttributeError:NoneTypeobjecthasnoattribute问题的彻底解决方法》在Python项目开发和调试过程中,经常会碰到这样一个异常信息... 目录问题背景与概述错误解读:AttributeError: 'NoneType' object has no at

Spring的RedisTemplate的json反序列泛型丢失问题解决

《Spring的RedisTemplate的json反序列泛型丢失问题解决》本文主要介绍了SpringRedisTemplate中使用JSON序列化时泛型信息丢失的问题及其提出三种解决方案,可以根据性... 目录背景解决方案方案一方案二方案三总结背景在使用RedisTemplate操作redis时我们针对

Kotlin Map映射转换问题小结

《KotlinMap映射转换问题小结》文章介绍了Kotlin集合转换的多种方法,包括map(一对一转换)、mapIndexed(带索引)、mapNotNull(过滤null)、mapKeys/map... 目录Kotlin 集合转换:map、mapIndexed、mapNotNull、mapKeys、map

nginx中端口无权限的问题解决

《nginx中端口无权限的问题解决》当Nginx日志报错bind()to80failed(13:Permissiondenied)时,这通常是由于权限不足导致Nginx无法绑定到80端口,下面就来... 目录一、问题原因分析二、解决方案1. 以 root 权限运行 Nginx(不推荐)2. 为 Nginx

解决1093 - You can‘t specify target table报错问题及原因分析

《解决1093-Youcan‘tspecifytargettable报错问题及原因分析》MySQL1093错误因UPDATE/DELETE语句的FROM子句直接引用目标表或嵌套子查询导致,... 目录报js错原因分析具体原因解决办法方法一:使用临时表方法二:使用JOIN方法三:使用EXISTS示例总结报错原