Android高工(三)——Handler消息机制
一、Handler的简介
官方文档解释如下:
A Handler allows you to send and process Message and Runnable objects associated with a thread's MessageQueue. Each Handler instance is associated with a single thread and that thread's message queue. When you create a new Handler it is bound to a Looper. It will deliver messages and runnables to that Looper's message queue and execute them on that Looper's thread. There are two main uses for a Handler: (1) to schedule messages and runnables to be executed at some point in the future; and (2) to enqueue an action to be performed on a different thread than your own.
官方文档的介绍中,主要解释了Handler的意义以及它在实际开发中的作用,大体如下:
Handler作为Android的消息机制,设计它的主要意义在于:
(1)搭配与线程绑定的消息队列和Looper轮询器,可以发送消息或者Runnable对象到与线程绑定的消息队列,并在looper中取出的消息后执行这个消息对应的操作(简单来说就是发送消息,处理消息,例如主线程的启动广播、Service等都是通过它来实现的)
(2)可以进行线程切换,通过Handler来处理子线程到主线程的切换,这也是Android提供的用于线程之间通信的方式,尤其是子线程与主线程之间
二、Handler消息机制的应用
1、主线程的系统消息
对于主线程中,有许多Application、四大组件等相关的生命周期的系统消息
public static final int BIND_APPLICATION = 110;
@UnsupportedAppUsage
public static final int EXIT_APPLICATION = 111;
@UnsupportedAppUsage
public static final int RECEIVER = 113;
@UnsupportedAppUsage
public static final int CREATE_SERVICE = 114;
@UnsupportedAppUsage
public static final int SERVICE_ARGS = 115;
@UnsupportedAppUsage
public static final int STOP_SERVICE = 116;
public static final int CONFIGURATION_CHANGED = 118;
public static final int CLEAN_UP_CONTEXT = 119;
@UnsupportedAppUsage
public static final int GC_WHEN_IDLE = 120;
@UnsupportedAppUsage
public static final int BIND_SERVICE = 121;
@UnsupportedAppUsage
public static final int UNBIND_SERVICE = 122;
public static final int DUMP_SERVICE = 123;
public static final int LOW_MEMORY = 124;
public static final int PROFILER_CONTROL = 127;
public static final int CREATE_BACKUP_AGENT = 128;
public static final int DESTROY_BACKUP_AGENT = 129;
public static final int SUICIDE = 130;
@UnsupportedAppUsage
public static final int REMOVE_PROVIDER = 131;
public static final int DISPATCH_PACKAGE_BROADCAST = 133;
@UnsupportedAppUsage
public static final int SCHEDULE_CRASH = 134;
public static final int DUMP_HEAP = 135;
public static final int DUMP_ACTIVITY = 136;
public static final int SLEEPING = 137;
public static final int SET_CORE_SETTINGS = 138;
public static final int UPDATE_PACKAGE_COMPATIBILITY_INFO = 139;
@UnsupportedAppUsage
public static final int DUMP_PROVIDER = 141;
public static final int UNSTABLE_PROVIDER_DIED = 142;
public static final int REQUEST_ASSIST_CONTEXT_EXTRAS = 143;
public static final int TRANSLUCENT_CONVERSION_COMPLETE = 144;
@UnsupportedAppUsage
public static final int INSTALL_PROVIDER = 145;
public static final int ON_NEW_ACTIVITY_OPTIONS = 146;
@UnsupportedAppUsage
public static final int ENTER_ANIMATION_COMPLETE = 149;
public static final int START_BINDER_TRACKING = 150;
public static final int STOP_BINDER_TRACKING_AND_DUMP = 151;
public static final int LOCAL_VOICE_INTERACTION_STARTED = 154;
public static final int ATTACH_AGENT = 155;
public static final int APPLICATION_INFO_CHANGED = 156;
public static final int RUN_ISOLATED_ENTRY_POINT = 158;
public static final int EXECUTE_TRANSACTION = 159;
public static final int RELAUNCH_ACTIVITY = 160;
public static final int PURGE_RESOURCES = 161;
public static final int ATTACH_STARTUP_AGENTS = 162;
3、子线程与主线程的通信/线程切换
例如Glide框架中,子线程加载图片后,在主线程上设置ImageView的bitmap
或者是我们创建子线程,创建一个Handler,传入主线程的消息队列,然后发送消息
三、Handler消息机制源码分析
1、Message消息
Message消息作为Handler通信的数据载体,实现了Parcelable接口,即可以执行序列化和反序列化操作
源码如下:
public final class Message implements Parcelable {
// 消息的code参数,作为消息标识
public int what;
// arg1和arg2用于传递int型参数
public int arg1;
public int arg2;
// 传递object对象
public Object obj;
// 发送消息的时间
@UnsupportedAppUsage
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public long when;
// 传递bundle对象
/*package*/ Bundle data;
// 指定发送消息的target,用于后面分发消息时判断分发给哪个Handler
@UnsupportedAppUsage
/*package*/ Handler target;
// 传递Runnable对象
@UnsupportedAppUsage
/*package*/ Runnable callback;
// 表示同步消息与异步消息的Flag
/*package*/ int flags;
// Message对象池的最大容量
private static final int MAX_POOL_SIZE = 50;
// 一个空的构造方法
public Message() {
}
// 从对象池获取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();
}
// 是否为系统的异步消息,异步消息利用同步屏障来进行优先执行
public boolean isAsynchronous() {
return (flags & FLAG_ASYNCHRONOUS) != 0;
}
(1)可传递的参数类型
可以传递Int型、object对象、Bundle对象、Runnable对象
(2)消息对象池
在Message中维护了一个Message对象池,消息队列本质上是一个单链表,在Message通过Message next来表示指向下个Message的指针
这个消息池最大消息个数为50个(以API 30为例)
private static final int MAX_POOL_SIZE = 50;
我们日常使用中,建议从消息池中获取消息对象
Message msg = Message.obtain()
那么消息池的消息是如何维护的呢?
首先一个线程中只会有一个消息队列,而其他的多个线程可能都会向这个线程插入消息,那么在获取消息对象的时候就需要考虑线程同步问题
这里是定义一个Object对象作为同步对象
public static final Object sPoolSync = new Object();
在obtain方法中,使用这个对象作为同步的对象体
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();
}
当spool对象为空时,表示此时Message对象池中没有对象,那么就new 一个Message对象
如果不为空,那么取出spool指向的Message对象,将spool指向取出对象的next对象上,同时对象池大小-1
那么spool是哪里赋值呢?
我们从Looper的loop方法中来查找,在执行消息结束后,会调用Message.recycleUnchecked()方法
/**
* Recycles a Message that may be in-use.
* Used internally by the MessageQueue and Looper when disposing of queued Messages.
*/
@UnsupportedAppUsage
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) {
// 将spool的值赋值给next
next = sPool;
// 将当前对象赋值给spool
sPool = this;
sPoolSize++;
}
}
}
即在消息执行完毕后,当前的spool指针会指向该对象,以供下一次使用
回答上面的问题,为什么要使用obtain而不是直接new对象
首先明确一点new对象后,必然会产生一个新的对象,而这个对象最终也会加入到对象池中;
而使用obtain方法, 如果对象池中存在可使用的对象,那么就可以直接使用
(3)消息类型
Message的消息类型主要有三类:
- 同步消息 ,target为Handler对象,isAsynchronous为false
- 异步消息(一般为系统的消息,例如view的绘制等,这类消息一般要搭配屏障消息,保证优先执行),异步消息target为null,isAsynchronous()为true
- 同步屏障消息(本质上也是同步消息,只是用于搭配异步消息使用,队列中按时间排序的消息队列中异步消息优先执行),同步屏障消息target为null,isAsynchronous为false
2、消息队列MessageQueue
消息队列MessageQueue作为Looper对象的属性存在,它的作用就是作为一个消息队列(单链表),可以执行消息插入和取出Message对象
(1)插入消息enqueueMessage()
插入消息本质上就是将插入的消息添加到当前消息队列的末尾,并指定消息的执行时间
插入的消息包括同步消息和异步消息
插入同步消息,一般是我们向Handler中调用sendMessage插入
插入异步消息,则是Android源码中针对UI绘制等场景插入,例如:Choreographer中
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);
}
}
}
boolean enqueueMessage(Message msg, long when) {
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;
// 根据执行时间,调整队列的头节点,保证头节点是最早执行的,在取出消息时也是从头节点开始取出的
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;
// 遍历找到当前消息队列的末尾元素,将末尾元素的next指向插入的消息msg,msg的next指向空
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;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
// 用于欢迎next方法取出消息的无限循环
// 只有当当前next已经阻塞,且队列中没有其他消息时才会调用唤醒方法
nativeWake(mPtr);
}
}
return true;
}
从插入消息的源码中我们可以看到,主要是根据消息的执行时间对单链表进行了调整,头节点总是执行时间最早的,从头节点向后遍历,执行时间点依次增加或者是相等
即,假设msg1基于当前时间点延时为0,msg2延时0,msg3延时3,msg4延时5,按照msg1,msg2,msg3,msg4依次插入,则消息队列中结构如下:

