Android Handler相关面试题你能答对多少?子线程和主线程是如何切换的?
Posted 冬天的毛毛雨
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android Handler相关面试题你能答对多少?子线程和主线程是如何切换的?相关的知识,希望对你有一定的参考价值。
Handler机制是面试官非常喜欢问的知识点,源码我看完几遍,还是会觉得不清晰,里面的代码非常绕。后来我决定放弃探究细节,先把相关的类和调用的方法画一个草图,然后理清互相调用的关系,再结合关于Handler的高频面试题,去寻找答案,这样一轮下来,会对Handler有更深的认识。现在把这个过程,面试题寻找的答案以及相关的解释整理成这篇文章,如有不对,欢迎大家予以指正。
高频面试题
1.获取Message实例的方式有哪些?哪一种更好?
获取Message实例的方法主要有两种,一种是直接创建,Message msg = new Message。另一种是通过Message.obtain()或者Handler.obtatinMessage()来得到一个Message对象。更推荐使用后一种方式,这种方式得到的对象是从对象回收池中得到,复用已经处理完的Message对象,而不是重新生成一个新对象。Message.obtain()和Handler.obtatinMessage()最终都是调用了Message类的obtain()方法,查看方法内容,就知道是从对象回收池里得到Message。
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
2.当Activity有多个Handler的时候,Message消息是否会混乱?怎么样区分当前消息由哪个Handler处理?
不会混乱,哪个Handler发送的消息,到时候也是这个handler处理。在发送消息的时候,会绑定target,这个target就是Handler本身,当需要handler调用dispatchMessage(msg)处理消息的时候,这个Handler就是发送消息时绑定的handler。
无论用哪一种方法发送消息,最终都会调用enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis)来发送消息
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
这里的this,就是当前的handler。在来看需要Handler处理消息的时候,取的是哪一个handler,下面贴出主要源码。
public static void loop() {
......
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// This must be in a local variable, in case a UI event sets the logger
......
if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
final long dispatchEnd;
try {
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
......
msg.recycleUnchecked();
}
}
这是循环消息时的部分代码,处理消息代码是msg.target.dispatchMessage(msg);,这里的target就是当时发送消息的handler。
3.在子线程发送消息,却能够在主线程接收消息,主线程和子线程是怎么样切换的?
子线程用handler发送消息,发送的消息被送到与主线程相关联的MessageQueue,也是主线程相关联的Looper在循环消息,handler所关联的是主线程的Looper和MessageQueue,所以最后消息的处理逻辑也是在主线程。只有发送消息是在子线程,其它都是在主线程,Handler与哪个线程的Looper相关联,消息处理逻辑就在与之相关的线程中执行,相应的消息的走向也就在相关联的MessageQueue中。所以子线程切换到主线程是很自然的过程,并没有想象中的复杂。
4.能不能在子线程中创建Handler?
可以,但是在创建前先调用prepare()方法创建Looper。Handler创建的时候,会去检查是否有创建Looper,如果没有创建就会抛出异常。相关源码如下:
public Handler(Callback callback, boolean async) {
......
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
所以我们在子线程需要先调用prepare()方法创建Looper。这里还多提一点,在主线程创建就不需要自己创建Looper,因为在ActivityTread类里面,已经为我们创建好了,相关源码如下:
public static void main(String[] args) {
......
Looper.prepareMainLooper();// 为主线程创建looper
// Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
// It will be in the format "seq=114"
long startSeq = 0;
if (args != null) {
for (int i = args.length - 1; i >= 0; --i) {
if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
startSeq = Long.parseLong(
args[i].substring(PROC_START_SEQ_IDENT.length()));
}
}
}
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
Looper.prepareMainLooper();这句代码就是为主线程创建Looper。 所以在主线程中直接创建一个Handler,就直接可以循环消息,因为安卓的主线程已经为我们准备好了Looper。
5.一个线程可以有几个Handler?几个Looper?
一个线程可以有多个Handler,但是只有一个Looper。创建Handler之前,需要创建Looper,否则会报错。源码里面已经做了说明。
public Handler(Callback callback, boolean async) {
......
mLooper = Looper.myLooper();
if (mLooper == null) {//判断Looper是否被创建
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
再来看Looper的创建,是在prepare()方法里。
// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<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));
}
在创建之前去判断looper是否存在,存在就会抛出Only one Looper may be created per thread异常,这是在告诉我们一个线程只能有一个Looper。而TreadLocal的作用就是线程间隔离,确保一个线程对应一个Looper。还可以看看Looper构造方法的源码
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
MessageQueue的初始化是在Looper的构造方法里。不管一个线程有多少个Handler,相关联的都是同一个Looper和MessageQueue。
关于Handler,可以问的问题有很多,以上只是抽出一些我认为比较重要的问题。在寻找答案以后,我将Handler机制的整个过程在脑海中过了一遍,并且画了个草图。
Handler机制中重要类的相互关联图
Handler机制原理涉及几个重要的类:Handler、Message、MessageQueue、Looper。
就用子线程向主线程发送消息来说明整个过程。
首先在主线程创建一个Handler,在Handler类里面会创建Looper以及MessageQueue的对象,并且在Handler构造方法里面赋值
final Looper mLooper;
final MessageQueue mQueue;
public Handler(Callback callback, boolean async) {
......
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
mLooper = Looper.myLooper();得到的Looper是主线程的Looper
mQueue = mLooper.mQueue;得到的MessageQueue就是在Looper构造方法里面创建的MessageQueue。
创建好了Handler实例,我们就会在子线程调用handler.sendMessage(msg);发送消息,将message放到MessageQueue里面。在enqueueMessage()里面就给每个message设置target,这个target就是当前的handler。
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
再然后调用Looper.loop();(在ActivityThread的main()里面已经帮我们写好)开始循环消息,拿到消息以后就会用handler取出消息进行处理,重点代码是msg.target.dispatchMessage(msg);,这里的handler就和一开始我们为message设置的Handler对应。
/**
* 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);
}
}
最后我们在Handler的handleMessage(msg)里面去处理我们的消息。
这就是子线程给主线程发送消息的整个过程,源码很多,我只是截取了部分重点。这里多提一点就是线程处理消息。 在线程中处理消息需要做三件事情
- 先创建一个Looper (Looper.prepare())
- 再创建Handler,默认是和当前线程的Looper关联起来
- 循环消息(Looper.loop())
这三个步骤的顺序不能调换。因为主线程已经帮我们创建了Looper,所以我们不需要写,如果是在子线程创建Looper就需要了。
Handler机制的理解要靠自己去琢磨,不断的看源码,去理解,理清它们之间的互相调用。只有比较深入的理解了Handler,才能在面试中回答面试官的问题,靠死记硬背是不可取的。多花时间去看,多动脑,不偷懒,总能把Handler拿下。
最后
文章到这里就结束了,有些东西你不仅要懂,而且要能够很好地表达出来,能够让面试官认可你的理解,例如Handler机制,这个是面试必问之题。有些晦涩的点,或许它只活在面试当中,实际工作当中你压根不会用到它,但是你要知道它是什么东西。
不管怎么样,不论是什么样的大小面试,要想不被面试官虐的不要不要的,只有刷爆面试题题做好全面的准备,当然除了这个还需要在平时把自己的基础打扎实,这样不论面试官怎么样一个知识点里往死里凿,你也能应付如流啊~
最后小编在这里给大家分享一份,由我在网上收集了一些 android 开发相关的学习文档、面试题、Android 核心笔记等等文档,希望能帮助到大家学习提升,如有需要参考的可以直接去我 CodeChina 地址:https://codechina.csdn.net/u012165769/Android-T3 访问查阅。
以上是关于Android Handler相关面试题你能答对多少?子线程和主线程是如何切换的?的主要内容,如果未能解决你的问题,请参考以下文章