深入理解安卓消息队列机制(2)Java 层 Looper

1 消息队列机制和事件驱动

从 Looper.cpp 的分析我们可以看到,消息队列主要目的是为进程内的各个模块,提供一种异步通信机制:

1 发送方和接收方可以处于同一个线程,也可以处于不同线程;

2 通过发送 Message,发送方和接收方可以处于一种 "松耦合" 状态;

3 想象一下:

如果没有这种 "松耦合" 机制,程序都需要通过类似函数调用的 "同步" 方式,持续占用 CPU,持续执行。

而作为安卓系统中的应用程序是 "事件"驱动的:用户的输入事件,网络访问事件,文件读取事件等等。

消息队列机制,正是处理这种事件驱动的业务一种最合适的设计模式。

2 Java 层消息队列机制

为了给 Java 层提供消息队列机制,Android 系统对 native Looper 进行了封装,主要涉及到如下代码:

bash 复制代码
frameworks/base/core/java/android/app/ActivityThread.java
frameworks/base/core/java/android/os/Looper.java
frameworks/base/core/java/android/os/MessageQueue.java
frameworks/base/core/java/android/os/Message.java
frameworks/base/core/java/android/os/Handler.java
frameworks/base/core/jni/android_os_MessageQueue.cpp
frameworks/base/core/jni/android_os_MessageQueue.h

上面各个文件涉及到的对象的关系,可以用下图表示:

1 Looper.java

Java 层封装的 Looper 对象,也是线程唯一的 (TLS),在 ActivityThread 开始时就初始化了主线程的 Looper。

用户程序中自己新建的线程,没有 Looper,需要主动调用 Looper.prepare 才能为当前线程创建 Looper

2 MessageQueue.java

Looper 中的 MessageQueue 就是 Java 层的消息队列,注意这里Java 层的消息以链表的形式直接存储在 MessageQueue 中,并不会传输到 Native 层。

MessageQueue 中的成员变量 mMessages 就是这个存储 Message 的链表。

ini 复制代码
Message mMessages;

3 Message.java

Message.java 就是消息队列中的消息模型,是抽象消息的载体,如下几个字段就是具体运输消息的容器:

arduino 复制代码
public class Message {
    public int what;
    public int arg1;
    public int arg2;
    public int arg3;
    public Object obj;
    Bundle data;
}

Message 的 target 和 callback 就是这条消息的处理器,在 Looper 消息循环中,依次调用每个 Message 的 target 处理对应消息。

kotlin 复制代码
public class Message {
    ......
    Handler target;
    Runnable callback;
}

从这里也可以看出,为什么我在上图中没有画 Handler ? 因为从逻辑上讲,Handler 对象只是 Message 的一个成员,负责处理对应的 Message

Message 包含 next 字段,本身定义为链表;在 MessageQueue 中,Message mMessages 就是真正的 "消息队列"。

ini 复制代码
Message next;

Message 包含 when 字段,是通过 SystemClock.uptimeMillis 定义的消息分发时间。在消息队列中,消息是按照时间顺序从小到大排列的。

csharp 复制代码
    public long when;

4 Native MessageQueue:

MessageQueue 对应的 JNI 对象,为了便于和 Native Looper 建立联系

5 Native Looper:

消息队列机制的核心,通过 epoll 机制提供事件的等待和唤醒机制,之前已经重点分析,详见:

深入理解安卓消息队列机制(1)Native层 Looper

6 epoll:

Linux 内核提供的 IO 多路复用机制,Looper 借用这个机制主要实现事件的监听和线程的休眠唤醒。

2.1 主线程 Looper 建立流程

Looper 是在主线程 ActivityThread创建开始就初始化的,主要工作是通过 Looper.prepare 建立 epoll 对象,并通过 Looper.loop 持续监听消息变化。

2.1.1 主线程 Looper.prepareMainLooper 流程

  1. 在 ActivityThread.java 的 main() 函数中,会初始化 Looper 对象
typescript 复制代码
ActivityThread.java 
    public static void main(String[] args) {
            ......
            Looper.prepareMainLooper();
            Looper.loop();
    }
  1. Looper.java 中,会新建一个 Looper 对象,并通过 TLS 机制保存为线程唯一实例
arduino 复制代码
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            sMainLooper = myLooper();
        }
    }
    
    private static void prepare(boolean quitAllowed) {
        sThreadLocal.set(new Looper(quitAllowed));
    }
    
     static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
  1. Looper 构造函数中,会新建一个 MessageQueue对象
