Android 消息机制详解(Android P)

前言

Android 消息机制,一直都是 Android 应用框架层非常重要的一部分,想更加优雅的进行 Android 开发,我想了解消息机制是非常必要的一个过程,此前也分析过很多次 Handler 消息机制, 不过都是浮于 Java 层,对于底层的源码并没有深究,经过一年的努力,笔者对于 Android 应用框架层有了新的认识和理解,这里重新写下这篇文章。
本文主要从以下几点来阐述 Androd 消息机制

  • 线程消息队列的创建入口
  • 消息循环的准备
  • 消息循环的启动
  • 消息的发送与处理

一. 线程消息队列创建入口

我们知道, 应用进程主线程初始化的入口是在 ActivityThread.main() 中, 我们看看他是如何构建消息队列的

public class ActivityThread {

    static volatile Handler sMainThreadHandler;  // set once in main()

    public static void main(String[] args) {
        ......
        // 1. 做一些主线程消息循环的初始操作
        Looper.prepareMainLooper();
        
        ......
        
        // 2. 启动消息循环
        Looper.loop();
    }
    
}
复制代码

好的, 可以看到 ActivityThread 中的消息循环构建过程如下

  • 调用 Looper.prepareMainLooper, 做一些准备操作
  • 调用 Looper.loop 真正的开启了消息循环

接下来我们先看看准备操作中做了些什么

二. 消息循环的准备

public final class Looper {

    private static Looper sMainLooper;  // guarded by Looper.class
    
    public static void prepareMainLooper() {
        // 1. 调用 prepare 方法真正执行主线程的准备操作
        prepare(false);
        
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            // 2. 调用了 myLooper 方法, 获取一个 Looper 对象给 sMainLooper 赋值
            sMainLooper = myLooper();
        }
    }

    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        // 1.1 new 了一个 Looper 对象
        // 1.2 将这个 Looper 对象写入 ThreadLocal 中
        sThreadLocal.set(new Looper(quitAllowed));
    }
    
    public static @Nullable Looper myLooper() {
        // 2.1 通过 ThreadLocal 获取这个线程中唯一的 Looper 对象
        return sThreadLocal.get();
    }
    
    final MessageQueue mQueue;
    final Thread mThread;
    
    private Looper(boolean quitAllowed) {
        // 创建了一个消息队列
        mQueue = new MessageQueue(quitAllowed);
        // 获取了当前的线程
        mThread = Thread.currentThread();
    }
    
}
复制代码

可以看到 Looper.prepareMainLooper 中主要做了如下几件事情

  • 调用 prepare 方法真正执行主线程的准备操作
    • 创建了一个 Looper 对象
      • 创建了 MessageQueue 这个消息队列, 保存到成员变量 mQueue 中
      • 获取该对象创建线程, 保存到成员变量 mThread 中
    • 将这个 Looper 对象存入 ThreadLocal 中
  • 调用 myLooper 方法, 从 ThreadLocal 中获取刚刚写入的 Looper 对象
  • 将这个 Looper 对象, 保存到静态变量 sMainLooper 中, 表示这个是当前应用进程主线程的 Looper 对象

好的, 可以看到在创建 Looper 对象的时候, 同时会创建一个 MessageQueue 对象, 将它保存到 Looper 对象的成员变量 mQueue 中, 因此每一个 Looper 对象都对应一个 MessageQueue 对象

我们接下来看看 MessageQueue 对象创建时, 做了哪些操作

MessageQueue 的创建

public final class MessageQueue {
    
    private final boolean mQuitAllowed; // true 表示这个消息队列是可停止的
    private long mPtr;                  // 描述一个 Native 的句柄
    
    MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        // 获取一个 native 句柄
        mPtr = nativeInit();
    }
    
    private native static long nativeInit();

}
复制代码

好的, 可以看到 MessageQueue 对象在创建的过程中, 会调用 nativeInit 来获取一个 native 的句柄, 我们看看这个 nativeInit 做了哪些操作

