每日一题: Handler源码
面试率: ★★★☆☆
面试技巧与建议
handler作为Android的主线程,是其主要的消息机制,作为的软件开发人员,掌握并熟知handler底层运行原理已经成为了Android开发的一种标配,面试必问.
面试建议
handler涉及很广,我们可以选择适合自己的深度来局部掌握该面试题,为什么局部掌握,因为全局去了解耗费精力太大,而且原理逻辑繁杂,不适合快速吸收.因此下面我给出了两种方案:
深入者如Looper、Handler、Message三者关系
浅入者如handler性能优化,常见方法使用与区别,handler与子线程等
相关问题.
面试技巧
其实可以从侧面的角度去分析这个问题,如实际项目中如何正确的使用handler.使用时遇到过什么问题,如何解决该问题.
面试题
下面是从handler的源码和实际开发中提取出的一些面试问题,从实际中出发探讨handler源码,面试中最恰当不过的事情,莫过于此.
一个线程可以有几个Lopper实例,为什么?
一个线程中只有一个Looper实例.
sThreadLocal是一个ThreadLocal对象,可以在一个线程中存储变量。可以看到,在第5行,将一个Looper的实例放入了ThreadLocal,并且2-4行判断了sThreadLocal是否为null,否则抛出异常。这也就说明了Looper.prepare()方法不能被调用两次,同时也保证了一个线程中只有一个Looper实例.
源码
public static final void prepare() {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(true));
}
继续看Lopper的构造方法做了什么,那里面的loop()方法呢,知道finalfinal Looper me = myLooper()
的作用吗?
构造方法中,创建了一个MessageQueue(消息队列)
final Looper me = myLooper()
的final保证了loopr的唯一性.
构造方法源码:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mRun = true;
mThread = Thread.currentThread();
}
loop()方法源码.
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;//final了消息队列
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) {
Message msg = queue.next(); //阻塞轮询消息
if (msg == null) {
return;
}
//其他源码省略....
}
通过前面1,2问题可以总结下Looper的主要作用是什么?
Looper主要作用:
- 与当前线程绑定,保证一个线程只会有一个Looper实例,同时一个Looper实例也只有一个MessageQueue。
- loop()方法,不断从MessageQueue中去取消息,交给消息的target属性的dispatchMessage去处理。
首先Looper.prepare()在本线程中保存一个Looper实例,然后该实例中保存一个MessageQueue对象;因为Looper.prepare()在一个线程中只能调用一次,所以MessageQueue在一个线程中只会存在一个。
从Handler中有没学到什么好的技术,或者思想?
看过MessageQueue吗,里面的for (;;) 和while(true) 区别:
- while区别根据编译器不同情况有所不同,例如写死循环while(true)有的编译器会傻傻的每次都把true做一下判断.
- for(;;)写死循环比较好,减少了判断
编译前 编译后
while (true){todo}; mov eax,true
test eax,eax
je foo+23h
jmp foo+18h
编译前 编译后
for (;;){}; jmp foo+23h
一目了然,for (;;)指令少,不占用寄存器,而且没有判断跳转,比while (true)好。
Handler如何与MsgQueue关联在一起?
这个问题可以先观察下Handler的构造方法到底做了什么?
首先得到当前线程中保存的Looper实例,进而与Looper实例中的MessageQueue想绑定关联.
public Handler(Callback callback, boolean async) {
//部分源码省略...
//获取当前线程保存的Looper实例
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
//通过上面mLooper获取了实例中保存的MessageQueue,这样两者就绑定在一起了,关联上.
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
使用handler发送Message的流程是什么?
sendMessage(hd) ->sendMessageAtTime(hd) ->
enqueueMessage(hd) ->dispatchMessage(looper) ->
handleMessage(looper)
那么在Activity中,我们并没有显示的调用Looper.prepare()和Looper.loop()方法,为啥Handler可以成功创建呢?
这是因为在Activity的启动代码中,已经在当前UI线程ActivityThread
调用了Looper.prepare()和Looper.loop()方法。
子线程里可以创建handler吗?
可以的,可以在一个子线程中去创建一个Handler,然后使用这个handler实例在任何其他线程中发送消息,最终处理消息的代码都会在你创建Handler实例的线程中运行。
详情见下面实例:
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare(); //创建Looper并与本线程绑定【第一步】
mHandler = new Handler() {
//定义并实现Handler.handleMessage方法【第二步】
public void handleMessage(Message msg) {
// process incoming messages here
}
};
Looper.loop(); // 启动Looper消息循环【第三步】
}
}
handler会不会导致Activity的泄漏?
- 问题分析
Handler泄露的关键点有两个:- 内部类
- 生命周期和Activity不一定一致
看看下面代码
public class MainActivity extends QActivity {
//这里应该使用static否则容易泄漏
class MyHandler extends Handler {
... ...
}
}
内部类持有外部类Activity的引用,当Handler对象有Message在排队,则无法释放( 比如activity已经destory了但是MessageQueen还有消息则,looper就会在轮询,因此activity就无法被释放,因内部类有act引用),进而导致Activity对象不能释放。
- 解决方式:
如果Handler中有延迟的任务或者是等待执行的任务队列过长,都有可能因为Handler继续执行而导致Activity发生泄漏。
此时的引用关系链是:
Looper -> MessageQueue -> Message -> Handler -> Activity
解决方案
- 可以在UI退出之前,执行remove Handler消息队列中的消息与runnable对象。
- 使用Static + WeakReference的方式来达到断开Handler与Activity之间存在引用关系的目的。
主线程中的Looper.loop()一直无限循环为什么不会造成ANR?
- Activity的生命周期都是运行在 Looper.loop() 的控制之下,当收到不同Message时则采用相应措施,如果它停止了,应用也就停止了.
- Android的消息是一种事件机制,而looper.loop() 不断地接收事件、处理事件,只要每次处理的事件不被looper堵塞那么,就不会爆anr.
- 也就说我们的代码其实就是在这个循环里面去执行的,当然不会阻塞了.
让我们先看一遍造成ANR的原因,就明白了
造成ANR的原因一般有两种:
1. 当前的事件没有机会得到处理(即主线程正在处理前一个事件,没有及时的完成或者looper被某种原因阻塞住了)
2. 当前的事件正在处理,但没有及时完成
总结:
真正会卡死主线程的操作是在回调方法onCreate/onStart/onResume等操作时间过长,会导致掉帧,甚至发生ANR,而looper.loop本身不会导致应用卡死.
总结
看了代码之后,觉得它一点都不神秘,不就是实现了我们常用的“消息驱动机制”吗?
消息驱动机制的四要素:
1. 接收消息的“消息队列”
2. 阻塞式地从消息队列中接收消息并进行处理的“线程”
3. 可发送的“消息的格式”
4. “消息发送函数”
以上四要素在Android中实现对应的类如下:
1. 接收消息的“消息队列” ——【MessageQueue】
2. 阻塞式地从消息队列中接收消息并进行处理的“线程” ——【Thread+Looper】
3. 可发送的“消息的格式” ——【Message<Runnable被封装在Message中>】
4. “消息发送函数”——【Handler的post和sendMessage】
网友评论