arduino 复制代码
    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
    }
  1. MessageQueue 通过 JNI 方法 nativeInit,构造 NativeMessageQueue 对象;

Java 层保存Native 对象的指针 mPtr

scss 复制代码
    MessageQueue(boolean quitAllowed) {
        mPtr = nativeInit();
    }
  1. android_os_MessageQueue.cpp 中可以查看 nativeInit 具体流程:

就是新建一个 NativeMessageQueue 对象,强引用 +1,

scss 复制代码
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
    NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
    nativeMessageQueue->incStrong(env);
    return reinterpret_cast<jlong>(nativeMessageQueue);
}
  1. NativeMessageQueue 作用:

新建一个 Native Looper 对象,作为线程单例;简单而言就是建立了一个 epoll 对象。

具体流程可以参考第一节:Native Looper 的分析。

ini 复制代码
NativeMessageQueue::NativeMessageQueue() :
        mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
    mLooper = Looper::getForThread();
    if (mLooper == NULL) {
        mLooper = new Looper(false);
        Looper::setForThread(mLooper);
    }
}

2.1.2 主线程 Looper.loop 流程

上面6步,完成了 Looper.prepare 过程。接下来,ActivityThread 调用 Looper.loog 开始监听消息;

监听过程和 Native Looper 过程类似。

  1. ActivityThread 调用 Java 层的 Looper.loop()
typescript 复制代码
ActivityThread.java 
    public static void main(String[] args) {
            ......
            Looper.prepareMainLooper();
            Looper.loop();
    }
  1. loop 是个死循环,持续通过 epoll 监听 fd 的变化:
csharp 复制代码
    public static void loop() {
        ......
        for (;;) {
            if (!loopOnce(me, ident, thresholdOverride)) {
                return;
            }
        }
    }
  1. loopOnce 核心逻辑如下:

从 MessageQueue 中获取一个 Message 并通过 msg.target 的 dispatchMessge 分发处理消息。

msg.target 就是我们熟悉的与这个Message关联的 Handler, 而 dispatchMessage 最终会调用 Handler.handleMessage

java 复制代码
    private static boolean loopOnce(final Looper me,
            final long ident, final int thresholdOverride) {
        Message msg = me.mQueue.next(); // might block
        try {
            msg.target.dispatchMessage(msg);
        } cache (Exception e) {
        }
     }
  1. MessageQueue.next()

从 next 方法从MessageQueue 中获取下一个消息,如果没有消息,或者消息还没有到分发时间,线程会被暂时 block.

简化后的 next 逻辑如下:

ini 复制代码
    Message next() {
          for (;;) {
              // 没有消息会被底层 epoll_wait 暂时 block
              nativePollOnce(ptr, nextPollTimeoutMillis); 
              // 从消息队列中取下一个消息
              Message msg = mMessages;
              // ...... 如果 msg 到了分发时间 ...... 
              mMessages = msg.next;
              msg.next == null;
              return msg;
          }
    }
  1. 其中 nativePollOnce 最终调用的是 native 层的 Looper.pollOnce 方法;

Looper.pollOnce 最终等待的是 epoll_wait 事件,前文已经分析,不再赘述。

scss 复制代码
static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
        jlong ptr, jint timeoutMillis) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}

2.1.2 Q:Java 层的 Looper 和 Native 层的 Looper 是否是同一个对象?

从上面的分析可以看出: Java 层的 Looper.java 和 Natvive 层的 Looper 显然不是同一个对象。

他们的关系如下:

1 两者都是线程唯一的,Java 对象通过 ThreadLocal 方法,Native 对象通过 pthread 提供的方法分别实现了 TLS 的功能。

2 Java 层的消息存储在 MessageQueue 的 mMessages 中,是一个链表;Native 层的消息存储在 m层Vector mMessageEnvelo 中。

3 epoll 对象是同一个,因此如果 epoll_wait 被唤醒,Java Looper 和 Native Looper 都会唤醒,开始分发消息。pes

2.1.3 Q:Java 层调用 nativePollOnce 似乎没有干什么事?

确实,Java 层依赖 natvicePollOnce 其实只是依赖 epoll_wait 进行线程的休眠和唤醒。Object.wait 和 Object.notify 也可以实现此功能。

为什么要 Java 层的 Looper 依赖 Native 层的 Looper?猜测是因为设计者希望 Java Looper 和 Native Looper 使用同一个锁去休眠唤醒线程。

2.1.4 Java 层和 Native 层的对象对应关系

