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

相关文章

线上Java OOM问题定位与解决方案超详细解析

《线上JavaOOM问题定位与解决方案超详细解析》OOM是JVM抛出的错误,表示内存分配失败,:本文主要介绍线上JavaOOM问题定位与解决方案的相关资料,文中通过代码介绍的非常详细,需要的朋... 目录一、OOM问题核心认知1.1 OOM定义与技术定位1.2 OOM常见类型及技术特征二、OOM问题定位工具

Vue3绑定props默认值问题

《Vue3绑定props默认值问题》使用Vue3的defineProps配合TypeScript的interface定义props类型,并通过withDefaults设置默认值,使组件能安全访问传入的... 目录前言步骤步骤1:使用 defineProps 定义 Props步骤2:设置默认值总结前言使用T

Web服务器-Nginx-高并发问题

《Web服务器-Nginx-高并发问题》Nginx通过事件驱动、I/O多路复用和异步非阻塞技术高效处理高并发,结合动静分离和限流策略,提升性能与稳定性... 目录前言一、架构1. 原生多进程架构2. 事件驱动模型3. IO多路复用4. 异步非阻塞 I/O5. Nginx高并发配置实战二、动静分离1. 职责2

解决升级JDK报错:module java.base does not“opens java.lang.reflect“to unnamed module问题

《解决升级JDK报错:modulejava.basedoesnot“opensjava.lang.reflect“tounnamedmodule问题》SpringBoot启动错误源于Jav... 目录问题描述原因分析解决方案总结问题描述启动sprintboot时报以下错误原因分析编程异js常是由Ja

MySQL 表空却 ibd 文件过大的问题及解决方法

《MySQL表空却ibd文件过大的问题及解决方法》本文给大家介绍MySQL表空却ibd文件过大的问题及解决方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考... 目录一、问题背景:表空却 “吃满” 磁盘的怪事二、问题复现:一步步编程还原异常场景1. 准备测试源表与数据

解决Nginx启动报错Job for nginx.service failed because the control process exited with error code问题

《解决Nginx启动报错Jobfornginx.servicefailedbecausethecontrolprocessexitedwitherrorcode问题》Nginx启... 目录一、报错如下二、解决原因三、解决方式总结一、报错如下Job for nginx.service failed bec

SysMain服务可以关吗? 解决SysMain服务导致的高CPU使用率问题

《SysMain服务可以关吗?解决SysMain服务导致的高CPU使用率问题》SysMain服务是超级预读取,该服务会记录您打开应用程序的模式,并预先将它们加载到内存中以节省时间,但它可能占用大量... 在使用电脑的过程中,CPU使用率居高不下是许多用户都遇到过的问题,其中名为SysMain的服务往往是罪魁

MySQ中出现幻读问题的解决过程

《MySQ中出现幻读问题的解决过程》文章解析MySQLInnoDB通过MVCC与间隙锁机制在可重复读隔离级别下解决幻读,确保事务一致性,同时指出性能影响及乐观锁等替代方案,帮助开发者优化数据库应用... 目录一、幻读的准确定义与核心特征幻读 vs 不可重复读二、mysql隔离级别深度解析各隔离级别的实现差异

C++ vector越界问题的完整解决方案

《C++vector越界问题的完整解决方案》在C++开发中,std::vector作为最常用的动态数组容器,其便捷性与性能优势使其成为处理可变长度数据的首选,然而,数组越界访问始终是威胁程序稳定性的... 目录引言一、vector越界的底层原理与危害1.1 越界访问的本质原因1.2 越界访问的实际危害二、基

Python多线程应用中的卡死问题优化方案指南

《Python多线程应用中的卡死问题优化方案指南》在利用Python语言开发某查询软件时,遇到了点击搜索按钮后软件卡死的问题,本文将简单分析一下出现的原因以及对应的优化方案,希望对大家有所帮助... 目录问题描述优化方案1. 网络请求优化2. 多线程架构优化3. 全局异常处理4. 配置管理优化优化效果1.