// frameworks/base/core/jni/android_os_MessageQueue.cpp
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
    // 1. 创建了一个 NativeMessageQueue 对象
    NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
    ......
    // 增加 env 对它的强引用计数
    nativeMessageQueue->incStrong(env);
    // 2. 将这个 NativeMessageQueue 对象强转成了一个句柄返回 Java 层
    return reinterpret_cast<jlong>(nativeMessageQueue);
}


class NativeMessageQueue : public MessageQueue, public LooperCallback {
public:
    NativeMessageQueue();
    ......
private:
    JNIEnv* mPollEnv;
    jobject mPollObj;
    jthrowable mExceptionObj;
};

NativeMessageQueue::NativeMessageQueue() :
        mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
    // 1.1 尝试调用 Looper.getForThread 获取一个 C++ 的 Looper 对象
    mLooper = Looper::getForThread();
    // 1.2 若当前线程没有创建过 Looper, 则走这个分支
    if (mLooper == NULL) {
        // 创建 Looper 对象
        mLooper = new Looper(false);
        // 给当前线程绑定这个 Looper 对象
        Looper::setForThread(mLooper);
    }
}
复制代码

好的可以看到 nativeInit 方法主要做了如下的操作

  • 创建了一个 C++ 的 NativeMessageQueue 对象
    • 尝试通过 Looper.getForThread 获取一个 C++ 的 Looper 对象
    • 若当前线程没有创建过 Looper
      • new 一个 C++ 的 Looper 对象
      • 给当前线程绑定 Looper 对象

可以看到这里的流程与 Java 的相反

  • Java 是先创建 Looper, 然后在 Looper 内部创建 MessageQueue
  • Native 是先创建 NativeMessageQueue, 在其创建的过程中创建 Looper

接下来看看这个 C++ 的 Looper 对象在实例化的过程中做了哪些事情

Looper(Native) 的实例化

// system/core/libutils/Looper.cpp
Looper::Looper(bool allowNonCallbacks) :
        mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
        mPolling(false), mEpollFd(-1), mEpollRebuildRequired(false),
        mNextRequestSeq(0), mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
    // 1. 创建 pipe 管道, 返回该管道文件读写的描述符
    mWakeEventFd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
    AutoMutex _l(mLock);
    // 处理 epoll 相关事宜
    rebuildEpollLocked();
}

void Looper::rebuildEpollLocked() {
    ......
    // 2. 创建一个 epoll 对象, 将其文件描述符保存在成员变量 mEpollFd 中
    mEpollFd = epoll_create(EPOLL_SIZE_HINT);
    // 3. 将 pipe 管道的文件读写描述符 mWakeEventFd, 添加到 epoll 中, 让 epoll 对该管道的读写进行监听
    struct epoll_event eventItem;
    memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
    eventItem.events = EPOLLIN;
    eventItem.data.fd = mWakeEventFd;
    int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);
    ......
}
复制代码

好的, 可以看到 Looper 实例化时做了如下的操作

  • 调用 eventfd 创建了一个 pipe 管道, 返回了该管道的文件读写描述符
  • 创建了一个 epoll 对象
  • 将 pipe 管道的文件读写描述符 mWakeEventFd, 添加到 epoll 中, 让 epoll 对该管道的读写进行监听

可以看到这里引入了两个新的名词, pipe 管道管道监听管理对象 epoll

  • pipe 管道: 这个管道在一个线程的消息循环过程中起到的作用非常大

    • 当一个线程没有新的消息需要处理时, 它就会睡眠在这个管道的读端文件描述符上, 直到有新的消息需要处理为止
    • 当其他线程向这个线程发送了一个消息之后, 其他线程就会通过这个管道的写端文件描述符写入一个数据, 从而将这个线程唤醒, 以便它可以对刚才发送到它消息队列中的消息进行处理
  • epoll: epoll 机制是 Linux 为了同时监听多个文件描述符的 IO 读写事件而设计的 多路复用 IO 接口

    • 当 epoll 监听了大量文件描述符的 IO 读写事件, 但只有少量的文件描述符是活跃的, 那么这个 epoll 会显著的减少 CPU 的使用率

相互依赖的结构图

到这里消息循环的准备工作就已经完成了, 这里分析一下它们的结构图

