Android 中的 Handler
都被人说烂了,但是还是想多说一次,因为在 Android 的系统中它真的非常重要而且它的机制并没有很复杂,无论是新手和老手都可以好好学习下,这对理解 Android 系统很重要,所以说学习的性价比非常高。
Android 中 IPC
跨进程通信主要依靠 Binder
, 而同一个进程的通信就主要依靠 Handler
。
这里先简单描述下 Handler
的工作流程:
首先在 Handler
处理任务的线程需要调用 Looper#prepare()
方法,来为当前线程创建一个 Looper
实例,这个实例通过 ThreadLocal
存放,也就是一个线程只能有一个这个实例;Looper
构造函数中会初始化一个 MessageQueue
对象,MessageQueue
对象就是用来封装任务的队列,它是用的链表实现,而任务队列的 Item 就是 Message
对象,也就是单个的任务;任务处理线程调用 Looper#prepare()
方法完成后,会调用 Looper#loop()
方法,这个时候任务线程就会阻塞去处理对应 Looper
中的 MessageQueue
中的任务,处理的方式就是一个死循环从 MessageQueue
中去获取最新的 Message
获取到后,然后调用对应的 Handler#handleMessage()
方法去执行它,执行完成后又去获取下一个任务,如果没有新的任务就会陷入阻塞,等有新的任务来的时候会唤醒这个阻塞(这个唤醒机制是用的 Linux 中的 epoll
机制),继续执行新的任务。
上面大致描述了 Looper
和 MessageQueue
如何从队列中获取新的任务和执行任务,这里再简单描述下怎么插入任务,首先要自定一个 Handler
对象,构造函数中需要传入上面所创建的 Looper
对象,其中自定义的 Handler#handleMessage
方法就是用来执行任务的方法,其他的线程需要向对应的 Looper
线程添加任务时,就调用上面 Handler
实例的 sendMessage
方法来添加任务,也就是向 MessageQueue
队列中添加任务,最终会在 Looper
所对应的线程执行,这样就完成了一次跨线程的通信。
下面是一个简单的 Handler
使用的代码:
override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)var handlerLooper: Looper? = nullval handlerThread = object : Thread() {override fun run() {super.run()Looper.prepare()handlerLooper = Looper.myLooper()Looper.loop()}}handlerThread.start()while (handlerLooper == null) {// 等待 HandlerThread 初始化完成}val handler = object : Handler(handlerLooper!!) {override fun handleMessage(msg: Message) {super.handleMessage(msg)if (msg.what == 0) {// TODO: 处理任务,该方法最终工作在 HandlerThread 线程}}}val msg = handler.obtainMessage(0)// 发送的代码工作在主线程handler.sendMessage(msg)
}
Android 中有现成的 HandlerThread
,不用自定义,我这里是为了展示这个过程。
Tips: 我后续的源码分析都是基于 andorid 31
Looper
Looper 初始化
// ...
public static void prepare() {prepare(true);
}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
对象就直接报错,如果没有新建一个 Looper
对象放在 ThreadLocal
中,在 Looper
的构造函数中,创建了 MessageQueue
实例和保存了当前的 Thread
对象。
Looper 任务处理
// ...
public static void loop() {final Looper me = myLooper();if (me == null) {throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");}if (me.mInLoop) {Slog.w(TAG, "Loop again would have the queued messages be executed"+ " before this one completed.");}me.mInLoop = true;// ..me.mSlowDeliveryDetected = false;for (;;) {if (!loopOnce(me, ident, thresholdOverride)) {return;}}
}
// ...
Looper#loop()
方法也非常简单,修改了当前的 Looper
的一些状态,然后在死循环中无限调用 loopOnce()
方法去执行任务,如果该方法返回 false
就表示该 Looper
已经退出,就跳出循环。
private static boolean loopOnce(final Looper me,final long ident, final int thresholdOverride) {Message msg = me.mQueue.next(); // might blockif (msg == null) {// No message indicates that the message queue is quitting.return false;}// This must be in a local variable, in case a UI event sets the loggerfinal Printer logging = me.mLogging;if (logging != null) {logging.println(">>>>> Dispatching to " + msg.target + " "+ msg.callback + ": " + msg.what);}// ...Object token = null;if (observer != null) {token = observer.messageDispatchStarting();}long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);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);}}if (logging != null) {logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);}// ...msg.recycleUnchecked();return true;
}
直接调用 MessageQueue#next()
方法去获取 Message
对象,如果返回空就表示 Looper
已经退出了。这里有一个 Printer
对象,在 Message
执行前后都会打印对应的日志,可以通过调用 Looper#setMessageLogging()
方法来自定义这个对象,根据打印的日志内容我们就可以分辨是开始前还是开始后,在 APM 中经常通过这个方法来判断主线程的任务执行是否超时。执行任务会调用的是 handler.target#dispatchMessage()
方法,其实这个 target 对象就是 Handler
,后续再分析这个方法。
这次看代码还有新发现,这里还多了一个 Observer
对象,我记得以前的旧版本是没有的,可以通过 Looper#setObserver()
方法来设置,它的接口如下:
public interface Observer {/*** Called right before a message is dispatched.** <p> The token type is not specified to allow the implementation to specify its own type.** @return a token used for collecting telemetry when dispatching a single message.* The token token must be passed back exactly once to either* {@link Observer#messageDispatched} or {@link Observer#dispatchingThrewException}* and must not be reused again.**/Object messageDispatchStarting();/*** Called when a message was processed by a Handler.** @param token Token obtained by previously calling* {@link Observer#messageDispatchStarting} on the same Observer instance.* @param msg The message that was dispatched.*/void messageDispatched(Object token, Message msg);/*** Called when an exception was thrown while processing a message.** @param token Token obtained by previously calling* {@link Observer#messageDispatchStarting} on the same Observer instance.* @param msg The message that was dispatched and caused an exception.* @param exception The exception that was thrown.*/void dispatchingThrewException(Object token, Message msg, Exception exception);
}
Observer
可以监听任务开始,结束和异常,看上去它可以替换 Printer
,不过它是对应用层隐藏的,需要骚操作。
Handler
Handler 初始化
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {mLooper = looper;mQueue = looper.mQueue;mCallback = callback;mAsynchronous = async;
}
其中 Looper
和 MessageQueue
就不用多说了;其中 mCallback
对象就是可以优先于 Handler#handleMessage
方法处理任务,也可以拦截任务不让 Handler#handleMessage
执行;async
是表示发送的消息是否是异步消息,这个和消息屏障关系密切,后续会再分析。
Handler 发送消息
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);
}
无论是调用 Handler
的哪个方法发送消息,最终都会到 enqueueMessage()
方法里面去,在 Looper
中我们说到 target
就是 Handler
对象,在这里就得到验证了,如果 Handler
的构造函数中设置为异步,Message
也会被设置为异步,然后直接将处理好的 Message
通过 MessageQueue#enqueueMessage()
方法添加到队列中。
Handler 处理消息
在 Looper
下发消息时说到最后会调用 Handler
的 dispatchMessage()
方法:
public void dispatchMessage(@NonNull Message msg) {if (msg.callback != null) {handleCallback(msg);} else {if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}}handleMessage(msg);}
}
如果 Message
中有设置 Callback
就直接在对应的 Callback
中处理,不继续下发; 如果 Message
中没有设置 Callback
,会先检查 Handler
是否有设置 mCallback
,如果有设置,调用 mCallback
处理,mCallback
的 handleMessage
返回 true
就表示要拦截该消息,如果不拦截就交由 Handler
的 handleMessage()
方法处理。
MessageQueue
MessageQueue 插入任务
在讲 Handler
的时候说到插入消息会直接调用 MessageQueue#enqueueMessage()
方法:
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;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.nextprev.next = msg;}// We can assume mPtr != 0 because mQuitting is false.if (needWake) {nativeWake(mPtr);}}return true;
}
如果 Message
没有设置 Handler
直接抛出异常;如果 Message
已经是 inUse
状态,直接抛出异常;如果 MessageQueue
已经退出,就直接回收 Message
;按照任务的触发时间由小到大,插入到 mMessage
链表中,最后如果需要唤醒 next()
方法中的 nativePollOnce()
方法,就会调用 nativeWake
方法,这是一个 C++ 实现的方法(内部实现其实是向一个管道 fd
中写入了一个数字 1,通过 epoll
机制,会监听到管道 fd
有数据写入,然后会唤醒 nativePollOnce()
方法,然后去读取新的 Message
)。
MessageQueue 读取任务
在讲 Looper
时讲到最后获取任务是通过 MessageQueue#next()
方法:
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 iterationint nextPollTimeoutMillis = 0;for (;;) {if (nextPollTimeoutMillis != 0) {Binder.flushPendingCommands();}// 等待 epoll 唤醒,唤醒是等待 nativeWake() 方法调用或者超时(超时时间为 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;// target 为空,就表示这是一个屏障的消息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.// 当前消息的时间大于当前的时间,表示还没有到达执行的时间,计算间隔,等待重新进入 nativePollOnce 方法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;}// 后续就是在没有消息时回调 IdleHandler// If first time idle, then get the number of idlers to run.// Idle handles only run if the queue is empty or if the first message// in the queue (possibly a barrier) is due to be handled in the future.// 如果 pendingIdleHandlerCount 小于 0 就表示当前的这一次 next() 方法调用,还没有执行过 IdleHandler,就需要去读取 IdleHandler 的数量,在一次 next() 方法调用时最多执行一次 IdleHandler。if (pendingIdleHandlerCount < 0&& (mMessages == null || now < mMessages.when)) {pendingIdleHandlerCount = mIdleHandlers.size();}if (pendingIdleHandlerCount <= 0) {// 这里就表示 IdleHandler 为空,或者已经执行过了。// No idle handlers to run. Loop and wait some more.mBlocked = true;continue;}if (mPendingIdleHandlers == null) {mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];}mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);}// Run the idle handlers.// We only ever reach this code block during the first iteration.for (int i = 0; i < pendingIdleHandlerCount; i++) {final IdleHandler idler = mPendingIdleHandlers[i];mPendingIdleHandlers[i] = null; // release the reference to the handlerboolean keep = false;try {// 执行 idleHandlerkeep = idler.queueIdle();} catch (Throwable t) {Log.wtf(TAG, "IdleHandler threw exception", t);}// 如果不需要保留就把它移出if (!keep) {synchronized (this) {mIdleHandlers.remove(idler);}}}// Reset the idle handler count to 0 so we do not run them again.// 这里设置为 0 后,IdleHandler 就不会再次执行,需要等待下次的 next() 方法调用pendingIdleHandlerCount = 0;// While calling an idle handler, a new message could have been delivered// so go back and look again for a pending message without waiting.nextPollTimeoutMillis = 0;}
}
通过 nativePollOnce()
方法等待唤醒(通过调用 nativeWake()
方法)或者超时(超时时间是 nextPollTimeoutMillis
),唤醒后直接获取最新的一条 Message
(先跳过同步消息逻辑),然后判断任务执行的时间,如果大于当前时间就继续等待,直到可以执行,如果小于当前时间直接返回,同时把这条消息从队列中移除(先跳过 IdleHandler 逻辑)。
同步消息和消息屏障
如果 target
对象为空就表示该消息是一个同步屏障,如果是同步屏障的话他就会重新再去取该同步屏障消息后面的最新一个异步消息去执行,如果没有异步消息就会去等待。
通过以下方法添加屏障消息:
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.nextmsg.next = p;prev.next = msg;} else {msg.next = p;mMessages = msg;}return token;}
}
其实就是在消息队列中添加了一个 target
为空的消息,同时在 Message
的 arg1
对象中添加 token
对象,然后这个 token
也会返回给调用方,这个 token
在移除屏障时需要用到。
通过以下方法可以移除这个消息屏障:
public void removeSyncBarrier(int token) {// Remove a sync barrier token from the queue.// If the queue is no longer stalled by a barrier then wake it.synchronized (this) {Message prev = null;Message p = mMessages;while (p != null && (p.target != null || p.arg1 != token)) {prev = p;p = p.next;}if (p == null) {throw new IllegalStateException("The specified message queue synchronization "+ " barrier token has not been posted or has already been removed.");}final boolean needWake;if (prev != null) {prev.next = p.next;needWake = false;} else {mMessages = p.next;needWake = mMessages == null || mMessages.target != null;}p.recycleUnchecked();// If the loop is quitting then it is already awake.// We can assume mPtr != 0 when mQuitting is false.if (needWake && !mQuitting) {nativeWake(mPtr);}}
}
我之前在 [Framework] Android Choreographer 工作原理有简单介绍过,在绘制的时候就会用到同步消息和消息屏障来提高绘制相关的 Message
的优先级。
请求绘制最终会到 ViewRootImpl#scheduleTraversals()
方法中,在这个方法中就会添加一个屏障:
void scheduleTraversals() {if (!mTraversalScheduled) {mTraversalScheduled = true;mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);notifyRendererOfFramePending();pokeDrawLockIfNeeded();}
}
在 Choregorapher
收到 Vsync
信号后会发送一个 Handler
的异步 Message
:
public void onVsync(long timestampNanos, long physicalDisplayId, int frame,VsyncEventData vsyncEventData) {// ..Message msg = Message.obtain(mHandler, this);msg.setAsynchronous(true);mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);// ..
}
在绘制完成后会调用 ViewRootImpl#unscheduleTraversals()
方法移除这个屏障:
void unscheduleTraversals() {if (mTraversalScheduled) {mTraversalScheduled = false;mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);mChoreographer.removeCallbacks(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);}}
IdleHandler
IdleHandler
就是在 next()
方法获取不到消息时会执行一次,next()
方法调用一次最多只能执行一次 IdleHandler
,执行 IdleHandler
的时候就表示当前的 Looper
比较空闲,IdleHandler
的数量最多为 4 个 (在 Activity
的 onDestroy()
回调就是在 IdleHandler
中执行的,可能写这部分代码的人,认为 onDestroy()
相对其他的生命周期没有那么重要,但是由于 IdleHandler
的特性也会引发一些问题,后续有时间会分析这个问题) 。
可以通过以下方法添加 IdleHandler
:
public void addIdleHandler(@NonNull IdleHandler handler) {if (handler == null) {throw new NullPointerException("Can't add a null IdleHandler");}synchronized (this) {mIdleHandlers.add(handler);}}
epoll 在 Handler 中的工作方式
在 MessageQueue
的构造函数中会通过 Native 方法 nativeInit()
初始化 epoll
,初始化流程会先通过 epoll_create()
方法创建一个 EpollFd
,然后构建一个管道的 fd
用于 epoll
来监听它的读写状态,这个 fd
被命名为 WakeFd
,然后通过 epoll_ctl()
方法将 WakeFd
添加到 EpollFd
中,以供后续通过 EpollFd
能够监听 WakeFd
的状态。
在 MessageQueue
的 next()
方法中会通过 Native 方法 nativePollOnce()
方法去监听上面所提到 WakeFd
的状态,Native 层是通过 epoll_wait()
方法去监听状态,这个方法会等到超时或者 WakeFd
有数据写入就会返回。
在 MessageQueue
的 enqueueMessage()
方法中插入 Message
后,会判断是否需要唤醒 next()
中的 nativePollOnce()
方法,如果需要唤醒就会调用 Native 方法 nativeWake()
方法,Native 层就会向 WakeFd
中写入一个数字 1,然后 nativePollOnce()
间听到这个 WakeFd
写状态后就会返回,也就解除 next()
方法的阻塞去读取新的 Message
。
最后聊一聊主线程
在 Android Framework 中有两个主要的主线程 Handler
,一个是 ActivityThread
中的 H
,它主要来处理四大组建的各种生命周期;还有一个是 Choreographer
中的 FrameHandler
,它主要负责绘制,动画,输入等操作。这两个 Handler
负责的工作和用户体验都极为密切,主线程也可以说非常忙。
在项目中我经常发现很多的代码都会在主线程执行,明明有的操作不需要主线程执行。比如说在一个网络请求的通用方法中,在网络完成的回调中就会主动切换到主线程,明明很多地方的代码不需要用到主线程,我个人的观点是一定需要切换主线程时再切换,在一定程度上能够缓解应用卡顿的问题。
Android 学习笔录
Android 性能优化篇:https://qr18.cn/FVlo89
Android Framework底层原理篇:https://qr18.cn/AQpN4J
Android 车载篇:https://qr18.cn/F05ZCM
Android 逆向安全学习笔记:https://qr18.cn/CQ5TcL
Android 音视频篇:https://qr18.cn/Ei3VPD
Jetpack全家桶篇(内含Compose):https://qr18.cn/A0gajp
OkHttp 源码解析笔记:https://qr18.cn/Cw0pBD
Kotlin 篇:https://qr18.cn/CdjtAF
Gradle 篇:https://qr18.cn/DzrmMB
Flutter 篇:https://qr18.cn/DIvKma
Android 八大知识体:https://qr18.cn/CyxarU
Android 核心笔记:https://qr21.cn/CaZQLo
Android 往年面试题锦:https://qr18.cn/CKV8OZ
2023年最新Android 面试题集:https://qr18.cn/CgxrRy
Android 车载开发岗位面试习题:https://qr18.cn/FTlyCJ
音视频面试题锦:https://qr18.cn/AcV6Ap