跳到主要内容
Android Handler 机制深度解析:27 个核心问题详解 | 极客日志
Java 大前端 java
Android Handler 机制深度解析:27 个核心问题详解 综述由AI生成 Android Handler 机制是 Android 多线程通信的核心,负责线程切换与消息分发。通过 27 个关键问题深入解析 Handler 设计原理,涵盖 MessageQueue 链表结构、同步屏障与异步消息处理、ThreadLocal 绑定 Looper 机制、IdleHandler 空闲任务执行以及 Handler 内存泄漏成因等核心知识点。同时探讨了 HandlerThread、IntentService 的使用场景及 BlockCanary 卡顿检测原理,旨在帮助开发者全面掌握 Android 消息循环体系,优化应用性能并避免常见陷阱。
KernelLab 发布于 2025/2/7 更新于 2026/6/3 16 浏览前言
Handler 是 Android 消息机制的核心组件,主要用于跨线程通信。本文将通过 27 个常见问题,深入剖析其内部实现原理与最佳实践。
1、Handler 被设计出来的原因?有什么用?
一种东西被设计出来肯定就有它存在的意义,而 Handler 的意义就是切换线程。
作为 Android 消息机制的主要成员,它管理着所有与界面有关的消息事件,常见的使用场景有:
比如 Activity 的启动,就是 AMS 在进行进程间通信的时候,通过 Binder 线程将消息发送给 ApplicationThread 的消息处理者 Handler,然后再将消息分发给主线程中去执行。
当子线程网络操作之后,需要切换到主线程进行 UI 更新。
总之一句话,Handler 的存在就是为了解决在子线程中无法访问 UI 的问题。
2、为什么建议子线程不访问(更新)UI?
因为 Android 中的 UI 控件不是线程安全的,如果多线程访问 UI 控件那还不乱套了。
那为什么不加锁呢?
会降低 UI 访问的效率。本身 UI 控件就是离用户比较近的一个组件,加锁之后自然会发生阻塞,那么 UI 访问的效率会降低,最终反应到用户端就是这个手机有点卡。
太复杂了。本身 UI 访问时一个比较简单的操作逻辑,直接创建 UI,修改 UI 即可。如果加锁之后就让这个 UI 访问的逻辑变得很复杂,没必要。
所以,Android 设计出了单线程模型来处理 UI 操作,再搭配上 Handler,是一个比较合适的解决方案。
3、子线程访问 UI 的崩溃原因和解决办法?
崩溃发生在 ViewRootImpl 类的 checkThread 方法中:
void checkThread () {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException (
"Only the original thread that created a view hierarchy can touch its views." );
}
}
其实就是判断了当前线程是否是 ViewRootImpl 创建时候的线程,如果不是,就会崩溃。
而 ViewRootImpl 创建的时机就是界面被绘制的时候,也就是 onResume 之后,所以如果在子线程进行 UI 更新,就会发现当前线程(子线程)和 View 创建的线程(主线程)不是同一个线程,发生崩溃。
解决办法有三种:
在新建视图的线程进行这个视图的 UI 更新,主线程创建 View,主线程更新 View。
在 ViewRootImpl 创建之前进行子线程的 UI 更新,比如 onCreate 方法中进行子线程更新 UI。
子线程切换到主线程进行 UI 更新,比如 Handler、view.post 方法。
4、MessageQueue 是干嘛呢?用的什么数据结构来存储数据?
看名字应该是个队列结构,队列的特点是什么?先进先出,一般在队尾增加数据,在队首进行取数据或者删除数据。
那 Handler 中的消息似乎也满足这样的特点,先发的消息肯定就会先被处理。但是,Handler 中还有比较特殊的情况,比如延时消息。
延时消息的存在就让这个队列有些特殊性了,并不能完全保证先进先出,而是需要根据时间来判断,所以 Android 中采用了链表的形式来实现这个队列,也方便了数据的插入。
来一起看看消息的发送过程,无论是哪种方法发送消息,都会走到 sendMessageDelayed 方法
public {
(delayMillis < ) {
delayMillis = ;
}
sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
{
mQueue;
enqueueMessage(queue, msg, uptimeMillis);
}
final
boolean
sendMessageDelayed
(@NonNull Message msg, long delayMillis)
if
0
0
return
public
boolean
sendMessageAtTime
(@NonNull Message msg, long uptimeMillis)
MessageQueue
queue
=
return
sendMessageDelayed 方法主要计算了消息需要被处理的时间,如果 delayMillis 为 0,那么消息的处理时间就是当前时间。
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.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;
prev.next = msg;
}
if (needWake) {
nativeWake(mPtr);
}
}
return true ;
}
首先设置了 Message 的 when 字段,也就是代表了这个消息的处理时间
然后判断当前队列是不是为空,是不是即时消息,是不是执行时间 when 大于表头的消息时间,满足任意一个,就把当前消息 msg 插入到表头。
否则,就需要遍历这个队列,也就是链表,找出 when 小于某个节点的 when,找到后插入。
好了,其他内容暂且不看,总之,插入消息就是通过消息的执行时间,也就是 when 字段,来找到合适的位置插入链表。
具体方法就是通过死循环,使用快慢指针 p 和 prev,每次向后移动一格,直到找到某个节点 p 的 when 大于我们要插入消息的 when 字段,则插入到 p 和 prev 之间。或者遍历到链表结束,插入到链表结尾。
所以,MessageQueue 就是一个用于存储消息、用链表实现的特殊队列结构。
5、延迟消息是怎么实现的? 总结上述内容,延迟消息的实现主要跟消息的统一存储方法有关,也就是上文说过的 enqueueMessage 方法。
无论是即时消息还是延迟消息,都是计算出具体的时间,然后作为消息的 when 字段进程赋值。
然后在 MessageQueue 中找到合适的位置(安排 when 小到大排列),并将消息插入到 MessageQueue 中。
这样,MessageQueue 就是一个按照消息时间排列的一个链表结构。
6、MessageQueue 的消息怎么被取出来的? 刚才说过了消息的存储,接下来看看消息的取出,也就是 queue.next 方法。
Message next () {
for (;;) {
if (nextPollTimeoutMillis != 0 ) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this ) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null ;
Message msg = mMessages;
if (msg != null && msg.target == null ) {
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
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 ;
msg.markInUse();
return msg;
}
} else {
nextPollTimeoutMillis = -1 ;
}
}
}
}
其实死循环就是为了保证一定要返回一条消息,如果没有可用消息,那么就阻塞在这里,一直到有新消息的到来。
其中,nativePollOnce 方法就是阻塞方法,nextPollTimeoutMillis 参数就是阻塞的时间。
1、有消息,但是当前时间小于消息执行时间,也就是代码中的这一句:
if (now < msg.when ) {
nextPollTimeoutMillis = (int ) Math.min(msg.when - now, Integer.MAX_VALUE);
}
这时候阻塞时间就是消息时间减去当前时间,然后进入下一次循环,阻塞。
if (msg != null ) {}
else {
nextPollTimeoutMillis = -1 ;
}
7、MessageQueue 没有消息时候会怎样?阻塞之后怎么唤醒呢?说说 pipe/epoll 机制? 接着上文的逻辑,当消息不可用或者没有消息的时候就会阻塞在 next 方法,而阻塞的办法是通过 pipe/epoll 机制
epoll 机制是一种 IO 多路复用的机制,具体逻辑就是一个进程可以监视多个描述符,当某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作,这个读写操作是阻塞的。在 Android 中,会创建一个 Linux 管道(Pipe)来处理阻塞和唤醒。
当消息队列为空,管道的读端等待管道中有新内容可读,就会通过 epoll 机制进入阻塞状态。
当有消息要处理,就会通过管道的写端写入内容,唤醒主线程。
还记得刚才插入消息的 enqueueMessage 方法中有个 needWake 字段吗,很明显,这个就是表示是否唤醒的字段。
其中还有个字段是 mBlocked,看字面意思是阻塞的意思,去代码里面找找:
Message next () {
for (;;) {
synchronized (this ) {
if (msg != null ) {
if (now < msg.when ) {
nextPollTimeoutMillis = (int ) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
mBlocked = false ;
return msg;
}
}
if (pendingIdleHandlerCount <= 0 ) {
mBlocked = true ;
continue ;
}
}
}
}
在获取消息的方法 next 中,有两个地方对 mBlocked 赋值:
当获取到消息的时候,mBlocked 赋值为 false,表示不阻塞。
当没有消息要处理,也没有 idleHandler 要处理的时候,mBlocked 赋值为 true,表示阻塞。
好了,确实这个字段就表示是否阻塞的意思,再去看看 enqueueMessage 方法中,唤醒机制:
boolean enqueueMessage (Message msg, long when ) {
synchronized (this ) {
boolean needWake;
if (p == null || when == 0 || when < p.when ) {
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;
prev.next = msg;
}
if (needWake) {
nativeWake(mPtr);
}
}
return true ;
}
当链表为空或者时间小于表头消息时间,那么就插入表头,并且设置是否唤醒为 mBlocked。
再结合上述的例子,也就是当有新消息要插入表头了,这时候如果之前是阻塞状态(mBlocked=true),那么就要唤醒线程了。
否则,就需要取链表中找到某个节点并插入消息,在这之前需要赋值 needWake = mBlocked && p.target == null && msg.isAsynchronous()
也就是在插入消息之前,需要判断是否阻塞,并且表头是不是屏障消息,并且当前消息是不是异步消息。也就是如果现在是同步屏障模式下,那么要插入的消息又刚好是异步消息,那就不用管插入消息问题了,直接唤醒线程,因为异步消息需要先执行。
最后一点,是在循环里,如果发现之前就存在异步消息,那就还是设置是否唤醒为 false。
意思就是,如果之前有异步消息了,那肯定之前就唤醒过了,这时候就不需要再次唤醒了。
最后根据 needWake 的值,决定是否调用 nativeWake 方法唤醒 next() 方法。
8、同步屏障和异步消息是怎么实现的?
同步消息。也就是普通的消息。
异步消息。通过 setAsynchronous(true) 设置的消息。
同步屏障消息。通过 postSyncBarrier 方法添加的消息,特点是 target 为空,也就是没有对应的 handler。
正常情况下,同步消息和异步消息都是正常被处理,也就是根据时间 when 来取消息,处理消息。
当遇到同步屏障消息的时候,就开始从消息队列里面去找异步消息,找到了再根据时间决定阻塞还是返回消息。
Message msg = mMessages;
if (msg != null && msg.target == null ) {
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
也就是说同步屏障消息不会被返回,他只是一个标志,一个工具,遇到它就代表要去先行处理异步消息了。
所以同步屏障和异步消息的存在的意义就在于有些消息需要'加急处理'。
9、同步屏障和异步消息有具体的使用场景吗? 使用场景就很多了,比如绘制方法 scheduleTraversals。
void scheduleTraversals () {
if (!mTraversalScheduled) {
mTraversalScheduled = true ;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null );
}
}
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true );
mHandler.sendMessageAtTime(msg, dueTime);
在该方法中加入了同步屏障,后续加入一个异步消息 MSG_DO_SCHEDULE_CALLBACK,最后会执行到 FrameDisplayEventReceiver,用于申请 VSYNC 信号。
10、Message 消息被分发之后会怎么处理?消息怎么复用的? 再看看 loop 方法,在消息被分发之后,也就是执行了 dispatchMessage 方法之后,还偷偷做了一个操作——recycleUnchecked。
public static void loop () {
for (;;) {
Message msg = queue.next();
try {
msg.target.dispatchMessage(msg);
}
msg.recycleUnchecked();
}
}
private static Message sPool;
private static final int MAX_POOL_SIZE = 50 ;
void recycleUnchecked () {
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++;
}
}
}
在 recycleUnchecked 方法中,释放了所有资源,然后将当前的空消息插入到 sPool 表头。
这里的 sPool 就是一个消息对象池,它也是一个链表结构的消息,最大长度为 50。
那么 Message 又是怎么复用的呢?在 Message 的实例化方法 obtain 中:
public static Message obtain () {
synchronized (sPoolSync) {
if (sPool != null ) {
Message m = sPool;
sPool = m.next;
m.next = null ;
m.flags = 0 ;
sPoolSize--;
return m;
}
}
return new Message ();
}
直接复用消息池 sPool 中的第一条消息,然后 sPool 指向下一个节点,消息池数量减一。
11、Looper 是干嘛呢?怎么获取当前线程的 Looper?为什么不直接用 Map 存储线程和对象呢? 在 Handler 发送消息之后,消息就被存储到 MessageQueue 中,而 Looper 就是一个管理消息队列的角色。Looper 会从 MessageQueue 中不断的查找消息,也就是 loop 方法,并将消息交回给 Handler 进行处理。
而 Looper 的获取就是通过 ThreadLocal 机制:
static final ThreadLocal<Looper> sThreadLocal = new 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));
}
public static @Nullable Looper myLooper () {
return sThreadLocal.get();
}
通过 prepare 方法创建 Looper 并且加入到 sThreadLocal 中,通过 myLooper 方法从 sThreadLocal 中获取 Looper。
12、ThreadLocal 运行机制?这种机制设计的好处? 下面就具体说说 ThreadLocal 运行机制。
public T get () {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null ) {
ThreadLocalMap.Entry e = map.getEntry(this );
if (e != null ) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
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 类中的 get 和 set 方法可以大致看出来,有一个 ThreadLocalMap 变量,这个变量存储着键值对形式的数据。
key 为 this,也就是当前 ThreadLocal 变量。
value 为 T,也就是要存储的值。
然后继续看看 ThreadLocalMap 哪来的,也就是 getMap 方法:
ThreadLocalMap getMap (Thread t) {
return t.threadLocals;
}
ThreadLocal.ThreadLocalMap threadLocals = null ;
原来这个 ThreadLocalMap 变量是存储在线程类 Thread 中的。
所以 ThreadLocal 的基本机制就搞清楚了:
在每个线程中都有一个 threadLocals 变量,这个变量存储着 ThreadLocal 和对应的需要保存的对象。
这样带来的好处就是,在不同的线程,访问同一个 ThreadLocal 对象,但是能获取到的值却不一样。
挺神奇的是不是,其实就是其内部获取到的 Map 不同,Map 和 Thread 绑定,所以虽然访问的是同一个 ThreadLocal 对象,但是访问的 Map 却不是同一个,所以取得值也不一样。
这样做有什么好处呢?为什么不直接用 Map 存储线程和对象呢?
ThreadLocal 就是老师。
Thread 就是同学。
Looper(需要的值)就是铅笔。
现在老师买了一批铅笔,然后想把这些铅笔发给同学们,怎么发呢?两种办法:
1、老师把每个铅笔上写好每个同学的名字,放到一个大盒子里面去(map),用的时候就让同学们自己来找。
这种做法就是 Map 里面存储的是同学和铅笔,然后用的时候通过同学来从这个 Map 里找铅笔。
这种做法就有点像使用一个 Map,存储所有的线程和对象,不好的地方就在于会很混乱,每个线程之间有了联系,也容易造成内存泄漏。
2、老师把每个铅笔直接发给每个同学,放到同学的口袋里(map),用的时候每个同学从口袋里面拿出铅笔就可以了。
这种做法就是 Map 里面存储的是老师和铅笔,然后用的时候老师说一声,同学只需要从口袋里拿出来就行了。
很明显这种做法更科学,这也就是 ThreadLocal 的做法,因为铅笔本身就是同学自己在用,所以一开始就把铅笔交给同学自己保管是最好的,每个同学之间进行隔离。
13、还有哪些地方运用到了 ThreadLocal 机制? public final class Choreographer {
private static final ThreadLocal<Choreographer> sThreadInstance =
new ThreadLocal <Choreographer>() {
@Override
protected Choreographer initialValue () {
Looper looper = Looper.myLooper();
if (looper == null ) {
throw new IllegalStateException ("The current thread must have a looper!" );
}
Choreographer choreographer = new Choreographer (looper, VSYNC_SOURCE_APP);
if (looper == Looper.getMainLooper()) {
mMainInstance = choreographer;
}
return choreographer;
}
};
private static volatile Choreographer mMainInstance;
Choreographer 主要是主线程用的,用于配合 VSYNC 中断信号。
所以这里使用 ThreadLocal 更多的意义在于完成线程单例的功能。
14、可以多次创建 Looper 吗? Looper 的创建是通过 Looper.prepare 方法实现的,而在 prepare 方法中就判断了,当前线程是否存在 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));
}
private Looper (boolean quitAllowed) {
mQueue = new MessageQueue (quitAllowed);
mThread = Thread.currentThread();
}
所以同一个线程,只能创建一个 Looper,多次创建会报错。
15、Looper 中的 quitAllowed 字段是啥?有什么用? 按照字面意思就是是否允许退出,我们看看他都在哪些地方用到了:
void quit (boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException ("Main thread not allowed to quit." );
}
synchronized (this ) {
if (mQuitting) {
return ;
}
mQuitting = true ;
if (safe) {
removeAllFutureMessagesLocked();
} else {
removeAllMessagesLocked();
}
}
}
哦,就是这个 quit 方法用到了,如果这个字段为 false,代表不允许退出,就会报错。
但是这个 quit 方法又是干嘛的呢?从来没用过呢。还有这个 safe 又是啥呢?
其实看名字就差不多能了解了,quit 方法就是退出消息队列,终止消息循环。
首先设置了 mQuitting 字段为 true。
然后判断是否安全退出,如果安全退出,就执行 removeAllFutureMessagesLocked 方法,它内部的逻辑是清空所有的延迟消息,之前没处理的非延迟消息还是需要取处理,然后设置非延迟消息的下一个节点为空(p.next=null)。
如果不是安全退出,就执行 removeAllMessagesLocked 方法,直接清空所有的消息,然后设置消息队列指向空(mMessages = null)
然后看看当调用 quit 方法之后,消息的发送和处理:
boolean enqueueMessage (Message msg, long when ) {
synchronized (this ) {
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 ;
}
}
当调用了 quit 方法之后,mQuitting 为 true,消息就发不出去了,会报错。
Message next () {
for (;;) {
synchronized (this ) {
if (mQuitting) {
dispose();
return null ;
}
}
}
}
public static void loop () {
for (;;) {
Message msg = queue.next();
if (msg == null ) {
return ;
}
}
}
很明显,当 mQuitting 为 true 的时候,next 方法返回 null,那么 loop 方法中就会退出死循环。
主线程中,一般情况下肯定不能退出,因为退出后主线程就停止了。所以是当 APP 需要退出的时候,就会调用 quit 方法,涉及到的消息是 EXIT_APPLICATION,大家可以搜索下。
子线程中,如果消息都处理完了,就需要调用 quit 方法停止消息循环。
16、Looper.loop 方法是死循环,为什么不会卡死(ANR)?
1、主线程本身就是需要一只运行的,因为要处理各个 View,界面变化。所以需要这个死循环来保证主线程一直执行下去,不会被退出。
2、真正会卡死的操作是在某个消息处理的时候操作时间过长,导致掉帧、ANR,而不是 loop 方法本身。
3、在主线程以外,会有其他的线程来处理接受其他进程的事件,比如 Binder 线程(ApplicationThread),会接受 AMS 发送来的事件
4、在收到跨进程消息后,会交给主线程的 Handler 再进行消息分发。所以 Activity 的生命周期都是依靠主线程的 Looper.loop,当收到不同 Message 时则采用相应措施,比如收到 msg=H.LAUNCH_ACTIVITY,则调用 ActivityThread.handleLaunchActivity() 方法,最终执行到 onCreate 方法。
5、当没有消息的时候,会阻塞在 loop 的 queue.next() 中的 nativePollOnce() 方法里,此时主线程会释放 CPU 资源进入休眠状态,直到下个消息到达或者有事务发生。所以死循环也不会特别消耗 CPU 资源。
17、Message 是怎么找到它所属的 Handler 然后进行分发的? 在 loop 方法中,找到要处理的 Message,然后调用了这么一句代码处理消息:
msg.target.dispatchMessage(msg);
所以是将消息交给了 msg.target 来处理,那么这个 target 是啥呢?
private boolean enqueueMessage (MessageQueue queue,Message msg,long uptimeMillis) {
msg.target = this ;
return queue.enqueueMessage(queue, msg, uptimeMillis);
}
在使用 Hanlder 发送消息的时候,会设置 msg.target = this,所以 target 就是当初把消息加到消息队列的那个 Handler。
18、Handler 的 post(Runnable) 与 sendMessage 有什么区别
post(Runnable)
sendMessage
public final boolean post (@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0 );
}
private static Message getPostMessage (Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
通过 post 的源码可知,其实 post 和 sendMessage 的区别就在于:
post 方法给 Message 设置了一个 callback。
那么这个 callback 有什么用呢?我们再转到消息处理的方法 dispatchMessage 中看看:
public void dispatchMessage (@NonNull Message msg) {
if (msg.callback != null ) {
handleCallback(msg);
} else {
if (mCallback != null ) {
if (mCallback.handleMessage(msg)) {
return ;
}
}
handleMessage(msg);
}
}
private static void handleCallback (Message message) {
message.callback.run();
}
1、如果 msg.callback 不为空,也就是通过 post 方法发送消息的时候,会把消息交给这个 msg.callback 进行处理,然后就没有后续了。
2、如果 msg.callback 为空,也就是通过 sendMessage 发送消息的时候,会判断 Handler 当前的 mCallback 是否为空,如果不为空就交给 Handler.Callback.handleMessage 处理。
3、如果 mCallback.handleMessage 返回 true,则无后续了。
4、如果 mCallback.handleMessage 返回 false,则调用 handler 类重写的 handleMessage 方法。
所以 post(Runnable) 与 sendMessage 的区别就在于后续消息的处理方式,是交给 msg.callback 还是 Handler.Callback 或者 Handler.handleMessage。
19、Handler.Callback.handleMessage 和 Handler.handleMessage 有什么不一样?为什么这么设计? 接着上面的代码说,这两个处理方法的区别在于 Handler.Callback.handleMessage 方法是否返回 true:
如果为 true,则不再执行 Handler.handleMessage
如果为 false,则两个方法都要执行。
那么什么时候有 Callback,什么时候没有呢?这涉及到两种 Handler 的 创建方式:
val handler1= object : Handler(){
override fun handleMessage (msg: Message ) {
super .handleMessage(msg)
}
}
val handler2 = Handler(object : Handler.Callback {
override fun handleMessage (msg: Message ) : Boolean {
return true
}
})
常用的方法就是第 1 种,派生一个 Handler 的子类并重写 handleMessage 方法。而第 2 种就是系统给我们提供了一种不需要派生子类的使用方法,只需要传入一个 Callback 即可。
20、Handler、Looper、MessageQueue、线程是一一对应关系吗?
一个线程只会有一个 Looper 对象,所以线程和 Looper 是一一对应的。
MessageQueue 对象是在 new Looper 的时候创建的,所以 Looper 和 MessageQueue 是一一对应的。
Handler 的作用只是将消息加到 MessageQueue 中,并后续取出消息后,根据消息的 target 字段分发给当初的那个 handler,所以 Handler 对于 Looper 是可以多对一的,也就是多个 Handler 对象都可以用同一个线程、同一个 Looper、同一个 MessageQueue。
总结:Looper、MessageQueue、线程是一一对应关系,而他们与 Handler 是可以一对多的。
21、ActivityThread 中做了哪些关于 Handler 的工作?(为什么主线程不需要单独创建 Looper)
1、在 main 方法中,创建了主线程的 Looper 和 MessageQueue,并且调用 loop 方法开启了主线程的消息循环。
public static void main (String[] args) {
Looper.prepareMainLooper();
if (sMainThreadHandler == null ) {
sMainThreadHandler = thread.getHandler();
}
Looper.loop();
throw new RuntimeException ("Main thread loop unexpectedly exited" );
}
2、创建了一个 Handler 来进行四大组件的启动停止等事件处理
final H mH = new H ();
class H extends Handler {
public static final int BIND_APPLICATION = 110 ;
public static final int EXIT_APPLICATION = 111 ;
public static final int RECEIVER = 113 ;
public static final int CREATE_SERVICE = 114 ;
public static final int STOP_SERVICE = 116 ;
public static final int BIND_SERVICE = 121 ;
22、IdleHandler 是啥?有什么使用场景? 之前说过,当 MessageQueue 没有消息的时候,就会阻塞在 next 方法中,其实在阻塞之前,MessageQueue 还会做一件事,就是检查是否存在 IdleHandler,如果有,就会去执行它的 queueIdle 方法。
private IdleHandler[] mPendingIdleHandlers;
Message next () {
int pendingIdleHandlerCount = -1 ;
for (;;) {
synchronized (this ) {
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when )) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (mPendingIdleHandlers == null ) {
mPendingIdleHandlers = new IdleHandler [Math.max(pendingIdleHandlerCount, 4 )];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
for (int i = 0 ; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null ;
boolean keep = false ;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception" , t);
}
if (!keep) {
synchronized (this ) {
mIdleHandlers.remove(idler);
}
}
}
pendingIdleHandlerCount = 0 ;
}
}
当没有消息处理的时候,就会去处理这个 mIdleHandlers 集合里面的每个 IdleHandler 对象,并调用其 queueIdle 方法。最后根据 queueIdle 返回值判断是否用完删除当前的 IdleHandler。
然后看看 IdleHandler 是怎么加进去的:
Looper.myQueue().addIdleHandler(new IdleHandler () {
@Override
public boolean queueIdle () {
return false ;
}
});
public void addIdleHandler (@NonNull IdleHandler handler) {
if (handler == null ) {
throw new NullPointerException ("Can't add a null IdleHandler" );
}
synchronized (this ) {
mIdleHandlers.add(handler);
}
}
ok,综上所述,IdleHandler 就是当消息队列里面没有当前要处理的消息了,需要堵塞之前,可以做一些空闲任务的处理。
我们一般会把一些事件(比如界面 view 的绘制、赋值)放到 onCreate 方法或者 onResume 方法中。但是这两个方法其实都是在界面绘制之前调用的,也就是说一定程度上这两个方法的耗时会影响到启动时间。
所以我们可以把一些操作放到 IdleHandler 中,也就是界面绘制完成之后才去调用,这样就能减少启动时间了。
如果使用不当,IdleHandler 会一直不执行,比如在 View 的 onDraw 方法里面无限制的直接或者间接调用 View 的 invalidate 方法。
其原因就在于 onDraw 方法中执行 invalidate,会添加一个同步屏障消息,在等到异步消息之前,会阻塞在 next 方法,而等到 FrameDisplayEventReceiver 异步任务之后又会执行 onDraw 方法,从而无限循环。
23、HandlerThread 是啥?有什么使用场景? public class HandlerThread extends Thread {
@Override
public void run () {
Looper.prepare();
synchronized (this ) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
}
哦,原来如此。HandlerThread 就是一个封装了 Looper 的 Thread 类。
就是为了让我们在子线程里面更方便的使用 Handler。
这里的加锁就是为了保证线程安全,获取当前线程的 Looper 对象,获取成功之后再通过 notifyAll 方法唤醒其他线程,那哪里调用了 wait 方法呢?
public Looper getLooper () {
if (!isAlive()) {
return null ;
}
synchronized (this ) {
while (isAlive() && mLooper == null ) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
return mLooper;
}
就是 getLooper 方法,所以 wait 的意思就是等待 Looper 创建好,那边创建好之后再通知这边正确返回 Looper。
24、IntentService 是啥?有什么使用场景? public abstract class IntentService extends Service {
private final class ServiceHandler extends Handler {
public ServiceHandler (Looper looper) {
super (looper);
}
@Override
public void handleMessage (Message msg) {
onHandleIntent((Intent)msg.obj);
stopSelf(msg.arg1);
}
}
@Override
public void onCreate () {
super .onCreate();
HandlerThread thread = new HandlerThread ("IntentService[" + mName + "]" );
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler (mServiceLooper);
}
@Override
public void onStart (@Nullable Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
首先,这是一个 Service
并且内部维护了一个 HandlerThread,也就是有完整的 Looper 在运行。
还维护了一个子线程的 ServiceHandler。
启动 Service 后,会通过 Handler 执行 onHandleIntent 方法。
完成任务后,会自动执行 stopSelf 停止当前 Service。
所以,这就是一个可以在子线程进行耗时任务,并且在任务执行后自动停止的 Service。
25、BlockCanary 使用过吗?说说原理 BlockCanary 是一个用来检测应用卡顿耗时的三方库。
上文说过,View 的绘制也是通过 Handler 来执行的,所以如果能知道每次 Handler 处理消息的时间,就能知道每次绘制的耗时了?那 Handler 消息的处理时间怎么获取呢?
public static void loop () {
for (;;) {
Printer logging = me.mLogging;
if (logging != null ) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
msg.target.dispatchMessage(msg);
if (logging != null ) {
logging.println("<<<<<<< Finished to " + msg.target + " " + msg.callback);
}
}
}
可以发现,loop 方法内有一个 Printer 类,在 dispatchMessage 处理消息的前后分别打印了两次日志。
那我们把这个日志类 Printer 替换成我们自己的 Printer,然后统计两次打印日志的时间不就相当于处理消息的时间了?
Looper.getMainLooper().setMessageLogging(mainLooperPrinter);
public void setMessageLogging (@Nullable Printer printer) {
mLogging = printer;
}
26、说说 Handler 内存泄露问题。 这也是常常被问的一个问题,Handler 内存泄露的原因是什么?
"内部类持有了外部类的引用,也就是 Handler 持有了 Activity 的引用,从而导致无法被回收呗。"
我们必须找到那个最终的引用者,不会被回收的引用者,其实就是主线程,这条完整引用链应该是这样:
主线程 —> threadlocal —> Looper —> MessageQueue —> Message —> Handler —> Activity
27、利用 Handler 机制设计一个不崩溃的 App? 主线程崩溃,其实都是发生在消息的处理内,包括生命周期、界面绘制。
所以如果我们能控制这个过程,并且在发生崩溃后重新开启消息循环,那么主线程就能继续运行。
Handler(Looper.getMainLooper()).post {
while (true ) {
try {
Looper.loop()
} catch (e: Throwable) {
}
}
}
总结 大家应该可以发现,有一个问题常被问,但是全篇都没有提,那就是:
之所以不提这个问题,是因为要回答好这个问题需要大量知识储备,希望屏幕前的你在读完这篇之后,再结合自己的知识库,形成自己的完美答案。
相关免费在线工具 Keycode 信息 查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
Escape 与 Native 编解码 JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
JavaScript / HTML 格式化 使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
JavaScript 压缩与混淆 Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online