消息循环相互依赖关系图

三. 消息循环的启动

public final class Looper {
    
    public static void loop() {
        // 1. 获取调用线程的 Looper 对象
        final Looper me = myLooper();
        ......
        // 2. 获取 Looper 对应的消息队列
        final MessageQueue queue = me.mQueue;
        // 3. 死循环, 不断的处理消息队列中的消息
        for (;;) {
            // 3.1 获取消息队列中的消息, 取不到消息会阻塞
            Message msg = queue.next(); // might block
            if (msg == null) {
                // 若取到的消息为 null, 这个 Looper 就结束了
                return;
            }
            ......
            try {
                // 3.2 处理消息
                msg.target.dispatchMessage(msg);
            } finally {
                ......
            }
            ......
        }
    }
    
}
复制代码

好的, 可以看到 Looper 的 loop 方法主要做了如下几件事情

  • 从方法调用的线程中取 Looper 对象
  • 获取这个 Looper 对应的消息队列
  • 通过死循环, 不断地处理消息队列中的数据
    • 通过 MessageQueue.next() 获取下一条要处理的 Msg
    • 通过 msg.target.dispatchMessage(msg); 分发处理消息(本次不细究)

好的, 可以看到获取消息的方式是通过 MessageQueue.next 拿到的, 我们接下来看看它是如何获取的

从队列中取消息

public final class MessageQueue {
    
    Message next() {
        // 获取 NativeMessageQueue 的句柄
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }
        // 描述空闲事件处理者的数量, 初始值为 -1 
        int pendingIdleHandlerCount = -1; 
        // 描述当前线程没有新消息处理时, 可睡眠的时间
        int nextPollTimeoutMillis = 0;
        // 死循环, 获取可执行的 Message 对象
        for (;;) {
            // 1. 若 nextPollTimeoutMillis 不为 0, 则说明距离下一个 Message 执行, 有一定的时间间隔
            if (nextPollTimeoutMillis != 0) {
                // 在空闲期间, 执行 Binder 通信相关的指令
                Binder.flushPendingCommands();
            }
            // 2. 这里调用了 nativePollOnce, 在 native 层检查消息队列中是否有 msg 可读, 若无可执行的 msg, 则执行线程的睡眠, 时间由 nextPollTimeoutMillis 决定
            nativePollOnce(ptr, nextPollTimeoutMillis);
            // 3. 取队列中下一条要执行的 Message 对象, 并返回出去
            synchronized (this) {
                final long now = SystemClock.uptimeMillis(); // 获取当前时刻
                Message prevMsg = null;
                Message msg = mMessages;
                // 3.1 移除消息队列中无效的 Message 
                // Condition: msg 不为 null & msg 的处理者为 null
                if (msg != null && msg.target == null) {
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                // 3.2 队列首部存在有效的 msg 
                if (msg != null) {
                    // 3.2.1 若当前的时刻 早于 队首消息要执行的时刻
                    if (now < msg.when) {
                        // 给 nextPollTimeoutMillis 赋值, 表示当前线程, 可睡眠的时间
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // 3.2.2 若当前的时刻 不小于 队首消息要执行的时刻
                        mBlocked = false;// 更改标记位置
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        // 将队首消息返回出去
                        return msg;
                    }
                } else {
                    // 3.3 说明消息队列中无消息, 则给 nextPollTimeoutMillis 置为 -1, // 表示可以无限睡眠, 直至消息队列中有消息可读
                    nextPollTimeoutMillis = -1;
                }
                // 4. 获取一些空闲事件的处理者
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                // 若无空闲事件, 则进行下一次 for 循环
                if (pendingIdleHandlerCount <= 0) {
                    mBlocked = true;
                    continue;
                }
                .......
            }

            // 4.1 处理空闲事件
            ......
            
            // 4.2 走到这里, 说明所有的空闲事件都已经处理好了
            // 将需要处理的空闲事件,置为 0 
            pendingIdleHandlerCount = 0;

            // 5. 因为处理空闲事件是耗时操作, 期间可能有新的 Message 入队列, 因此将可睡眠时长置为 0, 表示需要再次检查
            nextPollTimeoutMillis = 0;
        }
    }
    
