本文源码取自Android API Level 30
本文目标
彻底搞懂Handler源码套路以及经典面试8问
套路
我们就从最简单的场景入手,使用Handler来更新UI,子线程通过网络请求从服务器拿到数据,然后把数据用发消息的方式发送到主线程然后更新UI,这个时候就会调用到Handler的sendMessage() 或者 sendMessageDelayed() 这样的方法,然后我们追踪进sendMessage()方法:
Handler类
public final boolean sendMessage(@NonNull Message msg) {
return sendMessageDelayed(msg, 0);
}
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
//currentTimeMillis()系统当前时间,即日期时间,可以被系统设置修改
//uptimeMillis()自开机后,经过的时间,不包括深度休眠时间
//SystemClock.uptimeMillis() + delayMillis 当前时间 + 延迟时间
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
以上方法从sendMessage()一路调用到最后的 MessageQueue 的enqueueMessage()方法
MessageQueue类
boolean enqueueMessage(Message msg, long when) {
// 判断有没有 target
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
// 对当前消息队列加锁
synchronized (this) {
// 有没有在使用
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
// 判断消息队列是否弃用(通常因为线程已死)
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
// 标记消息正在使用中
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
// 第一次添加数据到队列中,或者当前 msg 的时间小于 mMessages 的时间
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
// 把当前 msg 添加到链表的第一个
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
// 不是第一次添加数据,并且 msg 的时间 大于 mMessages(头指针) 的时间
//要不要唤醒Looper= 当前Looper处于休眠状态 & 队头消息是同步屏障消息 & 新消息是异步消息
//目的是为了让异步消息尽早执行
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) { // 不断的遍历找到合适的位置
prev = p;
p = p.next;
//按时间顺序,找到该消息,合适的位置
//>>msg1.wneh=2--->msg.when=4-->msg.wneh-->6
//>>msg1.wneh=2--->msg.when=4-->【newMsg.when=5】-->msg.wneh-->6
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
// 调整链表中节点的指向关系
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// 唤醒looper
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
仔细观察以上代码发现,Message只是添加到MessageQueue队列中了,其实这个队列用的是单向链表,就是说第1个Message对象里面有个next字段,然后这个next字段指向第2个Message对象,然后第2个Message对象的next指向第3个Message对象,如此往复,然后一直指到next为null,链表结束.而且这个链表的顺序也是根据when的时间进行动态排列先后的,简单来说就是when时间越短越往前排,这个时间是从Handler类中的sendMessageDelayed方法(SystemClock.uptimeMillis() + delayMillis 当前时间 + 延迟时间 )计算得到的,而且这个SystemClock.uptimeMillis()方法是系统开机时间,只有手机深度休眠后这个值不会更新,用以下代码发送消息,在队列中如图展示
// 发送 Message1
Message message1 = new Message();
mHandler.sendMessage(message1, 0);
// 发送 Message2
Message message2 = new Message();
mHandler.sendMessageDelayed(message2,1000);
// 发送 Message3
Message message3 = new Message();
mHandler.sendMessageDelayed(message3, 500);
上面的一顿分析,我们只是看到handler发送了消息进到了MessageQueue消息队列中,然后就没了,并没有看到handler的handleMessage()方法被调用,这个时候先别捉急,先看一种现象,有时候这么写会报错
new Thread(){
@Override
public void run() {
Handler handler = new Handler();
}
}.start();
必须要这么写才可以:
new Thread(){
@Override
public void run() {
Looper.prepare();
Handler handler = new Handler();
Looper.loop();
}
}.start();
我们在Activivity中从来没有调用用Looper.prepare();为什么从来不报错呢?那是因为在应用的启动流程中,在ActivityThread的main方法中,系统帮我们做了
ActivityThread类
public static void main(String[] args) {
// ... 省略部分代码
//准备循环
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
//开始循环
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
重点看 Looper.prepareMainLooper();和Looper.loop();这两行代码,先看prepareMainLooper()
Looper类
@UnsupportedAppUsage
static final ThreadLocal sThreadLocal = new ThreadLocal();
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
/**
*该方法就是把looper对象存到ThreadLocal中
* 一个线程如何保证只有1个Looper
*1.首先利用ThreadLocal把Looper对象存到Thread对象的map中
* 2.然后就这个prepare方法里面会做判断,判断ThradLocal是否已经有存
*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));
}
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
可以看出,就是创建了Looper对象然后存到了ThreadLocal中,用来保证1个线程只有1个Looper对象,这样就保证了线程的安全.
然后看Looper.loop() 这行:
public static void loop() {
final Looper me = myLooper();
final MessageQueue queue = me.mQueue;
// 一个死循环
for (;;) {
// 不断的从消息队列里面取消息
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
try {
// 通过 target 去 dispatchMessage 而 target 就是绑定的 Handler
msg.target.dispatchMessage(msg);
} catch (Exception exception) {
} finally {
}
// 消息回收循环利用
msg.recycleUnchecked();
}
}
看到这里流程就清晰了,Looper.prepareMainLooper()创建了一个Looper对象,而且保证一个线程只有一个Looper;
Looper.loop() 这里面是个死循环,不断的从MessageQueue消息队列中取消息,然后通过Handler执行.Handler源码至此分析完毕
总结
handler 持有 Looper对象
Looper对象持有MessageQueue
handler
用于线程间通信,可以把子线程的数据发送到主线程,展开来说就是handler
通过sendMessage()
或者sendDelayMessage()
方法发送信息到MessageQueue
消息队列中,会根据when
的时间动态的更改消息队列中的位置,简单来说时间越短越位置越靠前.然后因为应用启动的时候在ActivityThread
的main
方法中会调用Looper.prepareMainLooper()
方法,这个方法的作用就是创建一个Looper对象
然后存到ThreadLocal中
(保证线程的安全性,一个线程对应一个looper
),然后还会调用Looper.loop()
方法,该方法主要是开启消息的轮询,如果有消息就调用handler
的dispatchMessage()
方法,然后调用handler
的handleMessage()
方法.
面试8问
1.为什么主线程不会因为 Looper.loop()里的死循环卡死?
主线程确实是通过Looper.loop()进入了循环状态,因为这样主线程才不会向我们一般创建的线程一样,当可执行代码执行完后,线程生命周期就终止了.
在主线程的MessageQueue没有消息时候,便阻塞在MessageQueue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到新消息达到.所以主线程大多数时候都是出于休眠状态,并不会消耗大量CPU资源
这里采用linux的epoll机制,是一种IO多路复用机制,可以同时监控多个文件描述符,当某个文件描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作拿到最新的消息,进而唤醒等待的线程.
2.post 和 sendMessage 两类发送消息的方法有什么区别?
post 一类的方法发送的是Runnable对象,但是最后还是会被封装成Message对象,将Runnable对象赋值给Message对象中的callBack字段,然后交由sendMessageAtTime()方法发送出去.
在处理消息时,会在dispatchMessage()方法里首先被handleCallback(msg)方法执行,实际上就是执行Message对象里面的Runnable对象的run方法
sendMessage 一类方法发送的直接是Message对象,处理消息时,在dispatchMessage里优先级会低于handleCallback(msg)方法,是通过自己重写的handleMessage(msg)方法执行
3.为什么要通过Message.obtain()方法获取Message对象?
obtain方法可以从全局消息池中得到一个空的Message对象,这样可以有效节省系统资源.同时,通过各种obtain重载方法还可以得到一些message的拷贝,或对Message对象进行一些初始化.
4.Handler实现发送延迟消息的原理是什么?
我们常用postDelayed()与sendMessageDelayed()来发送延迟消息,其实最终都是将延迟时间转为确定时间,然后通过 sendMessageAtTime() -> enqueueMessage() -> queue.enqueueMessage() 这一系列方法将消息插入到MessageQueue中.所以并不是先延迟在发送消息,而是直接发送消息,在借助MessageQueue的设计来实现消息的延迟处理
消息延迟处理的原理设计 MessageQueue的两个静态方法 MessageQueue.next() 和 MessageQueue.enqueueMessage().通过Native方法阻塞线程一定时间,等到消息的执行时间到后再取出消息执行.
5.同步屏障SyncBarrier是什么?有什么作用?
在一般情况下,同步和异步消息处理起来并没有什么不同.只有在设置了同步屏障后再会有差异.同步屏障从代码层面上看是一个Message对象,但是其target属性为null,用以区分普通消息.在MessageQueue.next() 中如果当前消息是提个同步屏障,则跳过后面的所有同步消息,找到第一个异步消息来处理.但是开发者调用不了.在ViewRootImpl的UI测绘流程有体现
6.IdleHandler是什么?有什么作用?
当消息队列没有消息时调用或者如果队列中仍有待处理的消息,但都未到执行时间,也会调用此方法,用以监听主线程空闲状态.
7.为什么非静态类的Handler导致内存泄漏?如何解决?
首先,非静态的内部类,匿名内部类,局部内部类都会隐式的持有其外部类的引用.也就是说在Activity中创建的Handler会因此持有Activity的引用
当我们在主线程使用Handler的时候,Handler会默认绑定这个线程的Looper对象,并关联其MessageQueue,Handler发出的所有消息都会加入到这个MessageQueue中.Looper对象的生命周期贯穿整个主线程的生命周期,所以当Looper对象中的MessageQueue里还有未处理完的Message时,因为每个Message都持有Handler的引用,所以Handler无法被回收,自然其持有引用的外部类Activity也无法回收,造成泄漏
使用静态内部类 + 弱引用的方式
8.如何在子线程中弹出toast?
调用Looper.prepara以及Looper.loop(),但是切记线程任务执行完,需要手动调用Looper.quitSafely(),否则线程不会结束.
9.一个线程如何保证只有1个Looper
主要在于
Looper类
的prepare方法
,该方法的作用就是把looper对象
存到ThreadLocal中
1.首先利用ThreadLocal
把Looper对象
存到Thread对象的map
中
2.然后这个prepare方法
里面会做判断,判断ThradLocal
是否已经存有
Looper对象
,如果有则直接报错Only one Looper may be created per thread