Java层 Native 层
Looper + MessageQueue Looper 消息队列
Message MessageEnvelope 消息+回调
Handler MessageHandler 消息处理回调
Message.what Message 消息

2.2 Looper 发送/接收消息流程

发送消息的过程分为两步:

1 构造一个 Message, 填充上对应的载体信息

2 指定消息对应的Handler,确定了Handler,就确定了 a. Looper 消息发送到哪个消息队列;b. 消息分发时由谁处理时

2.2.1 构造一个消息 Message 并发送

Message 提供了一组 obtain 方法可以构造 Message 消息体,一个典型的构造函数如下:

ini 复制代码
    public static Message obtain(Handler h, int what, int arg1, int arg2) {
        Message m = obtain();
        m.target = h;
        m.what = what;
        m.arg1 = arg1;
        m.arg2 = arg2;
        return m;
    }

构造完成后,sendToTarget 通过 Message 绑定的 Handler 发送消息:

scss 复制代码
    Message.obtain(mApplyHandler, MSG_UPDATE_SEQUENCE_NUMBER, toApplySeqNo, 0)
                            .sendToTarget();
csharp 复制代码
    public void sendToTarget() {
        target.sendMessage(this);
    }

2.2.2 Handler.sendMessage 过程

  1. Handler 在构造时,会默认关联上当前线程的 Looper:
ini 复制代码
    public Handler() {
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
    }

从构造函数,我们可以看到,如果不在主线程 new Handler(), 例如在 binder 线程中, 很有可能会遇到 RuntimeException 错误:

scss 复制代码
Can't create handler inside thread " + Thread.currentThread() + " that has not called Looper.prepare()
  1. sendToTarget 最终通过 sendMessageAtTime 把 Message 加入消息队列
less 复制代码
    public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        return enqueueMessage(queue, msg, uptimeMillis);
    }
  1. enqueueMessage 的核心逻辑,是根据 Message.when 分发时间,插入到消息队列 (链表 mMessages)当中
ini 复制代码
boolean enqueueMessage(Message msg, long when) {
    if (p == null || when == 0 || when < p.when) {
        // 插入到链表头
        // New head, wake up the event queue if blocked.
        msg.next = p;
        mMessages = msg;
    } else {
        // 插入到链表中间
        // 找到链表结束的位置
        for (;;) {
            prev = p;
            p = p.next;
            if (p == null || when < p.when) {
                break;
            }
        }
        // 插入 Message
        msg.next = p; // invariant: p == prev.next
        prev.next = msg;
    }
}
  1. 一般情况下,会主动通过 nativeWake 主动唤醒一次 epoll_wait,触发一次消息分发
scss 复制代码
nativeWake(mPtr);
// 最终调用 native Looper 的 wake()
void NativeMessageQeue::wake() {
    mLooper->wake();
}

Native 的 Looper 唤醒的逻辑,就是往 epoll fd 上,写入一个 1,从而唤醒 epoll_wait

2.2.3 Handler.dispatchMessage 过程

  1. 接收消息的过程是自下而上触发的,前文说到,Looper 在创建以后,通过 Looper.loop 一直保持监听状态:说到,
scss 复制代码
    Message next() {
        for (;;) {
            // wake 以后,这里就会返回;否则一直 polling
            nativePollOnce(ptr, nextPollTimeoutMillis);
        }
    }
  1. 紧接着在,nativePollOnce 以后,在 mMessages 消息队列中,找到下一个需要分发的消息,作为 MessageQueue.next() 的返回值。
ini 复制代码
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null) {
                    if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        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;
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }
  1. Looper.java 的 loopOnce 获得 MessageQueue 中获取的 msg,通过 target.dispatchMessage 进行分发
java 复制代码
    private static boolean loopOnce(final Looper me,
            final long ident, final int thresholdOverride) {
        Message msg = me.mQueue.next(); // might block
        msg.target.dispatchMessage(msg);
        }
  1. Handler.dispatchMessage 被调用:
less 复制代码
    public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            handleMessage(msg);
        }
    }

Handler 判断是应该调用 Runable 回调,还是走 handlerMessage 方法。

到此, Java 层 Looper 发送和接收消息全流程,分析完成。

3 其他问题

3.1 Message 内存池

在应用程序中,Message 对象会被经常创建和销毁。Android 系统通过内存池机制,避免大量创建和销毁导致触发 Java GC,从而降低效率。

java 复制代码
    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.java 中可以看到,静态变量 sPool 保存了已经创建的 Message, 并通过链表的形式存其来,最多保存 50 个 Message对象。