    // native 方法
    private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/
 
}
复制代码

可以看到 MessageQueue.next 内部维护了一个死循环, 用于获取下一条 msg, 这个 for 循环做了如下的操作

  • 若 nextPollTimeoutMillis 不为 0, 则说明距离下一个 Message 执行, 有一定的时间间隔
    • 在空闲期间, 执行 Binder 通信相关的指令
  • 调用 nativePollOnce 根据 nextPollTimeoutMillis 时长, 执行当前线程的睡眠操作
  • 取队列中下一条要执行的 Message
    • 移除消息队列中无效的 Message
    • 队列首部存在有效的 msg
      • 若当前的时刻 < 队首消息要执行的时刻
        • 则更新 nextPollTimeoutMillis, 下次进行 for 循环时, 会进行睡眠操作
      • 若当前的时刻 >= 队首消息要执行的时刻
        • 则将队首消息的描述返回出去
    • 队列首部不存在有效的 msg
      • 将 nextPollTimeoutMillis 置为 -1, 下次 for 循环时可以无限睡眠, 直至消息队列中有消息可读
  • 空闲消息的获取和处理(本次不细究)
    • 获取空闲事件的处理者
    • 若无空闲事件, 则进行下一次 for 循环
    • 若存在空闲事件, 则处理空闲事件
      • 将 pendingIdleHandlerCount 置为 0, 表示空闲事件都已经处理完成了
      • 将 nextPollTimeoutMillis 置为 0, 因为处理空闲事件是耗时操作, 期间可能有新的 Message 入队列, 因此将可睡眠时长置为 0, 表示需要再次检查

至此一次 for 循环就结束了, 可以看到 Message.next() 中其他的逻辑都非常的清晰, 但其睡眠是一个 native 方法, 我们继续看看它的内部实现

消息队列中无消息时线程睡眠的实现

// frameworks/base/core/jni/android_os_MessageQueue.cpp
static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
        jlong ptr, jint timeoutMillis) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    // 调用了 NativeMessageQueue 的 pollOnce
    nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}

void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
    ......
    // 1. 调用了 Looper 的 pollOne
    mLooper->pollOnce(timeoutMillis);
    ......
}

// system/core/libutils/Looper.cpp
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
    int result = 0;
    for (;;) {
        ......
        // result 不为 0 说明读到了消息
        if (result != 0) {
            ......
            return result;
        }
        // 2. 若未读到消息, 则调用 pollInner
        result = pollInner(timeoutMillis);
    }
}


int Looper::pollInner(int timeoutMillis) {
    ......
    int result = POLL_WAKE;
    ......
    struct epoll_event eventItems[EPOLL_MAX_EVENTS];
    // 3. 调用 epoll_wait 来监听 pipe 中的 IO 事件, 若无事件, 则睡眠在文件读操作上, 时长由 timeoutMillis 决定
    int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
    // 4. 走到这里, 说明睡眠结束了
    for (int i = 0; i < eventCount; i++) {
        int fd = eventItems[i].data.fd;              // 获取文件描述
        uint32_t epollEvents = eventItems[i].events;
        // 5. 若为唤醒事件的文件描述, 则执行唤醒操作
        if (fd == mWakeEventFd) {
            if (epollEvents & EPOLLIN) {
                awoken();// 唤醒
            } else {
                ......
            }
        } else {
           ......
        }
    }
    ......
    return result;
}
复制代码

好的可以看到 JNI 方法 nativePollOnce, 其内部流程如下

  • 将 Poll 操作转发给了 Looper.pollOne
  • 若未读到消息, 则调用 Looper.pollInner
  • 调用 epoll_wait 来监听 pipe 中的 IO 事件, 若无事件, 则睡眠在文件读操作上, 时长由 timeoutMillis 决定
  • 睡眠结束后调用 awoken 唤醒

好的, 至此线程是睡眠的机制也明了了, 这里通过 UML 图总结一下, 线程消息队列的创建与循环

线程消息的创建与循环的流程图

四. 消息的发送与处理

