ToB企服应用市场:ToB评测及商务社交产业平台
标题:
源码解析 Handler 面试宝典
[打印本页]
作者:
杀鸡焉用牛刀
时间:
2022-6-23 09:45
标题:
源码解析 Handler 面试宝典
Handler 面试源码解析面试宝典
前言
1、一个线程有几个Handler
考点
答案
2、一个线程有几个Looper?如何保证
考点
答案
3、Handler 内存泄漏原因?为什么其他的内部类没有说过这个问题
考点
答案
4、为何主线程可以new Handler?如果想要在子线程中new Handler 要做些什么?
考点
答案
5、子线程中维护的Looper,消息队列无消息的时候的处理方法是什么?有什么用?
考点
答案
6、既然可以存在多个Handler 往MessageQueue 中添加数据(发消息时各个Handler 可能处于不同线程),那它内部是如何确保线程安全的?
考点
答案
7、我们使用Message 时应该如何创建它?
考点
答案
8、Looper 死循环为什么不会导致应用卡死
考点
答案
总结
前言
在Android 的中,高级面试中,我们经常会被问到Handler 相关的知识点,而且占重比例还比较大,这是什么呢?下面一起来看一张图:
由上图我们可以看出,整个APP 的启动流程: Launcher(APP): zygote -> jvm -> ActivityThread.main()
ActivityThread.main()
就是我们APP 独有的main 启动方法,如图所示,绿色的部分,就是Handler 为我们开辟的独有空间,启动主线程独有的Looper,将当前App 独立出来。
由此我们可以得出一个结论:Handler 并不是只属于进程通讯,进程通讯只是Handler 的附属功能,而Handler 的真正功能是 所有的代码,都是在Handler 中运行的
1、一个线程有几个Handler
考点
这里面试官其实想了解的是,你对Handler的认知,如果这个问题打不出来,那么面试官也不会再去问了。
答案
在说答案之前,先看一直 Handler 的执行流程图:
由上图,我们可以看出:
一个线程里面,可以创建N个的Handler,如hander.sentXXX、 handler.postXX 都是在创建一个Handler。 每一个message,就是我们插入到消息队列 中的消息节点。
2、一个线程有几个Looper?如何保证
考点
这里面试官其实想了解的是,你对Handler 流程及源码的理解。
答案
在回答这个问题之前,我们再看一下Handler 的执行流程图:
handler -> sendMessage -> messageQueue.enqueueMessage -> looper.loop() -> messasgeQueue.next() -> handler.dispatchMessage() -> handler.handerMessage() ,handler 发送message,进入messageQueue.enqueueMessage 队列,进入looper中经过loop 死循环的不断遍历,驱动队列一直前进,经过handler.dispatchMessage() 分发给handler.handerMessage,这样我们就走完了整个的Handler 流程,也可以直接看错,
一个线程中,只有一个Looper
。
如何保证的呢?
ThreadLocal
多线程,线程上下文的存储变量,其实ThreadLocal 并不能存储任何的东西,但是在ThreadLocal 中,有一个ThreadLocalMap 集合,里面存储的 ,this 就是上下文,唯一的ThreadLocal key,key 唯一了,那么value 也就是唯一的,
ThreadLocal在创建的时候,会有一个判断,如果已经创建了,会报异常,所以一个线程有唯一的ThreadLocal 就有唯一的looper
。如下图:
ThreadLocal 线程隔离工具类
ThreadLocal 创建源码
3、Handler 内存泄漏原因?为什么其他的内部类没有说过这个问题
考点
应该是考官想知道,你对于GC回收 JVM 相关的东西吧。
答案
在这里,我我们先看一段代码:
Handler handler = new Handler(){
@SuppressLint("HandlerLeak")
@Override
public void handleMessage(@NonNull Message msg) {
Log.d("tiger", "handleMessage: ");
View view = null;
click(view);
MainActivity2.this.click(view);
}
};
public void click(View view) {
}
复制代码
上面代码片段中的handler 代码,会标黄,并给予一个警告:
这个处理程序类应该是静态的,否则可能发生内存泄漏。
那么为什么其他类不会有呢?
生命周期的问题
。流程 sendMessage -> sendMessageAtTime -> enqueueMessage 在 enqueueMessage 中,有段代码 msg.target = this;,意思就是Message 会持有当前的handler,handler 已经成为了massage的一部分,假如我设置一个消息需要等待20分钟后执行,那么就意味,我的message会一直等待20分钟之后才会执行,message 持有 handler,handler 持有 (this)activity,这样就导致GC无法回收,JVM 通过可达性算法,告诉我们,没法到达,就无法回收。内部类的生命周期,一旦在外部类生命周期中被别的生命周期持有了,那么外部类也不能被释放。
4、为何主线程可以new Handler?如果想要在子线程中new Handler 要做些什么?
考点
好烦啊,我也不知道啊,为什么还要要求这个?
答案
在APP 启动的时候,就在ActivityThread.main() 方法中,就创建了looper.loop() ,源码如下:
那么如何在子线程中new Handler呢?只需要在子线程中,手动添加 Looper.prepare(); 和 Looper.loop(); 就可以了。请看下面代码
public void click (View view){
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
// do something()
}
};
Looper.loop();
}
}).start();
}
复制代码
5、子线程中维护的Looper,消息队列无消息的时候的处理方法是什么?有什么用?
考点
对于源码的掌握程度
答案
子线程中维护的Looper 在无消息的时候调用quit,可以结束循环。loop() 是一个死循环,想要退出,必须msg == null。请看下面源码:
public static void loop() {
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
msg.recycleUnchecked();
}
}
复制代码
只有在调用quit 的时候,才会返回null。
Message next() {
for (;;) {
synchronized (this) {
if (mQuitting) {
dispose();
return null;
}
}
}
}
void quit(boolean safe) {
synchronized (this) {
if (mQuitting) {
return;
}
mQuitting = true;
}
}
复制代码
所以使用quit() 唤醒队列,执行loop() 退出循环,子线程looper 不在执行了。
消息入队
:根据时间排序,
当队列满的时候
,阻塞,直到用户通过next() 取出消息。当next 方法被调用的时候,通知MassageQueue 可以消息入队。
消息出队
:由Looper.loop()进行循环, 对queue 进行轮询操作,当消息达到执行时间就取出来,当MessageQueue 为空的时候,队列阻塞,等消息调用queue massage 的时候,通知队列,可以取出消息,停止阻塞。
handler 没有使用多线程中的阻塞队列 BlockQueue,因为主线程(系统)也在使用,如果使用BlockQueue 设置上限的话,系统可能会出现卡顿等情况。
从上图中,我们看可以出,handler 是一个生产者 - 消费者的设计模式。下面我们来看一下,
looper 中的两种循环阻塞方式:
执行时间阻塞(没有到执行时间),nativePollOnce(long ptr, int timeoutMillis) 执行阻塞操作,timeoutMillis 为 -1 表示无限等待,直到事件发生为止,如果为0,无需等待,立即执行。请看下面源码:
Message next() {
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int nextPollTimeoutMillis = 0;
for (;;) {
nativePollOnce(ptr, nextPollTimeoutMillis);// 循环进入阻塞状态,等待执行时间到达后唤醒
synchronized (this) {
if (msg != null) {
if (now < msg.when) {
// 消息不为空,并且没有到执行时间,nextPollTimeoutMillis 不为-1
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
}
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
}
}
}
复制代码
MessagaQueue 为空,执行阻塞,等待唤醒。当插入消息时,主动唤醒,请看下面源码:
Message next() {
if (ptr == 0) { // mPtr==0,表示中断循环,
return null;
}
int pendingIdleHandlerCount = -1;
int nextPollTimeoutMillis = 0;
for (;;) {
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
if (msg != null) {
} else {
// 无消息,timeoutMillis为-1表示无限等待,直到有事件发生为止
nextPollTimeoutMillis = -1;
}
}
}
}
// mPtr==0
private void dispose() {
if (mPtr != 0) {
nativeDestroy(mPtr);
mPtr = 0;
}
}
// 唤醒
boolean enqueueMessage(Message msg, long when) {
synchronized (this) {
boolean needWake;
Message p = mMessages;
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
needWake = mBlocked;
}
// mPtr != 0 循环没有中断,进行唤醒操作.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
复制代码
6、既然可以存在多个Handler 往MessageQueue 中添加数据(发消息时各个Handler 可能处于不同线程),那它内部是如何确保线程安全的?
考点
线程锁,后续会更多 synchronized 相关的东西
答案
synchronized锁: synchronized内置锁,它是由jvm自动完成的,插入和取都需要锁,因为取的时候,可能正在插入。它是锁的对象,因为MessageQueue 每个线程中只有一个Looper,每个Looper又只有一个MessageQueue.
7、我们使用Message 时应该如何创建它?
考点
难道是想知道有没有使用过?
答案
在创建Message 对象时,有三种方法:
Message message = new Message();
Message message1 = Message.obtain(); 查看源码的时候,发现内部调用的也是obtain() 方法。
Message message2 = handler.obtainMessage();
8、Looper 死循环为什么不会导致应用卡死
考点
难道是 ANR 的机制?
答案
应用卡死也就是发生ANR,那什么是ANR?ANR是如何检测的,知道了ANR是如何检测的,我们就知道Looper死循环为什么不会导致应用卡死?
什么是ANR?
ANR指的是应用无响应,ANR主体实现在系统层。所有与ANR相关的消息,都会经过系统进程(AMS)调度,然后派发到应用进程完成对消息的实际处理,同时,系统进程设计了不同的超时限制来跟踪消息的处理。 一旦应用程序处理消息不当,超时限制就起作用了, 它收集一些系统状态,比如CPU/IO使用情况、进程函数调用栈,并且报告用户有进程无响应了(ANR对话框)。
总结
来源:
https://blog.csdn.net/u010755471/article/details/124840957
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/)
Powered by Discuz! X3.4