如果超过 50 个,就Message 不再缓存在 sPool 中

新建 Message 对象,sPoll 有对象优先使用。

ini 复制代码
    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

销毁 Message 对象:对象还给 sPool

csharp 复制代码
    void recycleUnchecked() {
        // Mark the message as in use while it remains in the recycled object pool.
        // Clear out all other details.
        ......
         synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }

3.2 如何撤销一条消息

如果需要撤销一条消息,首先在 MessageQueue 需要能识别出这条消息:

识别消息有两种方式:

1 Handler + msg.what

2 Handler + runnable callback

ini 复制代码
    void removeMessages(Handler h, int what, Object object) {
            Message p = mMessages;
            // Remove all messages at front.
            while (p != null && p.target == h && p.what == what
                   && (object == null || p.obj == object)) {
                Message n = p.next;
                mMessages = n;
                p.recycleUnchecked();
                p = n;
            }
    }

3.3 Handler 同步屏障机制 同步Sync Barrier

Handler之同步屏障机制(sync barrier)_handler同步屏障原理-CSDN博客

默认 Handler 都是同步消息,如果发出了同步屏障,MessageQueue 中所有的同步消息都不会被处理;直到屏障解除。

例如:ViewRootImpl 中,绘制界面时,会发出同步屏障,优先处理屏幕渲染消息。

php 复制代码
    /**
     * Posts a synchronization barrier to the Looper's message queue.
     *
     * Message processing occurs as usual until the message queue encounters the
     * synchronization barrier that has been posted.  When the barrier is encountered,
     * later synchronous messages in the queue are stalled (prevented from being executed)
     * until the barrier is released by calling {@link #removeSyncBarrier} and specifying
     * the token that identifies the synchronization barrier.
     *
     * This method is used to immediately postpone execution of all subsequently posted
     * synchronous messages until a condition is met that releases the barrier.
     * Asynchronous messages (see {@link Message#isAsynchronous} are exempt from the barrier
     * and continue to be processed as usual.
     *
     * This call must be always matched by a call to {@link #removeSyncBarrier} with
     * the same token to ensure that the message queue resumes normal operation.
     * Otherwise the application will probably hang!
     *
     * @return A token that uniquely identifies the barrier.  This token must be
     * passed to {@link #removeSyncBarrier} to release the barrier.
     *
     * @hide
     */

3.4 IdleHandler

IdleHandler 是什么?怎么使用,能解决什么问题?-CSDN博客

java 复制代码
    final class GcIdler implements MessageQueue.IdleHandler {
        @Override
        public final boolean queueIdle() {
            doGcIfNeeded();
            purgePendingResources();
            return false;
        }
    }

3.5 Messenger 跨进程 Handler

Handler.sendMessage 可以在进程内部进行异步通信,Messenger 是Handler 的跨进程版本。

如果通过 binder 接口可以获取另外一个进程的 Messenger 对象,那么可以直接通过 Messenger.send 发送消息给其他进程

java 复制代码
 Messenger.send()
 
    public void send(Message message) throws RemoteException {那么可以
        mTarget.send(message);
    }

4 总结

Looper 时安卓系统提供的消息队列机制,利用 linux 的 epoll 作为工具: epoll_wait 进行线程等待;write(fd) 进行线程唤醒。

Java 层的 Looper 是对 native Looper 的封装。Message 和 Handler 是提供给 Java 层开发人员的 收发消息的API。

相关推荐
刘立军11 分钟前
本地大模型编程实战(33)用SSE实现大模型的流式输出
架构·langchain·全栈
一直_在路上23 分钟前
Go 语言微服务演进路径:从小型项目到企业级架构
架构·go
黄林晴2 小时前
如何判断手机是否是纯血鸿蒙系统
android
火柴就是我2 小时前
flutter 之真手势冲突处理
android·flutter
法的空间3 小时前
Flutter JsonToDart 支持 JsonSchema
android·flutter·ios
循环不息优化不止3 小时前
深入解析安卓 Handle 机制
android
恋猫de小郭3 小时前
Android 将强制应用使用主题图标,你怎么看?
android·前端·flutter
jctech3 小时前
这才是2025年的插件化!ComboLite 2.0:为Compose开发者带来极致“爽”感
android·开源
用户2018792831673 小时前
为何Handler的postDelayed不适合精准定时任务?
android
叽哥4 小时前
Kotlin学习第 8 课:Kotlin 进阶特性:简化代码与提升效率
android·java·kotlin