我们都知道, Android 系统提供了 Handler 类, 描述一个消息的处理者, 它负责消息的发送与处理

public class Handler {
   
   final Looper mLooper;
   final MessageQueue mQueue;
   final Callback mCallback;
   
   public Handler() {
       this(null, false);
   }
   
   public Handler(Callback callback, boolean async) {
       // 调用该方法的线程的 Looper
       mLooper = Looper.myLooper();
       // 这里扔出了 Runtime 异常, 因此 Handler 是无法在没有 Looper 的线程中执行的
       if (mLooper == null) {
           throw new RuntimeException(
               "Can't create handler inside thread " + Thread.currentThread()
                       + " that has not called Looper.prepare()");
       }
       // 获取消息队列
       mQueue = mLooper.mQueue;
       // 给 Callback 赋值
       mCallback = callback;
       ......
   }
   
   public final boolean sendMessage(Message msg){
       ......
   }
   
   public void handleMessage(Message msg) {
   
   }
   
}
复制代码

好的, 可以看到 Handler 的结构如上述代码所示, 本次只 focus 以下三个方法

  • 构造方法
    • 获取当前线程的 Looper
    • 获取当前线程的 MessageQueue
    • 给 Callback 赋值(这个 Callback 的作用, 在后面描述)
  • 发送消息的方法
    • sendMessage
  • 处理消息的方法
    • handleMessage

好的, 接下来我们先看看如何发送消息的

消息的发送

我们先看看, Android 中的消息是如何发送的

public class Handler {
   
   ......
   public final boolean sendMessage(Message msg) {
       // 回调了发送延时消息的方法
       return sendMessageDelayed(msg, 0);
   }
   
   public final boolean sendMessageDelayed(Message msg, long delayMillis) {
       if (delayMillis < 0) {
           delayMillis = 0;
       }
       // 回调了发送指定时刻消息的方法
       return sendMessageAtTime(msg, /*指定时刻 = 当前时刻 + 延时时间*/SystemClock.uptimeMillis() + delayMillis);
   }
   
   public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
       MessageQueue queue = mQueue;
       ......
       // 回调了入消息队列的方法
       return enqueueMessage(queue, msg, uptimeMillis);
   }
   
   private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
       // 将这个消息的处理者, 设置为其自身
       msg.target = this;
       ......
       // 调用 MessageQueue 的 enqueueMessage 执行入队列的操作
       return queue.enqueueMessage(msg, uptimeMillis);
   }
}
复制代码

可以看到发送消息的操作, 进过了一系列方法的调用, 会走到 sendMessageAtTime 中, 表示发送指定时刻的消息, 然后会调用 enqueueMessage 执行入消息队列操作

  • 将这个消息的 target 赋值为自身, 表示这个消息到时候会被当前 Handler 对象执行
  • 调用了 MessageQueue.enqueueMessage 将消息投递到消息队列中去

接下来看看 MessageQueue.enqueueMessage 做了哪些操作

public final class MessageQueue {

    boolean enqueueMessage(Message msg, long when) {
        ......
        synchronized (this) {
            ......
            msg.when = when;           // 将消息要执行的时刻写入成员变量
            Message p = mMessages;     // 获取当前队首的消息
            boolean needWake;          // 描述是否要唤醒该 MessageQueue 所绑定的线程
            // Case1: 
            // 1. p == null, 队列为空
            // 2. 入队列消息需要立即执行
            // 3. 入队列消息执行的时间 早于 当前队首消息执行的时间
            if (p == null || when == 0 || when < p.when) {
                // 将该消息放置到队首
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;  // 队首元素变更了, 有可能需要立即执行
            } else {
            // Case2: 入队列的消息执行时间 晚于 队首消息执行时间
                ......
                // 将该消息插入到消息队列合适的位置
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    // 将 needWake 置为 false, 因为该消息插入到了后面, 因此不需要唤醒
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p;
                prev.next = msg;
            }

            // 处理该消息队列绑定线程的唤醒操作
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }
    
    private native static void nativeWake(long ptr);

}
复制代码

可以看到 MessageQueue 在执行消息入队列时, 做了如下操作

