在 Android 系统中,非主线程不可更新 UI。因而经常在自线程中使用 mHandler.post{}
的方式,将 UI 更新操作 post 到主线程中去执行。这个 post 操作,则是所谓的 Android 消息队列机制。
Handler —— 方便 api 调用的入口
通过查看 Handler() 构建方法的源码,可以发现有以下成员变量的初始化:
// Looper
mLooper = Looper.myLooper();
// MessageQueue
mQueue = mLooper.mQueue;
// 自定义 callback,用于处理 sendMessage 接口发送的 Message
mCallback = callback;
// boolean 类型
mAsynchronous = async;
Handler 常用的几个方法: boolean post(@NonNull Runnable r)
, boolean sendMessage(@NonNull Message msg)
,void removeCallbacksAndMessages(@Nullable Object token)
。追踪这几个方法的调用链,发现最后都是形成一个 Message,并将其加入 MessageQueue(或从中移除)
至此,消息队列中,最重要的类出现了:
Handler、Message
以及隐藏在handler 中的: Looper、MessageQueue
实际功能上,MessageQueue 来源于 Looper (mQueue = mLooper.mQueue
),而 Message 由是被 enqueue 到 MessageQueue 中。那么消息队列的基础应该是 Looper。
Looper —— 与线程一对一绑定的传送带
在 Handler 中初始化 Looper时,还伴随着一个判空的 Exception
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
此处提到了 Looper 的两个方法 —— Looper.myLooper
和在 Exception 提示中提到的 Looper.prepare
但细看两个方法,也都很简单。
public static @Nullable Looper myLooper() {
// 从 sThreadLocal 中取出了当前 thread 对应的 Looper
return sThreadLocal.get();
}
public static void prepare() {
prepare(true);
}
// 主线程对应的 mainLooper 不可退出;其他线程对应的 Looper 都可退出
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
// 新建一个 Looper 并存储
sThreadLocal.set(new Looper(quitAllowed));
}
private Looper(boolean quitAllowed) {
// 初始化 MessageQueue
mQueue = new MessageQueue(quitAllowed);
// 绑定 thread
mThread = Thread.currentThread();
}
由此可见,Looper.prepare() 绑定的是当前线程。且如果已经绑定过 Looper,再次绑定,会抛异常
但除此之外,Looper 也没有明显的逻辑了?
可以通过 HandlerThread
这个 Android 系统自带类,来查看 Looper 的使用
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
在 prepare 之后,还调用了 Looper.loop()
以下代码省略了大部分非主线逻辑
public static void loop() {
final Looper me = myLooper();
...
for (;;) {
// 死循环调用 loopOnce
if (!loopOnce(me, ident, thresholdOverride)) {
return;
}
}
}
private static boolean loopOnce(final Looper me,
final long ident, final int thresholdOverride) {
// 从 MessageQueue 中获取下一个消息
Message msg = me.mQueue.next(); // might block
...
// 消息分发执行
msg.target.dispatchMessage(msg);
...
// 消息回收,清空消息内容并放回对象池
msg.recycleUnchecked();
return true;
}
loop 的逻辑也很简单,不断获取消息队列(MessageQueue)中的数据(Message)并分发,重点在于,当队列中没有数据的时候,next 是个什么状态?
那么继续看下一个类:MessageQueue
MessageQueue —— 消息队列
首先看 MessageQueue 的构建函数:
MessageQueue(boolean quitAllowed) {
// 是否允许退出
mQuitAllowed = quitAllowed;
// native 函数调用
mPtr = nativeInit();
}
MessageQueue中涉及了 native 函数的调用。参考资料:Native层的Looper和MessageQueue
又或者 IT 工具 查看native层源码 这篇博文学习如何查看 native 层源码实现。
此处就不粘贴 native 层源码了,只进行接口的简单说明
// 创建 NativeMessageQueue 实例,并返回句柄
private native static long nativeInit();
// 销毁实例
private native static void nativeDestroy(long ptr);
// NativeMessageQueue 通过 native 的 Looper 在 timeoutMillis 时段内,实现线程挂起,不阻塞
private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/
// 唤醒被挂起的线程
private native static void nativeWake(long ptr);
MessageQueue 的 next
方法,除了通过 nativePollOnce 函数实现了线程的挂起,官方注释已经将逻辑阐述的很明了了,此处就不粘贴了。
队列的插入方法为 boolean enqueueMessage(Message msg, long when)
,同样的,官方有逻辑注释,需要注意的就是,在队列插入中,除了处理队列数据增删,还判断了当前队列是否在挂起中,或插入了新的队头,此时需要通过 nativeWake
唤醒阻塞
而 Handler 调用 removeCallbacksAndMessages
方法,也是调用到了MessageQueue 的 void removeCallbacksAndMessages(Handler h, Object object)
。在这个方法中,对队列进行了便利,移除了对应 Handler 创建的 Message
Message 的组装发送 和 分发执行
mHandler.post{}
实质上,也是通过 getPostMessage
方法组装了一个 Message,然后拼接到 MessageQueue 中
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
// 构建 Message
private static Message getPostMessage(Runnable r) {
// 从对象池中获取 message
// 对象池的存在主要是为了避免内存抖动——大量的对象创建和销毁,在 java 虚拟机中会造成垃圾回收(GC)需要,GC 需要暂停所有指令(STW —— Stop the world),频繁 GC 容易产生用户能感知到的卡顿
Message m = Message.obtain();
// 将 post 的runnable 赋值给 message 的 callback
m.callback = r;
return m;
}
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
// 将 delayMillis 的相对时间,转换为 SystemClock.uptimeMillis() + delayMillis 的绝对时间
// SystemClock.uptimeMillis() 计算的是开机时间
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
// 绑定线程的 MessageQueue
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);
}
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
// Message 的 target 为 handler
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
从上面代码可知,Message 的 target 为 Handler。如果是调用的 mHandler.post{}
方法,message 持有 post 的 Runnable 作为 callback。
在 Looper.loopOnce()
方法中,消息分发执行的代码为:msg.target.dispatchMessage(msg);
,即调用 handler 的 dispatchMessage
方法中
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
// 如果是 post 调用,直接 run callback
handleCallback(msg);
} else {
// handler 的 callback 处理
if (mCallback != null) {
// 如果 callback 接口方法返回了 true,则拦截
if (mCallback.handleMessage(msg)) {
return;
}
}
// handler 的子类处理 message
handleMessage(msg);
}
}
private static void handleCallback(Message message) {
message.callback.run();
}
消息队列小结
Handler 处理消息
- post(Runnable) 通过 message 设置 callback
- sendMessage 通过
Handler(@Nullable Callback callback, boolean async)
设置全局的 callback,或者定义 Handler 子类,并重写private static void handleCallback(Message message)
方法
Handler 持有 Looper
- 此 Looper 如果是入参,应传入执行线程的 Looper
- 如果是无参构建,需要在执行线程构建(会自动获取当前线程的looper)。所以在自线程中,直接用
new Handler().post{}
并不能将消息 post 到主线程。
Looper 通过 ThreadLocal 结构,保证一对一绑定线程
- 在已绑定 Looper 的线程中执行 Looper.prepare() 会抛异常
- Looper.prepare() 之后,还需调用 Looper.loop() 进行启动
- 多次在一个线程中调用 Looper.loop(),可能导致消息队列中的下一个消息,在 loop 调用的消息结束前,就被分发执行
Looper 拥有一个 MessageQueue 队列,用于存储消息
- MessageQueue 在
enqueueMessage
(插入)时,对 Message 进行了时间排序,并不是 FIFO(先进先出)
MessageQueue 中,通过 native 函数,控制挂起时长
当队列为空时,mBlocked = true
如果有消息,获取消息后,如果消息执行时间已经超过当前时间,立即执行,否则计算时间差,并挂起
常说 Android 的消息分发机制是基于生产者消费者模型的。可以将整个消息分发机制看成是一个车间,执行线程是消费者,发出消息的线程是生产者,Message 是需要加工的零件,MessageQueue 是承载零件的传送带,Looper 则是移动传送带的滚轮