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)讲讲同步屏障与异步消息