  • 队列为空/入队列消息需要立即执行/入队列消息执行的时间早于当前队首消息执行的时间
    • 将该消息插入到队列的首部
    • 需要立即唤醒 MessageQueue 绑定的线程
  • 入队列的消息执行时间 晚于 队首消息执行时间
    • 将该消息按照时间从早到晚的顺序插入队列
    • 不需要立即唤醒 MessageQueue 绑定的线程
  • 根据 flag 处理该消息队列绑定线程的唤醒操作

消息入队列的过程还是很清晰明了的, 从上一篇文章的分析中我们知道, 若 MessageQueue 在当前时刻没有要执行的消息时, 会睡眠在 MessageQueue.next() 方法上, 接下来看看它是如何通过 nativeWake 唤醒的

// frameworks/base/core/jni/android_os_MessageQueue.cpp
static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    nativeMessageQueue->wake();
}

void NativeMessageQueue::wake() {
    mLooper->wake();
}

// system/core/libutils/Looper.cpp
void Looper::wake() {
    // 向 Looper 绑定的线程 pipe 管道中写入一个新的数据
    uint64_t inc = 1;
    ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t)));
    ....
}
复制代码

可以看到, nativeWake 是通过向 Looper 绑定的线程 pipe 管道中写入一个新的数据的方式唤醒目标线程的

  • 通过上一篇的分析可知, 此时 Looper::pollInner 中 epoll 在读操作上的睡眠便会停止

消息的处理

通过上一篇的分析可知, 当 MessageQueue.next 返回一个 Message 时, Looper 中的 loop 方法便会处理消息的执行, 先回顾一下代码

public final class Looper {
    
    public static void loop() {
        final Looper me = myLooper();
        ......
        final MessageQueue queue = me.mQueue;
        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // 若取到的消息为 null, 这个 Looper 就结束了
                return;
            }
            ......
            try {
                // 处理消息
                msg.target.dispatchMessage(msg);
            } finally {
                ......
            }
            ......
        }
    }
    
}
复制代码

好的, 可以看到当 MessageQueue.next 返回一个 Message 时, 便会调用 msg.target.dispatchMessage(msg) 去处理这个 msg

  • msg.target 为一个 Handler 对象, 在上面的分析中可知, 在通过 Handler 发送消息的 enqueueMessage 方法中, 会将 msg.target 设置为当前的 Handler
  • 可以看到, 这个 msg 正是由将它投递到消息队列的 Handler 处理了, 它们是一一对应的

接下来我们看看 Handler 处理消息的流程

 public class Handler {
     
    public void dispatchMessage(Message msg) {
        // 1. 若 msg 对象中存在 callback, 则调用 handleCallback
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            // 2. 若当前 Handler 设值了 Callback, 进入这个分支
            if (mCallback != null) {
                // 2.1 若这个 Callback 的 handleMessage 返回 true, 则不会将消息继续向下分发
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            // 3. 若消息没有被 mCallback 拦截, 则会调用 handleMessage 进行最后的处理
            handleMessage(msg);
        }
    } 
    
    /**
     * 方式一: 优先级最高
     */
    private static void handleCallback(Message message) {
        message.callback.run();
    }
    
    public interface Callback {
        /**
         * 方式二: 优先级次高
         * @return True if no further handling is desired
         */
        public boolean handleMessage(Message msg);
    }
     
    public Handler(Callback callback, boolean async) {
        ......
        // Callback 由构造函数传入
        mCallback = callback;
        ......
    } 
    
    /**
     * 方式三: 这个处理消息的方式, 由子类重写, 优先级最低
     */
    public void handleMessage(Message msg) {
    
    }
    
 }
复制代码

可以看到 Handler 的 dispatchMessage 处理消息主要有三种方式

  • 若是 Message 对象内部设置了 callback, 则调用 handleCallback 方法直接处理, 不会再往下分发
  • 若 Handler 设置 Callback, 则会调用 Callback.handleMessage 方法
    • Callback.handleMessage 返回 false, 则会将消息处理继续分发给 Handler.handleMessage

好的, 消息的发送和处理到这里就分析结束了, 最后再了解一下 MessageQueue 中空闲处理者的相关知识

