【转载请注明出处:走进源码,Android面试最常见Handler、Looper、Message问题总结与解答 CSDN 王智博】
今天楼主以面试的角度,走进源码,去探索Handler、Handler、Looper、Message的奥秘。
随着各种网络框架的普及,线程之间的通信再也不需要我们手动的创建Handler,然后handleMessage了,但是当我们想要创建一个主线程延时任务的时候,还是避免不了使用Handler发送一个延时任务,比如下面操作。
如果还没怎么使用过Handler的同学,可以看最简单的Handler、Looper、Message使用指南一(附github源码)
Handler mHandler = new Handler(Looper.getMainLooper());
Handler mHandler2 = new Handler();
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
//延时一秒的任务
}
}, 1000);
那么请问1.mHandler和mHandler2的声明区别是什么呢?2.为什么postDelay方法能够实现延时,延时底层是怎么实现的呢?
你看,这是不是我们面试常问的问题吗?
然后我们再看下面的问题,在子线程使用Handler sendMessage发送消息,主线程便能获取数据并且展示。
3.那么为什么Handler发送Message,主线程便能接收到Message并获取数据?4.为什么Handler对象能够子线程和主线程都能访问了?(这是java内存模型的知识,后面我会单独写一篇)
Handler mainHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
//主线程处理
switch (msg.what) {
case WHAT_MODIFY_TV:
tvReciver.setText("接受子线程传递的参数:" + msg.obj);
break;
default:
}
}
};
//子线程发送消息
mainHandler.sendMessage(Message.obtain(mainHandler, WHAT_MODIFY_TV, num++));
我们首先看new Handler(),实际调用Handler(callback,async),可以看到这里会初始化一个Looper,然后判断如果Looper不存在,说明你所在的线程还没有调用Looper的prepare方法。也就是说Looper的prepare方法里面是looper的初始化?
我们可以看到Looper的myLooper方法,实际上是获取ThreadLocal的Looper对象
那么sThreadLocal这个对象又是啥?
static final ThreadLocal sThreadLocal = new ThreadLocal();
实际上是一个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的prepare方法,ThreadLocal的Looper对象就不会创建,所以handler报错说你需要prepare。
所以new handler()和new Handler(Looper.getMainLooper())的区别在于new handler()是自己调用Looper.myLooper()获取到的looper,也就是获取你当前所在线程的Looper,一般我们在主线程new handler()获取到的就是主线程的Looper,也就是主线程的ThreadLocal
那么Looper.getMainLooper()为什么就能获取到主线程的Looper,而且就算是在子线程中,我们还是能获取到主线程的Looper。
这里其实还是内存模型的知识,后面会单独介绍下。
首先我们来看下Looper.getMainLooper()方法,可以看到这里是一个类锁,是线程安全的,返回的是sMainLooper这个对象,这是一个静态对象,Looper.getMainLooper是一个静态方法,所以所有的线程都能调用这个方法获取sMainLooper这个对象。
public static Looper getMainLooper() {
synchronized (Looper.class) {
return sMainLooper;
}
}
那么主线程这个sMainLooper对象又是什么时候初始化的呢? 可以看到是prepareMainLooper这个方法中初始化的,那么我们来看下这个prepareMainLooper的调用时机。
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
其实是在ActivityThread的main主函数里面调用的。
ActivityThread
public static void main(String[] args) {
...
Looper.prepareMainLooper();
}
小结:
1.new handler()持有的是当前线程的Looper,而new handler(Looper.getMainLooper())持有的是主线程的Looper
2.Looper其实是一个ThreadLocal类型的,myLooper()通过ThreadLocal.get获取当前线程的Looper
3.Looper.getMainLooper()获取的是sMainLooper这个静态对象,这个对象是在ActivityThread主线程的main主函数入口中,调用prepareMainLooper()是初始化的
对象是存在java虚拟机的内存模型中的堆区域,这个区域是所有线程都能够访问的
我们看上面的图,左边的是主线程,主线程中有一个Looper,Looper里面有一个消息队列MessageQueue。Looper的初始化是在Looper.prepare()里面,然后主线程ActivityThread调用loop()方法,从主线程一直取消息(如果没有消息,就拥塞)。我们看一下loop()方法。精简之后我们可以看到,这是一个for的死循环一直朝队列里面取msg,取出的msg执行target的dispatchMessage方法,其中的target又是什么了?
public static void loop() {
...
for (;;) {
Message msg = queue.next(); // 可能拥塞
if (msg == null) {
// 没有消息的话 就退出 looper.quit就是这个方式
return;
}
...
try {
//msg.target也就是handler的dispatchMessage方法
msg.target.dispatchMessage(msg);
}
...
//把msg放置到回收池里(队列头部)
msg.recycleUnchecked();
}
}
可以看到其实就是handler,所以就是执行handler的dispatchMessage方法。我们再看一下handler的 dispatchMessage。
Handler target;
public void dispatchMessage(Message msg) {
//Runnable postRunnable
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
//如果handler设置的callback返回true,表示消费了事件,不会调用handleMessage
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
可以看到,如果是消息中设置了callback,则首先执行callback,其实这个callback就是runnnable类型,也就是我们常用的postRunnable,然后是判断Handler的mCallback回调的handleMessage方法的返回值是否为true,true的话表示消息被消费了,否则才调用handler的handleMessage方法。
小结:handler把message发送到looper的messageQueue里面,messageQueue的next方法获取消息,然后调用message的dispatchMessage方法,然后找到其中的handler的handleMessage方法。
思考:messageQueue的next方法是拥塞的,请问没有消息的时候主线程会被卡死吗?
其实比如通过applicationThread这个匿名binder,AMS会调用其schedulePauseActivity方法,这个时候就会向主线程放入处理Activity Pause的消息。
messageQueue的next方法拥塞,又是怎么实现的呢?
Looper.loop -> MessageQueue.nativePollOnce
epoll_create() epoll_ctl() 注册事件的回调
looper.pollInner() -> epoll_wait() 等待接受事件唤醒的回调
MessageQueue.enqueueMessage(Message msg, long when) -> MessageQueue.nativeWake(long ptr)
我们首先看一下postDelay的代码,第4行首先会调用getPostMessage(r),然后第7行从Message缓存池里面obtain一个Message,然后把Runnable赋值到它的callback中,然后在第4行调用sendMessageDelay()方法,可以看到实际上最后调用了第17行的sendMessageAtTime,可以看到这个方法的第二个参数是当前时间(SystemClock.uptimeMillis)加上延时时间(delayMillis)。这里我们简单聊看一下消息的回收池。
// 1.1
public final boolean postDelayed(Runnable r, long delayMillis)
{
return sendMessageDelayed(getPostMessage(r), delayMillis);
}
// 1.2
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
// 1.3
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
....
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
可以看到我们通过obtain方法获取sPool静态池子里面的获取链表头的Message 。好了扯远了,我们继续回到sendMessageAtTime方法。
//同步的对象
private static final Object sPoolSync = new Object();
//Message回收池 全局的静态对象 链表
private static Message sPool;
//从全局的池子里面获取一个Message,避免创建Message重新分配内存
public static Message obtain() {
//线程安全
synchronized (sPoolSync) {
if (sPool != null) {
//取出当前链表头元素 这个就是我们获取的Message
Message m = sPool;
//把链表的头结点设置给下一个元素
sPool = m.next;
//把message的next指针置空
m.next = null;
m.flags = 0; // clear in-use flag
//池子的数量减1
sPoolSize--;
return m;
}
}
return new Message();
}
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
....
return enqueueMessage(queue, msg, uptimeMillis);
}
可以看到第4行调用了MessageQueue的enqueueMessage方法。然后我们再来看下面这样图是不是就更能理解了,Handler的sendMessage方法,实际上最后是调用MessageQueue的enqueueMessage方法。接下来我们分析这个方法。
boolean enqueueMessage(Message msg, long when) {
....
synchronized (this) {
...
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
//如果当前事件的时间<队列头的事件时间,说明当前事件优先级更高,把当前事件放在队列头
if (p == null || when == 0 || when < p.when) {
//把msg作为新的链表头,如果队列拥塞的话,唤醒它
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
//头结点是障碍物并且是异步的
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.next
prev.next = msg;
}
// 需要唤醒事件队列
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
我们可以看到第10行,如果当前的Message的时间<链表头的时间,就把当前Message放到最前面,第24行如果当前的消息遇到了事件比他大的,然后第 32行,就把Message插入到这个消息的前面。总之就是一句话,链表按照when字段排序。
Message next() {
....
int nextPollTimeoutMillis = 0;
for (;;) {
....
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
if (msg != null) {
if (now < msg.when) {
//下一个消息还没有准备好,设置一个时间去唤醒它
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
nextPollTimeoutMillis = -1;
}
}
}
第10行我们看到只有msg.when<=now的消息,才会取出来。那么msg的when字段是什么呢?其实就是msg创建的时间+postdelay的时间,所以如果当前时间戳没有到msg预期的postDelay的时间,第12行,会去取msg.when和当前的差值作为nextPollTimeoutMillis的值,然后去调用nativePollOnce(ptr, nextPollTimeoutMillis)。
所以说每个Message会携带一个期望执行时间(when),MessageQueue取消息的时候,会去比较这个期望执行时间(when)是不是已经到了(<=when),如果到了的话,才会去取消息,否则等一段时间去唤醒它。