MessageMessageQueue分析
Message & MessageQueue 原理分析
Handler
机制中 MessageQueue
对象是跨线程间通信的桥梁。 Message
对象是架起这座桥梁的材料。在 App 进程中,通过消息队列的方式,实现在不同的线程间传递消息,进而实现跨线程的通信。
主线程 MessageQueue 的创建
app 继承创建运行,首先运行的方法是 ActivityThread.main(String[] args)
方法。
// ActivityThread.java// 移除了与主要篇幅内容不强关联的代码。
public static void main(String[] args) {Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");// ......Looper.prepareMainLooper();// ......ActivityThread thread = new ActivityThread();thread.attach(false, startSeq);if (sMainThreadHandler == null) {sMainThreadHandler = thread.getHandler();}// ......Looper.loop();throw new RuntimeException("Main thread loop unexpectedly exited");
}
在 main(String[] args)
方法中,调用到 Looper.prepareMainLooper();
方法,在 prepareMainLooper()
创建 MessageQueue
实例。涉及的代码定义如下:
// Looper.java@UnsupportedAppUsage
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
@UnsupportedAppUsage
private static Looper sMainLooper; // guarded by Looper.class@UnsupportedAppUsage
final MessageQueue mQueue;
final Thread mThread;private Looper(boolean quitAllowed) {mQueue = new MessageQueue(quitAllowed); // 创建 Looper 对象时,创建 MessageQueue 对象。mThread = Thread.currentThread();
}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)); // 创建 Looper 对象
}@Deprecated
public static void prepareMainLooper() {prepare(false); // 将 Looper 设置到 ThreadLocal。synchronized (Looper.class) {if (sMainLooper != null) {throw new IllegalStateException("The main Looper has already been prepared.");}sMainLooper = myLooper(); // 通过 ThreadLocal,以当前线程作为 key,获取对应 Looper 对象。}
}
prepareMainLooper()
的执行过程:
Looper
类被加载到内存,静态变量sMainLooper
同时会被加载到内存并初始化为null
,常量sThreadLocal
被加载到内存并创建实例 ( 即new ThreadLocal<Looper>()
) 。( static 成员 (变量,方法) 的加载是 JVM 知识 )- 方法调用
prepare(false)
中创建Looper
对象,在私有构造方法中创建实例相关的MessageQueue
对象mQueue
,以及获取当前线程mThread
。 也就是 **一个Looper
,对应一个MessageQueue
**。 - 将创建的
Looper
对象设置到sThreadLocal
对象。 - 获取
sMainLooper
对象。
MessageQueue 取消息
在执行 ActivityThread.main(String[] args)
中,在创建完成 Looper
( 同时创建了 MessageQueue
) 后,最后执行了 Looper.loop()
方法。
简化地看下 Looper.loop()
方法的主要程序:
// Looper.javapublic static void loop() {final Looper me = myLooper(); // 获取当前线程绑定的 Looper 对象。if (me == null) {throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");}final MessageQueue queue = me.mQueue; // 得到 Looper 对象拥有的 MessageQueue 对象。// ......for (;;) { // 死循环,保证了进程的不停运行,不会在程序执行完成后,就结束进程。Message msg = queue.next(); // 获取消息队列中的消息,可能阻塞。if (msg == null) {// No message indicates that the message queue is quitting.return;}// ......try {// msg.target 是 Handler 对象,调用 Handler 的 dispatchMessage(),// 接着根据判断,调用到熟悉的 handleMessage(Message) 方法。msg.target.dispatchMessage(msg); // ......} catch (Exception exception) {// ......throw exception;} finally {// ......}// ......msg.recycleUnchecked(); // 回收 Message}
}
进入到 loop()
方法,首先获取到与当前线程绑定的 Looper
对象,及对应的 MessageQueue
消息队列对象。随后开始一个 for(;;)
死循环,保证了主线程不退出,一直运行,进而保证 app 进程的运行。
循环的第一行就是获取消息队列的对头消息 ( Message ) 。
MessageQueue.next()
方法获取消息
在 next()
方法中,通过与 native 层的交互实现了 无消息等待及有消息唤醒 的通信方式。
// MessageQueue.java@UnsupportedAppUsage
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();@UnsupportedAppUsage
Message next() {// mPtr是在 MessageQueue 实例创建时,调用 naiveInit() 后 native 层返回的创建 native 层数据的地址值。final long ptr = mPtr; if (ptr == 0) {return null;}for (;;) {// ......// 这里调用 native 方法,实现无消息时等待,有消息时唤醒线程。// 这样可以避免线程无消息时,无畏循环等待,避免cpu资源的空耗。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 是 null 的 Message 对象时,接着要去找下一个 异步Message对象 并及时处理。// 若是普通消息,即其 target 属性会有值的情况下,不会进入到下面的循环,进而 prevMsg 的值是 null。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 { // 需要立即处理的消息。// Got a message.mBlocked = false;if (prevMsg != null) { // prevMsg 不为 null,意味着发现了屏障消息。prevMsg.next = msg.next; // 直接将 prevMsg 指向 msg 的后继 Message 对象。} else {mMessages = msg.next; // 普通的消息,将 mMessages 链表头指向 msg 的后继 Message 对象。}msg.next = null; // 断开 msg 的 next 指针(地址值)if (DEBUG) Log.v(TAG, "Returning message: " + msg);msg.markInUse();return msg; // 返回当前的消息对象。}} else {// No more messages.nextPollTimeoutMillis = -1;}if (mQuitting) {dispose();return null;}// 这里是开始计算执行 IdleHandler 。// 1. 在消息列表是空(mMessages = null)。// 2. 或者第一个消息是屏障消息(在屏障消息后没有找到异步异步消息,msg = null)或 deplayed消息 (不需要马上执行)。// 在上述两种情况下执行。if (pendingIdleHandlerCount < 0&& (mMessages == null || now < mMessages.when)) {pendingIdleHandlerCount = mIdleHandlers.size();}if (pendingIdleHandlerCount <= 0) { // 没有 idle handler 时,直接接着后面的消息处理。mBlocked = true;continue;}if (mPendingIdleHandlers == null) {mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];}mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);} // end synchronized// 运行 IdleHandler 程序。for (int i = 0; i < pendingIdleHandlerCount; i++) {final IdleHandler idler = mPendingIdleHandlers[i];mPendingIdleHandlers[i] = null;boolean keep = false;try {keep = idler.queueIdle(); // 调用到 IdleHandler 的 queueIdle() 方法。} catch (Throwable t) {Log.wtf(TAG, "IdleHandler threw exception", t);}if (!keep) {synchronized (this) {mIdleHandlers.remove(idler);}}}pendingIdleHandlerCount = 0;nextPollTimeoutMillis = 0;} // end for
}
上述代码是从 MessageQueue
获取消息的代码。
主要代码在 for(;;)
中几点:
-
nativePollOnce(ptr, nextPollTimeoutMillis)
函数是MessageQueue
中的一个本地方法,其主要功能是在消息队列中进行一次轮询。函数会阻塞当前线程,等待消息队列中的消息到达或者超时。它会等待指定的时间(由nextPollTimeoutMillis
指定),如果在超时时间内有消息到达,则会立即返回;如果超时时间到达而没有消息到达,则会继续执行后续的逻辑。它的作用是实现消息队列的阻塞等待机制,以便在没有消息到达时,让线程进入休眠状态,避免空闲循环的浪费。当有消息到达时,它会唤醒线程,使其继续执行后续的消息处理逻辑。更确切的说,它使用的是 Linux 内核中的 epoll 机制实现无消息时等待,有消息时唤醒线程。
-
有消息到达:
nativePollOnce(ptr, nextPollTimeoutMillis)
方法返回,判断消息是否是屏障 ( msg.target == null ),若是屏障消息,则尝试在其后面的消息中找到异步消息。且prevMesg
,msg
向后移动,直到找到异步消息,或直到最后再也没有消息了。- 有消息过来 ( msg != null ),判断是否是 deplayed 消息,若是仅设置
nextPollTimeoutMillis
下载唤醒线程的时长。且继续往下,执行 Idle Handler 调用程序。 - 有消息,但不是 deplayed 消息,说明当前
msg
消息是需要返回到Looper
处理的消息。进一步判断if (prevMsg != null)
,也就是判断是否是异步消息。并且将msg
对象从Message
链表中移除并返回给Looper
程序,即结束next()
方法。
-
没有消息,且
nativePollOnce(ptr, nextPollTimeoutMillis)
等待时间到了,也会返回,并且执行后续的 Idle Handler 程序。 -
调用 Idle Handler 执行后,重置
pendingIdleHandlerCount
nextPollTimeoutMillis
两个变量,并进入下一个循环,处理消息。
向 MessageQueue 中添加 Message
在 app 程序中,发送消息最常用的方式是 Handler.sendMessage(Message)
方法完成。通过调用链,最终调用到方法:
// Handler.javaprivate 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);
}
最终调用的是 MessageQueue.enqueueMessage(Message msg, long when)
方法。在这个方法中,同时决定了是否唤醒线程。
Message
在 Looper
的 looper()
方法无限循环中,在 Message msg = queue.next();
返回获取到 msg
对象后,接着会调用到 msg.target.dispatchMessage(msg);
执行 Handler.dispatchMessage(Message msg)
方法。
// Handler.javapublic void dispatchMessage(@NonNull Message msg) {if (msg.callback != null) {handleCallback(msg);} else {if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}}handleMessage(msg);}
}
Message 分类
Android 中 Message 主要有 3 种:
- 同步消息:普通 app 程序中的消息都是这种消息类型。
- 屏障消息:在 UI 更新时先发送一个屏障消息,接着发送一个异步消息。
- 异步消息:处理动画,输入和绘图时发送异步消息。
UI 的刷新可以是由显示器的 vsync 信号驱动,即固定的刷新信号。也可以是由程序中处理 View
的一些状态时改变。异步消息是在 UI 刷新时发送到 MessageQueue
,执行入口是 ViewRootImpl.scheduleTraversals()
。
// ViewRootImpl.javafinal ViewRootHandler mHandler = new ViewRootHandler();@UnsupportedAppUsage
void scheduleTraversals() {if (!mTraversalScheduled) {mTraversalScheduled = true;mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); // 发送一个屏障信号。mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); // 发送异步消息if (!mUnbufferedInputDispatch) {scheduleConsumeBatchedInput();}notifyRendererOfFramePending();pokeDrawLockIfNeeded();}
}
在之前的 MessageQueue.next()
方法中,有几行代码:
// MessageQueue.javasynchronized(this) {// ......if (msg != null && msg.target == null) {// 发现屏障消息后,循环尝试查找到异步消息。do {prevMsg = msg; // 记录屏障消息msg = msg.next; // 获取下一个消息,这个消息需要使 异步消息} while (msg != null && !msg.isAsynchronous());}// ......
}
上面的这几行代码就是获取到 屏障消息 ,再进一步去查找后面的异步消息,优先处理异步消息,即 UI 刷新。
Message 创建
讲 Message
是因为它的设计中采用了池的实现方式。常用的创建方式:
Message.obtain()
:obtain()
有多个重载方法,且这些方法是静态的。它会从Message
池中去获取一个可用的Message
对象。若没有可用的对象,随即会创建一个新的Message
对象。使用这种方式创建的Message
对象可以被重复使用,避免了频繁地创建和销毁对象,提高了性能。new Message()
: 使用Message
构造方法创建一个新的对象。Handler.obtain()
: 使用Handler
的obtain()
方法创建,这种方式的实现本质上还是调用的Message.obtain()
的创建方式。
Message 池
Message
池子的实现使用的是 链表 的设计结构。
在 Message
类设计中,定义了 Message next;
类型的变量,用以指向 Message
对象。
// Message.java@UnsupportedAppUsage
/*package*/ Message next;/** @hide */
public static final Object sPoolSync = new Object(); // 同步锁
private static Message sPool; // 回收池定义,使用的是链表的设计。
private static int sPoolSize = 0; // 池子大小private static final int MAX_POOL_SIZE = 50; // 池中最多可放置的 Message 数量是50个@UnsupportedAppUsage
void recycleUnchecked() {// ...... 主要是重置 Message 的属性。// 下面开始回收 Message 对象,将它放入到池子中。synchronized (sPoolSync) {if (sPoolSize < MAX_POOL_SIZE) {next = sPool;sPool = this;sPoolSize++;}}
}
上面是 Message
部分代码,主要说明:
Message
池的采用的是链表的结构设计。- 池子最大容量是 50。
- 每次回收的
Message
对象都是在链表头部进行插入。 Message
对象回收时,需要进行线程的同步考虑。
关于 Message
MessageQueue
部分先分析这些。
更多推荐
MessageMessageQueue分析
发布评论