Handler是什么?只是为了跨线程更新UI吗?它是怎么把消息从子线程传递到主线程的?消息管理机制又是怎样的?带着这些疑问开始阅读Handler模块源码
APP启动流程
Lancher:zygote ==> jvm ==> ActivityThread.main
下面是 ActivityThread 的 Main() 的部分代码
public static void main(String[] args) {
。。。
Looper.prepareMainLooper();
。。。
Looper.loop();
。。。
}
ActivityThread 中初始化 Looper 并开始 loop(),loop 内部是死循环,APP所有的代码都是在 handler 上运行的
Handler的工作流程
流程: handler.sendMessage() --> messageQueue.enqueueMessage() 把消息放到消息队列,消息队列插入节点(优先级队列,单链表) --> Looper.loop 内部有个死循环,调用messageQueue.next() 取走消息 --> messageQueue.next() 取走消息 --> msg.target.dispatchMessage(msg) 消息分发出去 target是handler --> handler.handleMessage()
handler 调用 sendMessage(或其他函数) 发送消息,最后都会走到 sendMessageAtTime() 函数
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);
}
进而调用 enqueueMessage 函数
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);
}
整体调用流程如下图
下面是 enqueueMessage 的具体实现
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
synchronized (this) {
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
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 {
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;
}
}
return true;
}
上面逻辑比较简单,是消息的入队操作。此处的数据结构是个优先级队列,以时间为顺序。
队列的好处?先进先出,取数据直接取头部
至此 Handler 发送的消息已经进入 MessageQueue 的队列中。那消息是怎么被取走的?还记得 Looper 的 loop() 函数吗
public static void loop() {
// 此处是死循环的地方,也是取消息
for (;;) {
// might block 此处可能会阻塞,比如 MessageQueue 为空;;不断的从 MessageQueue 中取走消息
Message msg = queue.next();
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
try {
// target 就是 handler ,调用 handler 的 dispatchMessage 函数
msg.target.dispatchMessage(msg);
if (observer != null) {
observer.messageDispatched(token, msg);
}
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
}
}
}
Loop函数的作用是不断的取走MessageQueue中的msg
Message msg = queue.next();
msg 中有个变量 target 存储着 handler,调用其 dispatchMessage 分发消息
msg.target.dispatchMessage(msg);
dispatchMessage函数实现
/**
* Handle system messages here.
*/
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
到这里就是我们熟悉的 handleMessage 了,整个流程到此结束。从上面流程不难发现,整个流程在动的只有Message,message只是一块内存,不分什么主线程或子线程,这也解释了消息如何从子线程发送到主线程,运用的内存共享机制。
Looper
核心:构造函数、loop函数、ThreadLocal
构造函数
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
Looper的构造函数是私有的,只能由prepare创建,如何保证每个线程最多只有一个Looper?ThreadLocal
// static 、 final 修饰,每个线程的Looper都存在这里
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
// 判断当前的线程是否已经存在,如果存在则抛出异常,不存在则创建当前线程的 Looper 并存入 sThreadLocal
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
线程 ===》 ThreadLocalMap ===》 <唯一的ThreadLocal,value> ThreadLocal 保证了 key 唯一
sThreadLocal.get() != null 判断保证了值唯一 ===》 保证 Looper 唯一,MessageQueue唯一
final MessageQueue mQueue;
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
Looper 保证唯一后,在 Looper 的构造函数中初始化 mQueue ,mQueue 由 final 修饰,初始化后唯一
如此:每个线程保证了最多只有一个 Looper,MessageQueue
loop函数 与 消息管理机制
消息的睡眠与唤醒机制理论上应该是个:生产者 - 消费者设计模式
入队操作:根据时间排序,当队列满的时候阻塞直到用户通过next取出消息。当next方法被调用,通知MessageQueue可以进行消息的入队
出队操作:由Looper.loop()启动轮询器,对queue进行轮询。当消息达到执行时间就取出来。当MessageQueue为空的时候,队列堵塞,等消息队列调用enqueueMessage的时候通知队列可以取出消息,停止阻塞
队列满了不能阻塞,但队列空了是可以阻塞的,这里可以分两种情况:
- 如果 next() 拿到的 msg 还没到执行时间 - - - 自动唤醒
// Looper.java loop函数
Message msg = queue.next(); // might block
// MessageQueue.java next()函数
Message next() {
···
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
// 第二次循环到此时,阻塞时间 nextPollTimeoutMillis
// 调用底层 native 函数 --- Looper.cpp epoll_wait(),阻塞线程
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
····
if (msg != null) {
// 第一次循环到此,如果当前的 msg 还没到执行时间,则计算出等待的时间,循环下一轮的时候开始等待
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
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. 没有消息了,开始无限等待
nextPollTimeoutMillis = -1;
}
}
}
}
loop 在调用 queue.next() 时,如果获取的msg还没到执行时间 if (now < msg.when) 计算出要等待的时间,在下次循环中调用nativePollOnce(ptr, nextPollTimeoutMillis) 实现阻塞
- 消息队列为空 - - - 无限等待
for (;;) {
// 第二次循环至此,开始无限等待
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
if (msg != null) {
。。。。
} else {
// No more messages. 没有消息了,开始无限等待
nextPollTimeoutMillis = -1;
}
}
阻塞机制的实现
翻看源码,我们可以得知阻塞由Linux层的epoll、wake实现
native层有对应的Looper、MessageQueue一套机制。与select相比,epoll 的时间复杂度是o(1),而select的时间复杂度是o(n)。
MessageQueue
MessageQueue的数据结构:优先级队列
MessageQueue是一个优先级队列
// MessageQueue.java 里
Message mMessages;
// Message.java 里
Message next; // 指向下一个msg
public long when; // 执行时间
Message 中有个 next 引用指向下一个msg,构成队列;msg 入队时根据执行时间排序;所以MessageQueue是个优先级队列
消息机制 之 同步屏障
同步屏障,怎么理解?什么地方应用?
当下很多手机都是60hz,即1秒刷新60次屏幕,即16ms刷新一次屏幕,所以所有与UI刷新的msg都需要优先处理!
先看下 MessageQueue 里的代码实现
// MessageQueue.java next()函数
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
// 找到 msg.isAsynchronous() == true 的 msg
} while (msg != null && !msg.isAsynchronous());
}
如果第一个 msg 的 target=null 则遍历队列,找到 msg.isAsynchronous(),找到后先执行
看个同步屏障的具体实现
ViewRootImpl.java
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
// handler 发送 同步屏障 刷新 UI
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 发送 同步屏障后, 发送异步消息
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
// mChoreographer.postCallback 最终会调用下面函数
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
if (DEBUG_FRAMES) {
Log.d(TAG, "PostCallback: type=" + callbackType
+ ", action=" + action + ", token=" + token
+ ", delayMillis=" + 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);
}
}
}
// 看下 postSyncBarrier 的实现
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;
// 此处没有设置 target,即 target = null,并直接放到队列头部
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;
}
}
// 取消同步屏障
void unscheduleTraversals() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
// 取消同步屏障
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
mChoreographer.removeCallbacks(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
}
整理下思路,屏幕点击后先发一个 target 为 null 的 msg 并放到 MessageQueue 的队头
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
然后再发一个 msg.isAsynchronous()=true 的消息
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
当 Looper 的 loop() 函数调用 queue.next() 时,因为首个 message 的 target==null ,遍历队列找到 msg.isAsynchronous()=true的消息并执行,这就是同步屏障!
Message 享元设计模式
Message的创建以及销毁是非常频繁的,如此频繁的操作不做优化很容易造成内存碎片化、内存抖动进而引发内存泄漏OOM
// 内存回收
public void recycle() {
if (isInUse()) {
if (gCheckRecycle) {
throw new IllegalStateException("This message cannot be recycled because it "
+ "is still in use.");
}
return;
}
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 = UID_NONE;
workSourceUid = UID_NONE;
when = 0;
target = null;
callback = null;
data = null;
// 维护了一个池子,用完清空数据并放进来
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
HandlerThread 存在的意义
1. 方便使用:a.方便初始化 b.方便获取线程Looper
2. 保证线程安全
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
// 唤醒其他等待的锁,但不会释放当前的锁,要执行完当前的代码块才会释放
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
public Looper getLooper() {
if (!isAlive()) {
return null;
}
// If the thread has been started, wait until the looper has been created.
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
// 释放锁
wait();
} catch (InterruptedException e) {
}
}
}
return mLooper;
}
安全体现在哪?举个栗子,run 函数 Looper.prepare(); 还没执行完有其他线程调用了 getLooper() 函数,判断 mLooper == null 调用 wait() 释放当前锁,run 函数继续执行,对 mLooper 进行初始化并调用 notifyAll() 函数唤醒下面的 getLooper() 函数,run 函数锁内的代码执行完成后 getLooper 内的代码继续执行,返回 mLooper !
两个点:wait() 会释放当前锁 ; notifyAll 唤醒其他线程但不会释放当前锁
IntentService 存在的意义
Service一般处理后台耗时任务
应用需求:一项任务分成几个子任务,子任务按顺序先后执行,子任务全部完成后这项任务才算成功
这个需求可以用多个多线程来处理,一个线程处理完 -> 下一个线程 -> 下一个线程
IntentService就可以帮我们完成这个工作。而且能够很好的管理线程,保证只有一个子线程处理工作,而且是一个一个的完成任务,有条不紊的进行
优点:
- 任务结束后调用 stopSelf(msg.arg1) 结束Service,释放内存
- 可以保证所有的任务都在一个线程中执行,HandlerThread保证
- 保证子任务按顺序依次执行,由MessageQueue保证,优先级队列
面试题
面试题1:为什么MessageQueue不设置Message的数量上限?
:Handler不只是开发者使用,系统也在使用!系统的消息进不去不完蛋了嘛~
面试题2:一个线程有几个Handler?
:随便new,想几个就几个
面试题3:一个线程有几个Looper?如何保证?
:一个线程只有一个Looper,以ThreadLocal保证,prepare() 判断sThreadLocal.get() != null
面试题4:Handler 内存泄漏原因?为什么其他的内部类没有这个问题
:内部类持有外部类对象;生命周期一致则不会内存泄漏
:handler 持有当前Activity,而 handler 发送的 msg 持有 handler,如果 msg 20分钟后执行,activity不能释放造成内存泄漏
// Handler.enqueueMessage()
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
// handler 调用 MessageQueue 的 enqueueMessage() 方法
return queue.enqueueMessage(msg, uptimeMillis);
}
// MessageQueue.enqueueMessage()
boolean enqueueMessage(Message msg, long when) {
·····
}
// Looper.loop()
public static void loop() {
// 此处的 target 就是 Handler
msg.target.dispatchMessage(msg);
}
面试题5:为何主线程可以 new Handler ? 如果想要在子线程中 new Handler 要做些什么准备?
// 子线程必须
Looper.prepare();
Looper.loop();
面试题6:子线程中维护的 Looper,消息队列无消息的时候处理方案是什么?有什么用?
:主线程阻塞,参见上面 MessageQueue 的两个阻塞;
:子线程 调用 quit 函数 - - - 1.唤醒线程 2.MessageQueue返回一个null 3.退出loop
Looper.myLooper().quit();
面试题7:既然可以存在多个Handler往MessageQueue中添加数据(发消息时各个Handler可能出事不同线程),内部如何确保线程安全?
:锁。synchronized 内置锁?JVM完成,自动完成 synchronized(this)类对象所有的代码块、函数都会锁住,enqueueMessage时next无法调用。也就说一个线程只有一个地方可以操作MessageQueue队列
PS:为啥取消息的时候也要加锁?因为取消息的时候可能在插入,容易混乱~同样quit时也要加锁
面试题8: Message是如何从子线程到主线程的?
:Message本身只是一块内存,不分线程。
:子线程 handler.sendMessage(msg) --> MessageQueue.enequeMessage(msg)
:主线程 轮询MessageQueue --> msg
面试题9:使用 Message 时该如何创建它?
:享元设计模式(内存复用) loop{ msg.recycleUnchecked(); 回收 } 维持一个池子,防止碎片太多,防止内存抖动,防止OOM
面试题10: Looper 的死循环为什么不会导致应用卡死?block为什么不会导致应用卡死?
:卡死即是ANR,Looper的死循环与 ANR 并没有关系,这两个是不相关的!
ARN原因:点击5s没响应;广播10s没响应;service 20s没相应
深层次剖析:点击事件封装成 msg 发出去,5s没处理完,handler 发出 ANR 提醒
block为啥不会导致 ANR ?—》线程没事做了交出CPU,休眠。有事做了会唤醒
面试题11:Handler什么时候会处于阻塞状态?如何唤醒?
:MessageQueue的队头msg未到执行时间,到了执行时间会自动唤醒
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
// 睡眠 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.
// 如果还没到执行时间,计算出需要等待的时间 nextPollTimeoutMillis ,下轮循环的时候睡眠对应的时长
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.
nextPollTimeoutMillis = -1;
}
}
}
// ptr为-1,表示无限等待;如果为 0 则无需等待
private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/
:MessageQueue为空进入无限等待,当MessageQueue执行enqueueMessage时会自动唤醒
// 在 MessageQueue.java 的 enqueueMessage 函数
if (needWake) {
nativeWake(mPtr);
}
看了又看,想了又想,读了又读,这就是源码的魅力吧!