(2)插入同步屏障消息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;
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;
}
}
它的作用就是将同步屏障消息插入到消息队列的头部
(2)取出消息next()
next取出消息中,我们需要特别注意同步屏障消息、同步消息、异步消息的取出逻辑
@UnsupportedAppUsage
Message next() {
// Return here if the message loop has already quit and been disposed.
// This can happen if the application tries to restart a looper after quit
// which is not supported.
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
// 等待取出下个消息的时间
int nextPollTimeoutMillis = 0;
for (;;) {
// 如果下个消息的执行时间不等于0,表示暂时没有任务处理,那么当前线程将进入阻塞,此时需要将还未执行完的其他任务尽快处理完
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
// 如果nextpollTimeoutMillis不为0,则阻塞当前线程等待下个消息的执行
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;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
.......
}
在next方法中,比较重要的主要有两点:
1>无限for循环
for (;;) {
...
}
这里执行了一个无限的for循环,用于不断获取队列中是否有需要执行的消息
2>取出异步消息
当我们调用postSyncBarrier插入了同步屏障消息时,此时消息队列的头部就是同步屏障消息(当然插入多个同步屏障消息,它也是按照时间排序的),同步屏障消息的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());
}
(2)阻塞与唤醒
1> nativePollOnce
该方法 主要用于执行阻塞,其中支持传入超时时间,
具体如下:
- 0 立即返回,没有阻塞
- 负数 一直阻塞,直到事件发生,对于MessageQueue则是在enqueueMessage时调用nativewake唤醒,取消阻塞
- 正数,最多等待时间
当队列中存在消息,如果当前消息还未到执行时间,那么调用nativePollOnce(time)阻塞对应的时间
如果当前消息到达了执行时间,那么调用nativePollOnce(0),不执行阻塞,立即返回
当队列中不存在消息,那么调用nativePollOnce(-1),一直阻塞next方法,等待后续消息到来唤醒
它的本质是linux中的epoll机制,即在当前线程中释放CPU资源进入休眠,当等到时间到来后或者是有读写(例如调用nativewake)任务被唤醒后才会继续工作
2> nativeWake
该方法主要用于唤醒阻塞的nativePollOnce方法,从而继续取出消息
它的本质是通过pipe管道写入字符,唤醒休眠等待中的事件
在MessageQueue中,使用nativeWake的来唤醒next方法中的阻塞,主要场景有:
当消息队列中没有任何消息并且IdleHandler个数也为空,那么此时通过nativePollOnce(-1)一直阻塞当前线程,当enqueue插入消息时,就会调用nativeWake取消阻塞,唤醒next方法执行
当消息队列中头部消息为延时消息,则nativePollOnce阻塞一段时间,这个时间就是当前时间与消息执行时间的差值,如果此时在头部插入一条立即执行的消息,则会调用调用nativeWake取消阻塞
例如,Hnadler退出时,电泳nativewake唤醒消息队列,此时消息队列上消息为空,直接return
(3)阻塞for循环是否会导致ANR?
对于这个疑问,需要从两个角度来分析
1> ANR产生的原因
首先ANR产生的原因是我们在主线程执行了耗时操作,而为了保护主线程的系统消息的正常执行,因此对于Activity、Service、BroadCast中都增加了超时消息的机制,判断主线程可以在正常范围内执行完,如果没有执行完则触发ANR
这里以Service的ANR来分析
void scheduleServiceTimeoutLocked(ProcessRecord proc) {
if (proc.executingServices.size() == 0 || proc.thread == null) {
return;
}
Message msg = mAm.mHandler.obtainMessage(
ActivityManagerService.SERVICE_TIMEOUT_MSG);
msg.obj = proc;
mAm.mHandler.sendMessageDelayed(msg,
proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
}
// ActiveServices
// 前台服务的ANR时间
static final int SERVICE_TIMEOUT = 20*1000;
// 后台服务的ANR时间
static final int SERVICE_BACKGROUND_TIMEOUT = SERVICE_TIMEOUT * 10;
Service启动后,ASM的HandlerThread会发送定时消息,前台的延时为20s,后台的延时200s,以前台为例,它会计算Service的执行时间,如果Service的执行时间超过了20s,那么这个延时消息就会发送出去从而产生ANR
2> 主线程的消息驱动
对于主线程而言,它负责绘制UI和各种事件,例如输入响应、帧渲染回调等,都会发送消息到主线程的MessageQueue,这类型的系统消息会唤醒阻塞,执行绘制等事件处理
假设一直处于阻塞状态,那么说明主线程实际上没有任何任务处理,在等待下一个事件处理,因此无需担心ANR
这个问题的核心在与MessageQueue的enqueue方法会根据消息执行时间以及是否同步异步消息来唤醒nativePollOnce,并不是发送一个延时20s的消息就会导致Activity等无法正常执行绘制等任务的执行
同步屏障与异步消息的存在,会保护我们的系统消息及时执行
3、Looper
Looper,翻译过来为轮询器,主要作用就是创建对应线程的消息队列,并启动轮询
(1)初始化prepare()
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));
}
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
- quitAllowed: 表示当前消息队列是否可以退出,例如主线程的消息队列则是不可以退出的
- 创建的Looper对象保存在ThreadLocal中,作为线程的独有的对象
(2)启动轮询loop()
public static void loop() {
// 启动无限循环,从消息队列中获取消息
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// 将消息分发到msg对应的Handler对象
try {
msg.target.dispatchMessage(msg);
if (observer != null) {
observer.messageDispatched(token, msg);
}
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} catch (Exception exception) {
if (observer != null) {
observer.dispatchingThrewException(token, msg, exception);
}
throw exception;
} finally {
ThreadLocalWorkSource.restore(origWorkSource);
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
}
loop方法主要是启动一个无限循环,从消息队列中取出消息,通过msg.target.dispatchMessage分发给对应的Handler
4、Handler
Handler是系统提供对外操作的核心类,我们主要关心的是消息的发送和处理
(1)发送消息(send/post)
发送消息,主要有两种方式,send和post
1>即时消息
public final boolean sendEmptyMessage(int what)
{
return sendEmptyMessageDelayed(what, 0);
}
public final boolean sendMessage(@NonNull Message msg) {
return sendMessageDelayed(msg, 0);
}
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
2>延时消息
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public final boolean postDelayed(@NonNull Runnable r, long delayMillis) {
return sendMessageDelayed(getPostMessage(r), delayMillis);
}
(2)发送同步屏障消息
该API是不暴露给外部使用的,需要反射调用
主要是系统内部用于发送处理UI绘制相关的,为了优先处理UI绘制等相关任务,通常会先插入同步屏障,然后发送异步消息,插入异步消息后,移除屏障,保证异步消息优先执行
例如,Chroeographer中的与绘制相关的异步消息
private static final int MSG_DO_FRAME = 0;
private static final int MSG_DO_SCHEDULE_VSYNC = 1;
private static final int MSG_DO_SCHEDULE_CALLBACK = 2;
例如:ViewRootImpl中的scheduleTraversals方法
mHandler.getLooper().getQueue().postSyncBarrier() mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
同步屏障消息的发送是通过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;
// 遍历当前消息队列,将同步屏障消息插入到队列头部
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;
}
}
(3)处理消息(dispatchMessage)
消息的处理,是在Looper的loop方法中
loop(){
.......
msg.target.dispatchMessage(msg);
.....
}
我们主要分析Handler的dispatchMessage方法
/**
* Handle system messages here.
*/
public void dispatchMessage(@NonNull Message msg) {
// 当Message消息的Runnable不为空,则调用handleCallback方法
if (msg.callback != null) {
handleCallback(msg);
} else {
// 在创建Handler对象时传入回调,在Handler对应的线程处理
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
// 由Handler处理,需要覆写Handler的handleMessage方法
handleMessage(msg);
}
}
private static void handleCallback(Message message) {
// 调用Message中的Runnable的run方法
message.callback.run();
}
消息的处理主要过程如下:
五、IdleHandler
IdleHandler本质上是一个接口,如下:
/**
* Callback interface for discovering when a thread is going to block
* waiting for more messages.
*/
public static interface IdleHandler {
/**
* Called when the message queue has run out of messages and will now
* wait for more. Return true to keep your idle handler active, false
* to have it removed. This may be called if there are still messages
* pending in the queue, but they are all scheduled to be dispatched
* after the current time.
*/
boolean queueIdle();
}
它是定义在MessageQueue中的,作用是当MessageQueue空闲时,这里的空闲具体是指:
- 对于可退出的Handler,此时内部必然有消息,且消息的执行时间还未到达
- 对于不可退出的Handler,则分为两种情况
当最后一个消息return交付Looper后,执行下一次循环时,此时阻塞值还不是-1,但是当前消息队列中又不存在待执行的消息,那么就执行idleHandler的queueIdle
内部消息队列有消息,但是消息的执行时间还未到达,那么调用IdleHandler的queuIdle
具体看MessageQueue中的next方法
六、Handler需要注意的问题
1、休眠对Handler延时消息的影响
Handler延时消息使用了System.updateMillils()函数,该函数在系统休眠时不会进行计算,那么如果系统进入休眠,例如应用处于后台且锁屏时未连接USB时
只有当再次唤醒时才会执行
2、Handler的内存泄漏问题
例如组件销毁时,Handler仍然有延时消息没有发送
为了避免这个情况,我们需要在组件销毁的方法中,调用Handler的removeCallbackAndMessage方法,而该方法本质上是调动了MessageQueue的removeCallbackAndMessages方法
该方法的主要作用是遍历当前消息队列中的消息,依次回收到对象池中,并消息队列的头节点置为空,保证消息队列中没有需要执行的消息
void removeCallbacksAndMessages(Handler h, Object object) {
if (h == null) {
return;
}
synchronized (this) {
Message p = mMessages;
// Remove all messages at front.
while (p != null && p.target == h
&& (object == null || p.obj == object)) {
Message n = p.next;
mMessages = n;
// 回收消息
p.recycleUnchecked();
p = n;
}
// Remove all messages after front.
while (p != null) {
Message n = p.next;
if (n != null) {
if (n.target == h && (object == null || n.obj == object)) {
Message nn = n.next;
n.recycleUnchecked();
p.next = nn;
continue;
}
}
p = n;
}
}
}
3、子线程创建Handler问题
资料
1、nativePollOnce与nativeWake解析
https://blog.csdn.net/chewbee/article/details/78108201
https://www.cnblogs.com/jiy-for-you/archive/2019/10/20/11707356.html
2、同步屏障
https://www.jianshu.com/p/086462f061b7
https://blog.csdn.net/start_mao/article/details/98963744
https://blog.csdn.net/asdgbc/article/details/79148180
3、Handler的扩展
面试问题
1、Handler原理
(1)Looper、Message与Thread有什么对应关系?
(2)Looper轮询为什么不会产生ANR
(3)延时消息如何实现的?系统休眠、切换到后台对于延时消息是否有影响?
延时消息的发送,首先我们使用时传入了延时时间,单位毫秒,发送消息时Handler内部通过System.updateMillis()方法计算当前时间戳,用这个时间戳加上我们加入的延时,就是消息最后发送的时间戳
(4)Looper中的无限循环是否会消耗资源
2、子线程问题
(1)子线程中实例化 Handler会有什么问题?
在Handler构造方法中存在Looper非空的判断,如果为空,则抛出RuntimeException异常
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
(2)子线程创建Handler中使用Looper.prepare和loop方法的作用是什么?
UI线程默认会创建Looper和MessageQueue,并启动loop轮询
子线程需要我们手动处理这些逻辑
3、定时任务
(1)是否可以用Handler实现定时任务?
不可以,休眠时会停止计时
建议使用workManager或者AlarmManager或者Timer来执行
4、同步屏障与异步消息
(1)讲讲同步屏障与异步消息

浙公网安备 33010602011771号