前言
为什么要分析 Handler 源码,经历过Android 面试的都知道Handler 是面试必问的。但是不仅如此Handler也是其他组件不可或缺组件,如HandlerThread、 IntentService、以及Glide 生命周期的控制也有他的身影。
所以总而言之,言而总之 Handler实在是太重要了!!! 学习完这篇文章之后可以看下 Handler 应用
Handler的运行机制
面试的时候常问的一个问题,也是我们对Handler 原理不是很理解的时候经常遇到的问题:我们在主线程里面直接 new Handler()
之后可以直接使用它发送和处理事件,而在子线程缺不可以。
首先我们了解下Handler的工作流程
Handler的工作流程
通过上图我们可以总结出
- Looper 依赖于Thread,我们需要在线程中创建Looper。
Looper.prepare
- Looper需要在线程中运转,所以他得调用
Looper.loop()
那么回到上面那个问题,为什么主线程可以直接通过new Handler()
就能使用,而子线程却不行,接下来我们看下代码:
//ActivityThread.java
public static void main(String[] args) {
//省略代码...
Looper.prepareMainLooper();
//省略代码...
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");
}
复制代码
public static void loop() {
//省略代码...
final MessageQueue queue = me.mQueue;
//省略代码...
//死循环
for (;;) {
// 获取出队的msg
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
//省略代码...
//message 分发给对应的 handler 处理
msg.target.dispatchMessage(msg);
//省略代码...
//回收复用
msg.recycleUnchecked();
}
}
复制代码
- 一个app进程启动,需要zygote fork一个进程出来,而他执行的代码入口就是
ActivityThread.main
。 - 进程所执行的第一个线程就是我们的主进程。所以
Looper.prepareMainLooper()
创建了我们主线程的Looper Looper.loop()
启动了线程循环,也就是说我们后续所有在主线程运行的代码都是通过handler 处理的(这个很重要),如果主线程的Looper 结束循环,那么我们整个进程也就结束了
接着我们分别看下Looper.prepareMainLooper()
方法以及 Handler的构造函数做了什么
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
//1.创建Looper 存入到ThreadLocal中
sThreadLocal.set(new Looper(quitAllowed));
}
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
// 不能重复调用,否则抛异常,所以这个方法只能调一次
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
//2.从ThreadLocal中获取我们的主线程Looper
sMainLooper = myLooper();
}
}
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
//其他子线程可以调用
public static void prepare() {
prepare(true);
}
复制代码
public Handler() {
this(null, false);
}
public Handler(Callback callback, boolean async) {
//省略代码...
//3.获取当前线程的Looper
mLooper = Looper.myLooper();
//省略代码...
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
复制代码
上面代码中3步:
- 创建主线程Looper 存入到ThreadLocal中
- Looper保存了一个静态的 主线程Looper
- Handler 构造函数里会获取当前线程的Looper
分析到这里大家应该也就明白,为什么子线程不能像在主线程那样直接new Handler()
了,那么我如何在子线程正常使用Handler呢?
- 我们可以通过
new Handler(Looper.getMainLooper())
来使用主线程的Looper来创建Handler - 在子线程中执行Looper相关的必要方法,
Looper.prepare
和Looper.loop
不过这个会有个问题?最好的替代方案--使用HandlerThread。 这个问题我们先留着,我会在 Handler 其他应用文章中介绍 为什么使用HandlerThread
消息的传递(线程间通信)
Handler的核心就是为了线程间的通信,但是我们要知道一个线程一次只能处理一个,所以在并发的情况下就需要消息队列来保存的。
那么问题来了消息的处理又是按照遵循什么规则呢?先进先出?所以带着这个疑问我们进行代码的分析
消息的入队
消息的发送Handler主要提供了上图所示的方法,最终会执行MessageQueue.enqueueMessage
boolean enqueueMessage(Message msg, long when) {
//省略代码...
synchronized (this) {
//省略代码...
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
//1.p=null 表示队列为空 2.when 执行时间 如果为0或者比之前第一个message执行时间早,表示越早执行
//所以这两种情况都会将消息插入到链表头
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
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.
//如果存在阻塞、第一个消息是同步阻塞消息,并且当前消息是异步消息,先将唤醒标志为标记为true
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
//3.插入算法,循环遍历直到找到插入的前一个 message prev 和后一个message p
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
//如果P是异步消息,当前消息(即使是异步消息)执行时间在这个后面,将唤醒标志为标记为false
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// 4.如果需要唤醒 执行条件必须是满足1或2,并且当mBlocked = true时,mBlocked什么时候会为true 消息的出队会讲解
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
复制代码
- MessageQueue内部消息的存储是通过链表的方式,按照时间的优先执行顺序排列
- MessageQueue并没有对队列做限制,所以有多少消息度可以入队,无限存入会有内存溢出的风险。
- 那为我们可能会有疑问,为什么不用阻塞队列呢?如果队列满了,就不让他入队,大家都可以思考下这个问题
- 我们要知道主线程的Handler不仅仅我们在用,视图的刷新、Activity的启动等都是用到Handler的消息机制的。如果我们用阻塞队列,队列满了之后,系统消息也就进不去,那整个进程不像挂了吗
- 所以Handler 消息传递不能婪用
- 那为我们可能会有疑问,为什么不用阻塞队列呢?如果队列满了,就不让他入队,大家都可以思考下这个问题
消息的出队
我们知道MessageQueue在获取消息的时候会有阻塞的情况发生,阻塞主要分两种情况
- message 不到时间,自动唤醒 代码关键步骤执行流程 a1 -> b -> c
- messageQueue为空,无限等待 代码关键步骤执行流程 a2 -> b -> c
- 关于唤醒,我们可以看入队 代码的第4步
正常的流程就是将可执行的Menssage 直接返回
Message next() {
//省略代码...
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
//省略代码...
//c.执行阻塞,
//当nextPollTimeoutMillis = -1时无限期阻塞,只有通过新消息入队时唤醒
//当nextPollTimeoutMillis > 0 时,阻塞nextPollTimeoutMillis对应的时间自然唤醒,或者新消息执行时间早于当前最新消息的时间时,入队就会执行唤醒代码
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
//同步屏蔽,只有队列中第一个消息是同步屏蔽,才会去查找并执行异步消息
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
//a1. 下个消息还未准备就绪,距离执行还需多少时间
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
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 {
// No more messages.
//a2.没有消息 nextPollTimeoutMillis 赋值 -1
nextPollTimeoutMillis = -1;
}
//省略代码...
// If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
//第一次执行for 循环的时候,如果Handler没有消息要执行,则会,获取idle handlers 去执行
//只会执行一次下面的代码
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
//当没有mIdleHandlers 需要执行
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
//b. 标记阻塞
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
//省略代码...
// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}
复制代码
内存复用
内存复用的功能Message已经提供了,但是我们还是要思考下为什么需要内存复用?
我们试想下,如果我们不复用Message,我们就需要无限的new 一个新的Message,然后内存不足以new 一个新的Message,这时候就会回收再创建,回收即“stop the word”。这样就会产生内存的抖动,性能也因此受到了影响,
所以内存的复用他不仅能够避免内存抖动,也是性能优化的策略。类似Glide里面Bitmap复用也是基于这个原因设计内存复用。
内存复用采用享元的设计模式,接下来我们看下是如何回收、如何复用的
// Message链表 有next指向 下一个 可复用的Message
private static Message sPool;
// 在loop循环里当处理好message分发之后,会调用message.recycleUnchecked 进行回收
void recycleUnchecked() {
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details.
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = -1;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
//将message插入到链表前面
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
// 复用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();
}
复制代码
注意:创建Message对象的时候尽量使用obtain
同步屏障
同步屏障的概念,在Android中开发人员非常容易忽略,因为这个概念在我们普通的开发过程中太少见了,很容易被忽略。
大家经过上面的学习应该知道,线程的消息都是放到一个MessageQueue里面,取消息的时候是互斥取消息,而且只能从头部取消息,而添加的消息是按照消息的执行的先后顺序进行排序的,那么问题来了,同一个时间范围内的消息,如果它需要立即执行,那我们怎么办,我们需要等到队列轮询到我自己的时候才能执行,那岂不是黄花菜都凉了。所以我们需要给紧急需要执行的消息一个绿色通道,这个绿色通道就是同步屏障的概念。
同步屏障是什么?
屏障的意思即为阻碍,同步屏障就是阻碍同步消息,只让异步消息通过
开启同步屏障
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
private int postSyncBarrier(long when) {
// Enqueue a new sync barrier token.
// We don't need to wake the queue because the purpose of a barrier is to stall it.
synchronized (this) {
final int token = mNextBarrierToken++;
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
Message prev = null;
Message p = mMessages;
if (when != 0) {
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
return token;
}
}
复制代码
取消同步屏障
public void removeSyncBarrier(int token) {
// Remove a sync barrier token from the queue.
// If the queue is no longer stalled by a barrier then wake it.
synchronized (this) {
Message prev = null;
Message p = mMessages;
while (p != null && (p.target != null || p.arg1 != token)) {
prev = p;
p = p.next;
}
if (p == null) {
throw new IllegalStateException("The specified message queue synchronization "
+ " barrier token has not been posted or has already been removed.");
}
final boolean needWake;
if (prev != null) {
prev.next = p.next;
//说明同步屏障未执行,所以不需要唤醒,按正常逻辑执行
needWake = false;
} else {
mMessages = p.next;
//说明同步屏障已执行,并且下一个也不是同步屏障,所以需要唤醒,按正常逻辑执行
needWake = mMessages == null || mMessages.target != null;
}
p.recycleUnchecked();
// If the loop is quitting then it is already awake.
// We can assume mPtr != 0 when mQuitting is false.
if (needWake && !mQuitting) {
nativeWake(mPtr);
}
}
}
复制代码
异步消息的场景
在日常的开发过程中,很少会用到同步阻塞。这个基本上在Android的更新UI中用的比较多,优先级较高,需要优先处理。
例如,在View更新时,draw,requestLayout、invalidate等很多地方调用ViewRootImpl#scheduleTraversals
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
//开启同步阻塞
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//发送异步消息
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
复制代码
mChoreographer.postCallback
最终会执行Choreographer.postCallbackDelayedInternal
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
//省略代码...
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime <= now) {
scheduleFrameLocked(now);
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);//发送异步消息
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
复制代码
移除同步阻塞
void unscheduleTraversals() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
mChoreographer.removeCallbacks(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
}
复制代码
注意
同步屏障消息最好应用在短延时的消息 毫秒级的。因为我们同步屏障之后会让异步消息优先执行,他可能有延时,所以我们会阻塞线程,如果这个延时过长会产生时间上的浪费。比方说我延时了1s,但是在这1s内MessageQueue队列中排在异步消息之前的消息完全有时间消费掉,那么我们这个同步屏障就没有执行的意义了,还造成了时间层面的浪费。所以再次强调同步屏障消息最好应用在短延时的消息 毫秒级的。
总结
Handler的作用
- 使得Android开发难度大大降低
- 几乎看不到多线程死锁问题
- 方便了线程间的通信