Message&MessageQueue分析

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.java

public 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.java

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);
}

最终调用的是 MessageQueue.enqueueMessage(Message msg, long when) 方法。在这个方法中,同时决定了是否唤醒线程。

Message

Looperlooper() 方法无限循环中,在 Message msg = queue.next(); 返回获取到 msg 对象后,接着会调用到 msg.target.dispatchMessage(msg); 执行 Handler.dispatchMessage(Message msg) 方法。

// Handler.java

public 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.java

final 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.java

synchronized(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 部分先分析这些。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

VoidHope

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值