一、前言:
主要介绍 Looper 常见的面试题,包括以下几个问题:
- 先说一下造成ANR的原因:
- 造成ANR的原因一般有两种:
- 在 5s 内没有相响应事件的处理,例如:键盘按下、屏幕触摸等(即主线程正在处理前一个事件,没有及时的完成或者looper被某种原因阻塞住了)
- 广播没有在 10s 内执行完毕。
- 为了避免ANR异常,android使用了Handler消息处理机制。让耗时操作在子线程运行。
2、介绍一下Handler
- 答:Handler机制是Android系统中用于实现线程之间通信的一种机制。它包含了一个MessageQueue对象和一个Looper对象,用来处理MessageQueue中的消息。
- 当我们需要在一个线程中更新UI或执行一些耗时操作时,我们可以使用Handler机制。我们可以在主线程中创建一个Handler对象,并将其关联到主线程的Looper对象上。然后,在其他线程中,我们可以通过Handler.post()方法或Handler.sendMessage()方法向主线程发送消息,这些消息会被加入到主线程的MessageQueue中。主线程通过Looper.loop()方法不断地从MessageQueue中获取消息,并根据消息类型执行相应的操作。
- 通过这种方式,我们可以在其他线程中发送消息,然后在主线程中处理这些消息,从而实现线程之间的通信。这种机制也被广泛应用于Android应用程序中的异步任务、定时器等场景。
二、解决问题:
1. 一个线程有几个 Handler?
答:一个线程内创建 n 个 handler,就有 n个 Handler。
2. 一个线程有几个 Looper?如何保证?
答:一个线程有 1 个 Looper;通过 ThreadLocal 来保证一个线程只有一个 Looper。
3. Handler 内存泄漏原因?为什么其他内部类没有说过这个问题?
答:内部类持有外部类的引用(默认持有了外部类的 this);原理不同(MessageQueue->message->handler->activity)。
4.为何主线程可以 new Handler?如果在子线程中new Handler要做什么准备?
答:在主线程中,已经默认帮我们创建了Looper.prepare()和Looper.loop()方法,所以可以直接拿来使用;子线程是默认没有 Looper 的,如果需要使用 Handler 就必须为线程创建 Looper。我们经常提到的主线程,也就是 UI 线程,它就是 ActivityThread,ActivityThread被创建时就会初始化 Looper,这也就是在主线程中默认可以使用 Handler 的原因。
子线程使用 Handler方法如下:
//1.创建Looper对象
Looper.prepare();
//2.实例化Handler
handler =new Handler(){
public void handleMessage(Message msg) {
//接受主线程发送过来的消息
super.handleMessage(msg);
String info =(String) msg.obj;
};
};
//3.循环读取MessageQueue
Looper.loop();
下方是发送消息:
public void sendMessage(View v){
if(handler!=null){
Message msg =Message.obtain();
msg.obj ="来自主线程的数据";
handler.sendMessage(msg);
}
}
5.子线程中维护的 Looper,消息队列无消息的时候处理方案是什么?有什么用?
答:无消息的时候,会调用 quit()或 quitSafely()方法,删除所有消息,释放内存,释放线程。
- quit():会直接退出 Looper;
- quitSafely():只是设定一个退出标记,然后把消息队列中的已有消息处理完毕后才安全退出。
- Looper 退出后,通过 Handler 发送消息会失败,这个时候 Handler 的 send 方法会返回 false。
子线程的三个步骤如下:
//1.创建Looper对象
Looper.prepare();
//2.循环读取MessageQueue
Looper.loop();
//3.释放内存
quit();
阻塞函数:
Message msg = queue.next();
if(msg == null){
return;
}
注意:主线程不允许调用 quit()方法。
6. 既然可以存在多个 Handler 往 MessageQueue 中添加数据(发消息时各个 Handler 可能在不同的线程),那它内部是如何确保线程安全的?
答:Synchronized 保证发消息和取消息的安全性;handler delay 的消失,时间不完全准确;
注意:Synchronized修饰方法、静态方法和代码块;
7. 我们使用Mesage 时应该如何创建它?
答: 使用 obtain() 方法,内存复用,防止抖动。
发送消息:
public void sendMessage(View v){
if(handler!=null){
Message msg =Message.obtain();
msg.obj ="来自主线程的数据";
handler.sendMessage(msg);
}
}
8.使用 Handler 的 postDelay 后消息对列会有什么变化?
答:消息对列为空,消息不会执行,计算等待时间,然后进行消息队列操作。
9.Looper 死循环为什么不会导致应用卡死?
- 在处理消息的时候使用了Looper.loop()方法,并且在该方法中进入了一个死循环,同时Looper.loop()方法是在主线程中调用的,那么为什么没有造成阻塞呢?
看看Looper.loop()方法无限循环部分的代码
while (true) {
//取出消息队列的消息,可能会阻塞
Message msg = queue.next(); // might block
...
//解析消息,分发消息
msg.target.dispatchMessage(msg);
...
}
- 为什么这个死循环不会造成ANR异常呢?
答:因为Android 的是由事件驱动的,looper.loop() 不断地接收事件、处理事件,每一个点击触摸或者说Activity的生命周期都是运行在 Looper.loop() 的控制之下,如果它停止了,应用也就停止了。只能是某一个消息或者说对消息的处理阻塞了 Looper.loop(),而不是 Looper.loop() 阻塞它。
10 . ActivityThread中main方法
- ActivityThread类的注释上可以知道这个类管理着我们平常所说的主线程(UI线程)
- 首先 ActivityThread 并不是一个 Thread,就只是一个 final 类而已。我们常说的主线程就是从这个类的 main 方法开始,main 方法很简短
public static final void main(String[] args) {
...
//创建Looper和MessageQueue
Looper.prepareMainLooper();
...
//轮询器开始轮询
Looper.loop();
...
}
- 简单的来说:ActivityThread的main方法主要就是做消息循环,一旦退出消息循环,那么你的程序也就可以退出了。
11. 处理消息handleMessage方法
如下所示
- 可以看见Activity的生命周期都是依靠主线程的Looper.loop,当收到不同Message时则采用相应措施。
- 如果某个消息处理时间过长,比如你在onCreate(),onResume()里面处理耗时操作,那么下一次的消息比如用户的点击事件不能处理了,整个循环就会产生卡顿,时间一长就成了ANR。
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
case LAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
r.packageInfo = getPackageInfoNoCheck(r.activityInfo.applicationInfo, r.compatInfo);
handleLaunchActivity(r, null);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
break;
case RELAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityRestart");
ActivityClientRecord r = (ActivityClientRecord) msg.obj;
handleRelaunchActivity(r);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
break;
case PAUSE_ACTIVITY:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
handlePauseActivity((IBinder) msg.obj, false, (msg.arg1 & 1) != 0, msg.arg2, (msg.arg1 & 2) != 0);
maybeSnapshot();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case PAUSE_ACTIVITY_FINISHING:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
handlePauseActivity((IBinder) msg.obj, true, (msg.arg1 & 1) != 0, msg.arg2, (msg.arg1 & 1) != 0);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
...........
}
}
12. loop的循环消耗性能吗?
答:主线程Looper从消息队列读取消息,当读完所有消息时,主线程阻塞。子线程往消息队列发送消息,并且往管道文件写数据,主线程即被唤醒,从管道文件读取数据,主线程被唤醒只是为了读取消息,当消息读取完毕,再次睡眠。因此loop的循环并不会对CPU性能有过多的消耗。
13. 什么是 ThreadLocal?
答:ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其他线程来说则获取不到数据。
14. 系统为什么不允许在子线程中访问 UI 呢?
答:这是因为 Android 的 UI 控件不是线程安全的,如果在多线程中并发访问可能会导致 UI 控件处于不可期的状态。
15. 为什么系统不对 UI 控件的访问加上锁机制呢?
答:首先加上锁机制会让 UI访问的逻辑变得复杂;其次锁机制会降低 UI 访问的效率,因为锁机制会阻塞某些线程的执行。最简单且高效 的方法就是采用单线程模型处理 UI 操作。
16. Handler是什么?
- 答:Handler机制是Android系统中用于实现线程之间通信的一种机制。它包含了一个MessageQueue对象和一个Looper对象,用来处理MessageQueue中的消息。
- 当我们需要在一个线程中更新UI或执行一些耗时操作时,我们可以使用Handler机制。我们可以在主线程中创建一个Handler对象,并将其关联到主线程的Looper对象上。然后,在其他线程中,我们可以通过Handler.post()方法或Handler.sendMessage()方法向主线程发送消息,这些消息会被加入到主线程的MessageQueue中。主线程通过Looper.loop()方法不断地从MessageQueue中获取消息,并根据消息类型执行相应的操作。
- 通过这种方式,我们可以在其他线程中发送消息,然后在主线程中处理这些消息,从而实现线程之间的通信。这种机制也被广泛应用于Android应用程序中的异步任务、定时器等场景。
17. 消息机制是什么?
答:Android的消息机制主要是指Handler的运行机制以及Handler所附带的MessageQueue和Looper的工作过程。
18. ThreadLocal工作原理是什么?
答:ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,而且只有在指定的线程里才能获取到存储的数据,对于其他线程来说是获取不到的。
消息机制为什么要用到这个呢?因为对于Handler来说,要获取到当前线程的Looper,而Looper的作用域是当前线程,并且不同线程有不同的Looper,这个时候就可以很方便的通过ThreadLocal对Looper进行存取。
19.Handler和Message、MessageQueue、Looper之间的关系?
Message:Handler接收与处理的消息对象
MessageQueue:
消息队列,以先进先出队列形式管理所有的Message,且在初始化Looper对象时会创建一个与之关联的MessageQueue。每个线程最多只有一个MessageQueue。MessageQueue通常都是由Looper来管理,而主线程创建时,会创建一个默认的Looper对象,而Looper对象的创建,将自动创建一个MessageQueue。其他非主线程,不会自动创建Looper。
Looper:
每个线程只能够一个Looper,管理MessageQueue,不断从中去除Message分发给对应的Handler处理。
通俗一点讲:当我们的子线程想修改Activity中的UI组件时,我们可以新建一个Handler对象,通过这个对象向主线程发送消息;而我们发送的消息会先到主线程的MessageQueue中进行等待,由Looper按先入先出顺序取出,再根据Message对象的what属性分发给对应的Handler进行处理。
20. 在子线程中创建Handler报错是为什么?
错误信息:
java.lang.RuntimeException: Can’t create handler inside thread that has not called Looper.prepare()
不能再子线程中新建handler,没有呼叫Looper.prepare();,
答:handler构造函数中 需要持有一个looper的对象,如果没有,提示这个错误;
looper的主要作用是与当前线程绑定,保证一个线程只会有一个Looper实例,同时一个Looper实例也只有一个MessageQueue。然后looper的loop()方法就是不断从MessageQueue中取出消息,交给handler去发送消息。 而子线程是没有默认looper的,所以就会报错了。 解决办法也很简单,我们只需要调用prepare()方法,新建looper对象就好。看一下prepare都干了什么:
Looper.prepare()
21.如何在子线程创建Looper?
Looper.prepare();
22.Looper.loop()在什么情况下会退出?
1、next方法返回的msg == null
2、线程意外终止
23.Android如何保证一个线程最多只能有一个Looper?
1、Looper的构造方法为private,所以不能直接使用其构造方法创建。
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
要想在当前线程创建Looper。需使用Looper的prepare方法,Looper.prepare()。
假设如今要我们来实现Looper.prepare()这种方法,我们该怎么做?我们知道,Android中一个线程最多仅仅能有一个Looper,若在已有Looper的线程中调用Looper.prepare()会抛出RuntimeException(“Only one Looper may be created per thread”)。
2、看一看Android的中Looper的源代码。
public class Looper {
static final ThreadLocal sThreadLocal = new ThreadLocal();
public static void prepare() {
prepare(true);
}
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));
}
...
}
prepare()方法中调用了ThreadLocal的get和set方法。然而整个过程没有加入同步锁,Looper是怎样实现线程安全的?
通过ThreadLocal来保证Looper的唯一。
24.Handler 消息处理流程
流程图解析: 相关名词
- UI线程:就是我们的主线程,系统在创建UI线程的时候会初始化一个Looper对象,同时也会创建一个与其关联的MessageQueue;
- Handler:作用就是发送与处理信息,如果希望Handler正常工作,在当前线程中要有一个Looper对象
- Message:Handler接收与处理的消息对象
- MessageQueue:消息队列,先进先出管理Message,在初始化Looper对象时会创建一个与之关联的MessageQueue;
- Looper:每个线程只能够有一个Looper,管理MessageQueue,不断地从中取出Message分发给对应的Handler处理!
简述:
当我们的子线程想修改Activity中的UI组件时,我们可以新建一个Handler对象,通过这个对象向主线程发送信息;而我们发送的信息会先到主线程的MessageQueue进行等待,由Looper按先入先出顺序取出,再根据message对象的what属性分发给对应的Handler进行处理!
参考文章:
https://blog.csdn.net/wl1433289703/article/details/95633042
- 1.技术博客汇总
- 2.开源项目汇总
- 3.生活博客汇总
- 4.其他汇总
————————————————
原文链接:https://blog.csdn.net/m0_37700275/article/details/89419095