空闲处理者的添加与处理

1. 什么是空闲处理者

通过上一篇文章的分析可知 MessageQueue 通过 next 方法通过死循环获取下一个要处理的 Message, 若当前时刻不存在要处理的消息, 下次循环会进行睡眠操作

  • 在没有取到可执行消息 ---> 下次 for 循环进行睡眠 之间的时间间隔, 称之为空闲时间
  • 在空闲时间处理事务的对象, 称之为空闲处理者
public final class MessageQueue {

   Message next() {
        for (;;) {
            // 睡眠操作
            nativePollOnce(ptr, nextPollTimeoutMillis);
            synchronized (this) {
                Message prevMsg = null;
                Message msg = mMessages;
                ......
                if (msg != null) {
                    // ...... 
                    return msg;
                }
                
                // 空闲时间
                ....... 
            }

            // 空闲时间
            ......
            
        }
    }
    
}
复制代码

2. 空闲处理者的添加

public final class MessageQueue {
    
    public static interface IdleHandler {
        /**
         * 处理空闲消息
         */
        boolean queueIdle();
    }
    
    // 空闲消息集合
    private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
    
    public void addIdleHandler(@NonNull IdleHandler handler) {
        synchronized (this) {
            mIdleHandlers.add(handler);
        }
    }
    
}
复制代码

通过上述代码可以得到以下的信息

  • 空闲处理者使用 IdleHandler 接口描述
  • 空闲处理者通过 MessageQueue.addIdleHandler() 添加
  • 空闲处理者使用 MessageQueue.mIdleHandlers 维护

好的, 结下来看看处理细节

3. 空闲消息的处理

public final class MessageQueue {

    // 空闲消息集合
    private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
    // 空闲消息处理者的数组
    private IdleHandler[] mPendingIdleHandlers;
    
    Message next() {
        ...... 
        for (;;) {
            ......
            synchronized (this) {
                // 省略获取 msg 的代码
                ......
                // 1. 从空闲消息集合 mIdleHandlers 中获取 空闲处理者 数量
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                // 2 若无空闲处理者, 则进行下一次 for 循环
                if (pendingIdleHandlerCount <= 0) {
                    mBlocked = true;
                    continue;
                }
                ......
                // 3. 将空闲消息处理者集合转为数组
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // 4. 处理空闲消息
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];// 获取第 i 给位置的空闲处理者
                mPendingIdleHandlers[i] = null; // 置空
                boolean keep = false;        
                try {
                    // 4.1 处理空闲消息
                    keep = idler.queueIdle(); 
                } catch (Throwable t) {
                    ......
                }
                if (!keep) {
                    synchronized (this) {
                        // 4.2 走到这里表示它是一次性的处理者, 从 mIdleHandlers 移除
                        mIdleHandlers.remove(idler);
                    }
                }
            }
            ......
        }
    }
    
}
复制代码

好的, 可以看到 MessageQueue.next 在获取不到 msg 时, 会进行一些空闲消息的处理

  • 从空闲消息集合 mIdleHandlers 中获取 空闲处理者 数量
  • 若无空闲处理者, 则进行下一次 for 循环
  • 若存在空闲处理者, 则空闲消息处理者集合转为数组 mPendingIdleHandlers
  • for 循环处理空闲消息
    • 调用 IdleHandler.queueIdle 处理空闲消息
      • 返回 true, 下次再 MessageQueue.next 获取不到 msg 的空闲时间会继续处理
      • 返回 false 表示它是一次性的处理者, 从 mIdleHandlers 移除

总结

至此 Android 的消息机制就全部结束了, 此前分析过消息机制, 但一直没有深度到 Native 层, 只是浮于表面, 本次深入到了 Native 层, 看到了更底层的 epoll 监控管道相关的知识, 可以说发现了新的天地, 对 Handler 的机制有了更加深刻的理解, 本文中有分析的不正确或者不到位的地方, 希望大家多多批评指出, 笔者希望与大家共同成长。

猜你喜欢

转载自juejin.im/post/5c5272196fb9a04a0d5745d2