热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Android消息机制详解(AndroidP)

前言Android消息机制,一直都是Android应用框架层非常重要的一部分,想更加优雅的进行Android开发,我想了解消息机制是非常必要的一个过程,此前也分析过很多次Handl
前言

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 sThreadLocal = new ThreadLocal();
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(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 的使用率

相互依赖的结构图

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

《Android 消息机制详解(Android P)》 消息循环相互依赖关系图

三. 消息循环的启动

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 // 给 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 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(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 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 消息机制详解(Android P)》 线程消息的创建与循环的流程图

四. 消息的发送与处理

我们都知道, 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 // 将该消息放置到队首
msg.next = p;
mMessages = msg;
needWake = mBlocked; // 队首元素变更了, 有可能需要立即执行
} else {
// Case2: 入队列的消息执行时间 晚于 队首消息执行时间
......
// 将该消息插入到消息队列合适的位置
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || 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(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, 若当前时刻不存在要处理的消息, 下次循环会进行睡眠操作

  • 在没有取到可执行消息 &#8212;> 下次 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 mIdleHandlers = new ArrayList(); public void addIdleHandler(@NonNull IdleHandler handler) {
synchronized (this) {
mIdleHandlers.add(handler);
}
}}

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

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

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

3. 空闲消息的处理

public final class MessageQueue {
// 空闲消息集合
private final ArrayList mIdleHandlers = new ArrayList();
// 空闲消息处理者的数组
private IdleHandler[] mPendingIdleHandlers; Message next() {
......
for (;;) {
......
synchronized (this) {
// 省略获取 msg 的代码
......
// 1. 从空闲消息集合 mIdleHandlers 中获取 空闲处理者 数量
if (pendingIdleHandlerCount <0
&& (mMessages == null || now pendingIdleHandlerCount = mIdleHandlers.size();
}
// 2 若无空闲处理者, 则进行下一次 for 循环
if (pendingIdleHandlerCount <= 0) {
mBlocked = true;
continue;
}
......
// 3. 将空闲消息处理者集合转为数组
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// 4. 处理空闲消息
for (int i = 0; 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 的机制有了更加深刻的理解, 本文中有分析的不正确或者不到位的地方, 希望大家多多批评指出, 笔者希望与大家共同成长。


推荐阅读
  • 本文详细探讨了Zebra路由软件中的线程机制及其实际应用。通过对Zebra线程模型的深入分析,揭示了其在高效处理网络路由任务中的关键作用。文章还介绍了线程同步与通信机制,以及如何通过优化线程管理提升系统性能。此外,结合具体应用场景,展示了Zebra线程机制在复杂网络环境下的优势和灵活性。 ... [详细]
  • 零拷贝技术是提高I/O性能的重要手段,常用于Java NIO、Netty、Kafka等框架中。本文将详细解析零拷贝技术的原理及其应用。 ... [详细]
  • 普通树(每个节点可以有任意数量的子节点)级序遍历 ... [详细]
  • 本文介绍了 Java 中 io.netty.channel.kqueue.KQueueStaticallyReferencedJniMethods.evfiltSock() 方法的使用及其代码示例,帮助开发者更好地理解和应用该方法。 ... [详细]
  • JVM钩子函数的应用场景详解
    本文详细介绍了JVM钩子函数的多种应用场景,包括正常关闭、异常关闭和强制关闭。通过具体示例和代码演示,帮助读者更好地理解和应用这一机制。适合对Java编程和JVM有一定基础的开发者阅读。 ... [详细]
  • Java高并发与多线程(二):线程的实现方式详解
    本文将深入探讨Java中线程的三种主要实现方式,包括继承Thread类、实现Runnable接口和实现Callable接口,并分析它们之间的异同及其应用场景。 ... [详细]
  • 本文是Java并发编程系列的开篇之作,将详细解析Java 1.5及以上版本中提供的并发工具。文章假设读者已经具备同步和易失性关键字的基本知识,重点介绍信号量机制的内部工作原理及其在实际开发中的应用。 ... [详细]
  • QT框架中事件循环机制及事件分发类详解
    在QT框架中,QCoreApplication类作为事件循环的核心组件,为应用程序提供了基础的事件处理机制。该类继承自QObject,负责管理和调度各种事件,确保程序能够响应用户操作和其他系统事件。通过事件循环,QCoreApplication实现了高效的事件分发和处理,使得应用程序能够保持流畅的运行状态。此外,QCoreApplication还提供了多种方法和信号槽机制,方便开发者进行事件的定制和扩展。 ... [详细]
  • Java中不同类型的常量池(字符串常量池、Class常量池和运行时常量池)的对比与关联分析
    在研究Java虚拟机的过程中,笔者发现存在多种类型的常量池,包括字符串常量池、Class常量池和运行时常量池。通过查阅CSDN、博客园等相关资料,对这些常量池的特性、用途及其相互关系进行了详细探讨。本文将深入分析这三种常量池的差异与联系,帮助读者更好地理解Java虚拟机的内部机制。 ... [详细]
  • 如何利用Java 5 Executor框架高效构建和管理线程池
    Java 5 引入了 Executor 框架,为开发人员提供了一种高效管理和构建线程池的方法。该框架通过将任务提交与任务执行分离,简化了多线程编程的复杂性。利用 Executor 框架,开发人员可以更灵活地控制线程的创建、分配和管理,从而提高服务器端应用的性能和响应能力。此外,该框架还提供了多种线程池实现,如固定线程池、缓存线程池和单线程池,以适应不同的应用场景和需求。 ... [详细]
  • 在使用SSH框架进行项目开发时,经常会遇到一些常见的问题。例如,在Spring配置文件中配置AOP事务声明后,进行单元测试时可能会出现“No Hibernate Session bound to thread”的错误。本文将详细探讨这一问题的原因,并提供有效的解决方案,帮助开发者顺利解决此类问题。 ... [详细]
  • AIX编程挑战赛:AIX正方形问题的算法解析与Java代码实现
    在昨晚的阅读中,我注意到了CSDN博主西部阿呆-小草屋发表的一篇文章《AIX程序设计大赛——AIX正方形问题》。该文详细阐述了AIX正方形问题的背景,并提供了一种基于Java语言的解决方案。本文将深入解析这一算法的核心思想,并展示具体的Java代码实现,旨在为参赛者和编程爱好者提供有价值的参考。 ... [详细]
  • 在处理大图片时,PHP 常常会遇到内存溢出的问题。为了避免这种情况,建议避免使用 `setImageBitmap`、`setImageResource` 或 `BitmapFactory.decodeResource` 等方法直接加载大图。这些函数在处理大图片时会消耗大量内存,导致应用崩溃。推荐采用分块处理、图像压缩和缓存机制等策略,以优化内存使用并提高处理效率。此外,可以考虑使用第三方库如 ImageMagick 或 GD 库来处理大图片,这些库提供了更高效的内存管理和图像处理功能。 ... [详细]
  • 本文详细介绍了如何使用Python的多进程技术来高效地分块读取超大文件,并将其输出为多个文件。通过这种方式,可以显著提高读取速度和处理效率。 ... [详细]
  • 深入解析Spring Boot启动过程中Netty异步架构的工作原理与应用
    深入解析Spring Boot启动过程中Netty异步架构的工作原理与应用 ... [详细]
author-avatar
chen-yu2502881617
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有