MessageMessageQueue分析

编程入门 行业动态 更新时间:2024-10-26 10:29:10

MessageMessageQueue分析

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() 的执行过程:

  1. Looper 类被加载到内存,静态变量 sMainLooper 同时会被加载到内存并初始化为 null,常量 sThreadLocal 被加载到内存并创建实例 ( 即 new ThreadLocal<Looper>() ) 。( static 成员 (变量,方法) 的加载是 JVM 知识 )
  2. 方法调用 prepare(false) 中创建 Looper 对象,在私有构造方法中创建实例相关的 MessageQueue 对象 mQueue ,以及获取当前线程 mThread 。 也就是 **一个 Looper ,对应一个 MessageQueue **。
  3. 将创建的 Looper 对象设置到 sThreadLocal 对象。
  4. 获取 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(;;) 中几点:

  1. nativePollOnce(ptr, nextPollTimeoutMillis) 函数是 MessageQueue 中的一个本地方法,其主要功能是在消息队列中进行一次轮询。函数会阻塞当前线程,等待消息队列中的消息到达或者超时。它会等待指定的时间(由 nextPollTimeoutMillis 指定),如果在超时时间内有消息到达,则会立即返回;如果超时时间到达而没有消息到达,则会继续执行后续的逻辑。

    它的作用是实现消息队列的阻塞等待机制,以便在没有消息到达时,让线程进入休眠状态,避免空闲循环的浪费。当有消息到达时,它会唤醒线程,使其继续执行后续的消息处理逻辑。更确切的说,它使用的是 Linux 内核中的 epoll 机制实现无消息时等待,有消息时唤醒线程。

  2. 有消息到达:

    1. nativePollOnce(ptr, nextPollTimeoutMillis) 方法返回,判断消息是否是屏障 ( msg.target == null ),若是屏障消息,则尝试在其后面的消息中找到异步消息。且 prevMesgmsg 向后移动,直到找到异步消息,或直到最后再也没有消息了。
    2. 有消息过来 ( msg != null ),判断是否是 deplayed 消息,若是仅设置 nextPollTimeoutMillis 下载唤醒线程的时长。且继续往下,执行 Idle Handler 调用程序。
    3. 有消息,但不是 deplayed 消息,说明当前 msg 消息是需要返回到 Looper 处理的消息。进一步判断 if (prevMsg != null) ,也就是判断是否是异步消息。并且将 msg 对象从 Message 链表中移除并返回给 Looper 程序,即结束 next() 方法。
  3. 没有消息,且 nativePollOnce(ptr, nextPollTimeoutMillis) 等待时间到了,也会返回,并且执行后续的 Idle Handler 程序。

  4. 调用 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

Looperlooper() 方法无限循环中,在 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 是因为它的设计中采用了池的实现方式。常用的创建方式:

  1. Message.obtain(): obtain() 有多个重载方法,且这些方法是静态的。它会从 Message 池中去获取一个可用的 Message 对象。若没有可用的对象,随即会创建一个新的 Message 对象。使用这种方式创建的 Message 对象可以被重复使用,避免了频繁地创建和销毁对象,提高了性能。
  2. new Message(): 使用 Message 构造方法创建一个新的对象。
  3. Handler.obtain(): 使用 Handlerobtain() 方法创建,这种方式的实现本质上还是调用的 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 部分代码,主要说明:

  1. Message 池的采用的是链表的结构设计。
  2. 池子最大容量是 50。
  3. 每次回收的 Message 对象都是在链表头部进行插入。
  4. Message 对象回收时,需要进行线程的同步考虑。

关于 Message MessageQueue 部分先分析这些。

更多推荐

MessageMessageQueue分析

本文发布于:2023-12-08 08:29:25,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1672602.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:MessageMessageQueue

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!