深入理解安卓消息队列机制(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。

相关推荐
小比卡丘1 小时前
C语言进阶版第17课—自定义类型:联合和枚举
android·java·c语言
前行的小黑炭2 小时前
一篇搞定Android 实现扫码支付:如何对接海外的第三方支付;项目中的真实经验分享;如何高效对接,高效开发
android
_.Switch3 小时前
Python机器学习:自然语言处理、计算机视觉与强化学习
python·机器学习·计算机视觉·自然语言处理·架构·tensorflow·scikit-learn
落落落sss3 小时前
MybatisPlus
android·java·开发语言·spring·tomcat·rabbitmq·mybatis
代码敲上天.4 小时前
数据库语句优化
android·数据库·adb
GEEKVIP6 小时前
手机使用技巧:8 个 Android 锁屏移除工具 [解锁 Android]
android·macos·ios·智能手机·电脑·手机·iphone
feng_xiaoshi7 小时前
【云原生】云原生架构的反模式
云原生·架构
model20058 小时前
android + tflite 分类APP开发-2
android·分类·tflite
彭于晏6898 小时前
Android广播
android·java·开发语言
架构师吕师傅9 小时前
性能优化实战(三):缓存为王-面向缓存的设计
后端·微服务·架构