如何应对Android面试官->Handler原理解析,玩转同步屏障
前言
本章主要进行 Handler 的原理解析,以及一些面试中常见的问题的解答
源码解析
解析之前,先来几个开胃小菜的问题
- 线程间如何通讯?
- 为什么线程间不会干扰?
- 为什么wait/notify用武之地不大?
- 系统给每一个应用分配一个虚拟机有什么好处?
我们根据源码来一探究竟;
当我们通过桌面 lanucher 启动一个 app 的时候,都发生了什么呢?
我们通过 launcher(app) 启动另一个 app 的时候, zygote 进程会 fork 一个进程给每一个应用,同时分配一个 jvm,既然是每一个进程都有对应的一个 jvm,而每一个 jvm 的启动都有一个对应的 main 函数,jvm 通过 main 函数来启动,而每一个 app 的 main 函数就在 ActivityThread 中,而每一个 jvm 都是数据隔离,内存隔离的,保证安全的同时还能自己挂掉了不影响其他应用;
我们进入这个 ActivityThread 的 main 方法看下:
核心的两个调用,Looper.prepareMainLooper() 和 Looper.loop() 这两个方法,通过 prepareMainLooper 和 loop 我们的主线程就跑起来了,我们进入 loop 看一下
一个 for(;;) 死循环,开启了我们的 loop;由这个死循环我们可以看到 Android 所有的代码都是在 handler 上运行的;
因为进入了死循环,那么 Android 通过消息的方式,将一个一个的状态分发出去;
当调用到 Looper.loop() 的时候,就会无限循环的从 MessageQueue 中取消息,而如果想停掉 loop 只能通过 从 MessageQueue 中取到一条空消息的时候,才能停掉;
Message msg = me.mQueue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return false;
}
那么什么时候才会返回一个为空的 msg 呢?
应用退出,应用退出的时候会发送一个 quit,quit 中具体做了什么,我们后面会讲解到;
public void quit() {
mQueue.quit(false);
}
我们接下来从 handler 的用法开始着手看起;我们在使用 handler 的时候,通过 sendMessage 来发送一条消息,然后在 handleMessage 中接收消息来实现通信,那么 sendMessage 发生了什么?又是怎么调度到 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);
}
这里直接调用了 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);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
而 enqueueMessage 方法调用了 MessageQueue 的 enqueueMessage 方法,最终将消息打到了 MessageQueue 中,将消息放到了消息队列;这里是消息入队的操作,那么消息又是怎么出队列的呢?
所以我们可以在 MessageQueue 中看到有一个 next 函数,从 MessageQueue 中取消息,返回的是一个 Message 对象;
Message next() {
// 省略部分代码
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
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 = (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;
}
// 省略部分代码
// ...
}
}
那么,谁来从 MessageQueue 中取消息呢?当然是 Looper 了;Looper 中的 loop 方法,它会调用 MessageQueue 的 next 方法;
public static void loop() {
// 省略部分代码
for (;;) {
if (!loopOnce(me, ident, thresholdOverride)) {
return;
}
}
}
在 loopOnce 中调用 next 方法
private static boolean loopOnce(final Looper me,
final long ident, final int thresholdOverride) {
// 从 MessageQueue 中取消息
Message msg = me.mQueue.next(); // might block
}
取出消息之后,再调用 message 的 target 的 dispatchMessage 方法
try {
msg.target.dispatchMessage(msg);
if (observer != null) {
observer.messageDispatched(token, msg);
}
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
}
而 msg.target 就是 handler,而 handler 的 dispatchMessage 最终调用到 handleMessage;
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
所以整体链路就是:handler.sendMessage() -> MessageQueue.enqueueMessage() //消息队列插入到节点,Looper.loop() -> MessageQueue.next() -> handler.handleMessage();
在整个过程中,一直操作的就是这个 Message,不管我们是通过 new Message() 来创建还是通过 handler.obtainMessage() 来创建,它对应都是一块内存,这样就形成了一块内存共享,它是从子线程到主线程的一个内存共享;
正是因为内存不分线程,所以这块内存,子线程可以使用,主线程同样可以使用;
所以子线程和主线程是怎么完成切换的?就是通过内存共享,子线程去创建了一个 Message 并通过 handler.sendMessage() 发送到 MessageQueue 中,主线程中从MessageQueue 中获取到这个 Message 对象,这样就完成了线程切换;
MessageQueue
消息队列,它是一个由单链表实现的优先级队列;
单向链表
我们来看下这个 MessageQueue 的 enqueueMessage 方法;
boolean enqueueMessage(Message msg, long 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;
}
}
Message p = mMessages;可以看到 MessageQueue 持有着一个 Message,我们来看下这个 Message 的构成;
public final class Message implements Parcelable {
// 省略部分代码
Message next;
}
可以看到 Message 中持有着一个 Message 类型的 next,整体结构就是:
MessageQueue -> Message mMessages -> Message next -> Message next ....
这样就形成了一个单向链表;
优先级队列
那么 MessageQueue 是怎么体现出来优先级队列的呢?我们来一探究竟;
handler 在 sendMessage 的时候,是可以传递一个时间的,我们还是通过 enqueueMessage 来看下 message 不为空的时候,是如何排序的
boolean enqueueMessage(Message msg, long when) {
// 省略部分代码
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.
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;
}
}
可以看到一个 for 循环,break 之后,插入一条消息,那么这个 for 循环是怎么执行的呢?我们来分析一下;
if (p == null || when < p.when) {
break;
}
when 就是我要执行的这个 message 的时间,p.when 的 p 就是我正在轮训的这个 message,这两个 message 的时间进行比较,如果要执行的这个 when 小于当前轮训的这个 message 的 when(时间小说明需要越早执行),则终止循环,并将这个要执行的 message 插入到当前轮训的这个 message 的前面;
按照执行时间进行了一个排序;
而队列怎么定义呢? 队列是先进先出,先进我们保证不了了,已经通过时间排序了,我们来看下能不能先出,我们进入 next 函数看下;
Message next() {
```
final long now = SystemClock.uptimeMillis();
// 省略部分代码
```
if (msg != null) {
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);
}
}
}
可以看到拿当前时间和发送时间进行对比,所以它一直都是获取的队列中的第一个 Message;
所以 MessageQueue 是一个按照时间排序的优先级队列;
Looper
我们接下来看下 Looper 的源码,Looper 的源码核心就在两个地方,一个是 构造方法,一个是 loop 方法;
构造方法
可以看到,Looper 的构造方法是私有的;
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
那么,Looper 是怎么初始化的呢?是由 prepare 来进行初始化的;
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));
}
一个线程只能有一个 ThreadLocal,通过 ThreadLocal 保证一个线程只能有一个 Looper;而 MessageQueue 来自 Looper,所以,一个线程只有一个 MessageQueue,并且 MessageQueue 是 final 的,一旦创建就不会改变了;
final MessageQueue mQueue;
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
/**
* Return the {@link MessageQueue} object associated with the current
* thread. This must be called from a thread running a Looper, or a
* NullPointerException will be thrown.
*/
public static @NonNull MessageQueue myQueue() {
return myLooper().mQueue;
}
所以说,不要说 MessageQueue 属于哪个线程,它是一个容器,只能说它是在哪里创建的(在哪个线程创建的);
线程 -> ThreadLocalMap<ThreadLocal, currentThread> Looper -> MessageQueue 这样的一个对应关系;
常见问题
一个线程有几个Handler?
多个 Handler;
一个线程有几个 Looper?如何保证?
一个 Looper,通过 ThreadLocal 保证只有一个;
Looper 中声明的 ThreadLcoal 是 static final 的;
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
所以一个Looper中的 ThreadLocal 也是唯一的;
Looper 的 prepare 方法中调用 ThreadLocal 的 set 方法,存储了一个 Looper;
sThreadLocal.set(new Looper(quitAllowed));
二次调用 prepare 的时候,会判读当前的 Threadlcoal是否为空,不为空抛异常,所以保证了一个线程只能有一个 looper;
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
ThreadLocal 的 set 方法中,获取当前线程,通过当前线程获取当前线程持有的 ThreadLocaMap,并将 Looper 存入这个 map 中,key 为 ThreadLocal;
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocal
- 线程上下文的存储变量;
- 可以看做一个 hashmap,只不过它的 hash 碰撞解决方法同普通的 hashmap不一样;
- 1.7hashmap 通过链表解决 hash 碰撞;
- 1.8hashmap 通过链表+红黑树解决 hash 碰撞;
MessageQueue
Looper 初始化的时候,会初始化一个 MessageQueue,messagequeue 也是 final 的,一旦被初始化,就不可以改变;
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
ThreadLoaclMap
每一个线程 都有自己的 ThreadLoaclMap
key: 唯一的 ThreadLocal,为什么唯一,因为 ThreadLocal 是声明为了 static final 类型的;
value:Looper;
线程1 -> ThreadLocalMap1 -> <唯一的ThreadLocal,value1> looper1 线程2 -> ThreadLocalMap2 -> <唯一的ThreadLocal,value2> looper2;
Handler 内存泄露?为什么其他的内部类没有说过这个问题?
handler 声明为匿名内部类的时候,会持有外部类的引用;
调用 handler 的 sendMessage 方法,内部最终调用的是enqueueMessage;
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
会将当前 handler 赋值给 message.target,导致 message 持有 Handler,message -> Handler -> Activity 这样的一个持有关系;
消息会被放入消息队列,那么 messagequeue 就会持有这条消息,MessageQueue -> Message -> Handler ->Activity;
运行中的线程不会被回收,如果这个线程 delay 执行的时间不确定,那么消息被执行的时间不确定,有可能1秒后执行,有可能1分钟后执行,也有可能5分钟后执行,一旦五分钟执行的话,那么在这个 MessageQueue 中的五分钟后被执行的消息都会在内存中被MessageQueue持有着。也就是说 message 被持有着,那么 Handler 就会被持有着,导致 Activity 就会被持有着。Activity 就不能被释放;
为何主线程可以 new handler?如果想要在子线程中 new handler(更新 UI) 要做些什么准备?
子线程创建的话需要调用 Looper.prepare() 和 Looper.loop();
子线程更新 UI 也可以,但是只能更新自己创建的 View 换句话说「Android 的 UI 更新被设计成了单线程」子线程更新 UI,需要创建自己的 Looper;
Handler是如何做到线程切换的?
- 内存共享,内存不分线程,同样也不分进程(Handler通信实现的核心方案)
- 子线程执行的函数,这个函数就在子线程中,所以 handler.sendMessage(msg) 这个函数从子线程中发出去,最终发送到 MessageQueue.enqueueMessage(msg)中,也就是说通过这个方法将 message 这块内存发送到MessageQueue 中了;
- 一个线程只有一个Looper,而一个Looper就只管理着一个 MessageQueue;
- 我们的Looper中的loop函数在主线程中不断的轮询 MessageQueue,所以从这个 MessageQueue 中取出的消息给到 dispatchMessage 处理,dispatchMessage 调用 handleMessage,而 handleMessage 函数在主线程中,所以这个 message 就在主线程中被处理了;
Handler消息同步屏障原理「保障同步消息优先执行」
消息是根据执行时间进行先后排序,然后消息是保存在消息队列中,因而消息只能从队列的对头取出来,那么需要紧急处理的消息怎么办?
需要紧急处理的消息,打上一个标签:msg.target = null 标记为同步屏障消息;
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());
}
当消息不为空,并且target == null的时候 轮询这个消息队列,找到同步「需要立即执行」的消息;
Looper 死循环为什么不会导致应用卡死?
主线程消息队列没有消息之后,主线程就会 sleep 但是之后为什么不会 anr? 因为应用卡死压根跟这个 Looper 没有关系,应用在没有消息需要处理的时候,它是在睡眠,释放线程;卡死是 ANR,而 Looper是睡眠;
anr 触发机制是事件5秒没有响应,或者广播10没有执行,service 20没有执行完毕; anr 异常是在哪里抛出的?点击事件封装成一个message来处理,是在Choreographer中的 doFrame方法中封装成message的,然后执行doCallback进行消息的处理 msg 在 5s 内没有被处理,超时之后,handler.sendMessage 就会发送一条 anr 提醒;
我们使用 message 时应该如何去创建它?
使用 obtain 从消息池中取已经创建不被使用的 Message 防止内存抖动,这是享元设计模式;
如果使用 new 的方式,那么就会频繁创建,频繁回收,造成内存抖动现象;频繁 GC 造成内存抖动,在内存中就会存在大量碎片,不连续,当创建比较占内存的对象(比如 bitmap)就容易造成 OOM;频繁 GC 也容易造成掉帧,界面卡顿;
既然可以存在多个 handler 往 messagequeue 中添加数据(发消息时各个 handler 可能处于不同线程),那么它内部是如何确保线程安全的?
通过 内部加锁,synchronized (this) {} 机制来保证;
针对多个线程往主线程的 MessageQueue 中发消息,那么 this 锁的就是当前主线程的 MessageQueue,又因为一个线程只能有一个 Looper,只能有一个 MessageQueue,所以能控制多线程访问,确保线程安全;
MessageQueue被加锁之后,那么这个对象里面的所有函数,代码块就都会受限;
好了,Handler 解析就到这里吧;
下一章预告
进程通信 Binder 原理解析;
欢迎三连
来到来了,点个关注点个赞吧,你的支持是我最大的动力~~
转载自:https://juejin.cn/post/7342420969879175219