framework学习笔记11. Handler源码深入解析
如果不了解handler消息机制,请阅读上一篇文章:Handler源码解析
相关源码文件:
/frameworks/base/core/java/android/os/Handler.java
/frameworks/base/core/java/android/os/MessageQueue.java
/frameworks/base/core/java/android/os/Looper.java
framework/base/core/jni/android_os_MessageQueue.cpp
system/core/libutils/Looper.cpp
system/core/include/utils/Looper.h
在上一篇的 Handler 文章中,我们初步介绍了消息的入列和出列,并没有讲如何处理延时消息,那么本节就来分析一下 Handler 到底是怎么处理消息延迟的:假如我们要延迟 5s 再去处理这个消息。
- MessageQueue.next() 方法:
Message next() {
// 判断 native 层的 MessageQueue 对象有没有正常创建
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
// 消息执行需要等待的时间
int nextPollTimeoutMillis = 0;
for (;;) {
// 执行 native 层的消息延迟等待,第一次调 next 方法不会进来(不休眠)
// 0表示立即执行;-1表示永久休眠;1000表示休眠1s
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// 获取当前系统的时间
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
...
if (msg != null) {
if (now < msg.when) {
// 需要延迟, 计算延迟时间
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 不需要延迟获取已经过了时间,立马返回
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
// 标记为已在使用状态
msg.markInUse();
return msg;
}
} else {
// 如果队列里面没有消息,等待时间是 -1
nextPollTimeoutMillis = -1;
}
// 有没有空闲的 IdleHandler 需要执行,什么鬼???后面会讲
// 这里目前分析是 == 0 ,跳出
if (pendingIdleHandlerCount <= 0) {
mBlocked = true;
continue;
}
...
}
pendingIdleHandlerCount = 0;
nextPollTimeoutMillis = 0;
}
}
通过当前消息的执行时间与当前系统时间做比较,如果小于等于当前系统时间则立即返回执行该消息,如果大于当前系统时间则调用 nativePollOnce 方法去延迟等待被唤醒,当消息队列里面为空时则设置等待的时间为 -1。接下来Native 层的 framework/base/core/jni/android_os_MessageQueue.cpp
android_os_MessageQueue_nativePollOnce()方法:
// framework/base/core/jni/android_os_MessageQueue.cpp
static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
jlong ptr, jint timeoutMillis) {
// 通地址转换成 native 层的 MessageQueue 对象
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}
//framework/base/core/jni/android_os_MessageQueue.cpp的内部类NativeMessageQueue
void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
// 调用 native 层 Looper 对象的 pollOnce 方法
mLooper->pollOnce(timeoutMillis);
}
// system/core/libutils/Looper.cpp
inline int pollOnce(int timeoutMillis) {
return pollOnce(timeoutMillis, NULL, NULL, NULL);
}
// system/core/libutils/Looper.cpp
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
int result = 0;
for (;;) {
...
if (result != 0) {
...
return result;
}
// 再处理内部轮询
result = pollInner(timeoutMillis);
}
}
// system/core/libutils/Looper.cpp
int Looper::pollInner(int timeoutMillis) {
...
int result = POLL_WAKE;
mResponses.clear();
mResponseIndex = 0;
mPolling = true; //即将处于idle状态
// fd最大个数为16
struct epoll_event eventItems[EPOLL_MAX_EVENTS];
// 等待事件发生或者超时,在 nativeWake() 方法,向管道写端写入字符,则该方法会返回;
int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
...
return result;
}
上述代码中,我们知道进入休眠是通过android_os_MessageQueue_nativePollOnce () 调用底层Looper.cpp中的pollInner(int timeoutMillis),最终在epoll_wait()处等待timeoutMillis;那么另一方面唤醒的方法是什么呢?答案在源码中非常明显,是android_os_MessageQueue_nativeWake() -> 对应MessageQueue.java中的native方法nativeWake(long ptr)。
// framework/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);
return nativeMessageQueue->wake();
}
// framework/base/core/jni/android_os_MessageQueue.cpp
void NativeMessageQueue::wake() {
mLooper->wake();
}
// system/core/libutils/Looper.cpp
void Looper::wake() {
uint64_t inc = 1;
// 向管道 mWakeEventFd 写入字符1 , 写入失败仍然不断执行(唤醒操作)
ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t)));
if (nWrite != sizeof(uint64_t)) {
if (errno != EAGAIN) {
ALOGW("Could not write wake signal, errno=%d", errno);
}
}
}
小结:native 层其实也有 Handler.cpp 、MessageQueue.cpp 和 Looper.cpp 对象,但他们并不都是与 Java 层一一对应的,在源码分析的过程中得知,只有MessageQueue.java 和 MessageQueue.cpp 有关联。在java层计算好延迟时间后调用 native 层 nativePollOnce 方法,其内部实现采用 epoll 来处理延迟等待返回(6.0版本)。当有新的消息插入时会调用 native 层的 nativeWake 方法,这个方法很简单就是向文件描述符中写入一个最简单的 int 数据 1,目的是为了唤醒之前的 epoll_wait 方法,其实也就是唤醒 nativePollOnce 的等待。
- 接下来我们看看 IdleHandler:这个是安卓的一个 空闲机制,也就是说,没有消息需要立即处理时,就会触发这个机制;
IdleHandler是一个回调接口,可以通过MessageQueue的addIdleHandler添加实现类。当MessageQueue中的任务暂时处理完了(没有新任务或者下一个任务延时在之后),这个时候会回调这个接口,返回false,那么就会移除它,返回true就会在下次message处理完了的时候继续回调。
(1)使用:
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
// 开始执行一些其它操作,可能不耗时也可能稍微耗时
// 比如跨进程访问,比如查询数据库,比如收集某些信息,比如写日志等等
return false;
}
});
(2)源码:framework/base/core/java/android/os/MessageQueue.java
public void addIdleHandler(@NonNull IdleHandler handler) {
if (handler == null) {
throw new NullPointerException("Can't add a null IdleHandler");
}
synchronized (this) {
mIdleHandlers.add(handler);
}
}
Message next() {
int pendingIdleHandlerCount = -1; // -1 only during first iteration
for (;;) {
synchronized (this) {
// 目前这里分析有内容
if (msg != null) {
//如果有消息需要处理,就返回msg进行处理;
return msg;
}
} else {
// No more messages.否则继续向下执行
nextPollTimeoutMillis = -1;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// 循环遍历所有的 IdleHandler 回调
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
// 回调执行 queueIdle 方法
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
// 执行完函数返回是不是需要空闲时一直回调
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
pendingIdleHandlerCount = 0;
nextPollTimeoutMillis = 0;
}
}
- 屏障消息:在平时开发中,绝大多数使用的消息都是普通消息,实际上 Handler 的消息可不知这一种(普通消息、异步消息、屏障消息),下面我们就来了解一下这三种消息;
(1)普通消息:最常用,所以就一笔带过了;
Message message = Message.obtain();
message.what = ***;
mHandler.sendMessageDelayed(message, 1000);
(2)异步消息:有两种方式创建,一种是通过Handler,另外一种是通过Message;
// 在创建Handler时,传入的async参数是true
public Handler(Callback callback, boolean async) {
// 省略部分代码...
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
// 发送的 Message 设置 setAsynchronous(true)
Message message = Message.obtain();
message.what = XXX;
message.setAsynchronous(true);
mHandler.sendMessageDelayed(message, 1000);
(3)屏障消息:通过反射的方式调用。特征是target为null,不需要做分发处理。用于拦截队列中msg.when之后的同步消息,放行异步消息。在移除对应msg.arg1的屏障消息后,同步消息可恢复执行。
//往消息队列插入同步屏障
@RequiresApi(api = Build.VERSION_CODES.M)
public void sendSyncBarrier(){
try {
Log.d("TAG", "插入同步屏障");
MessageQueue queue = handler.getLooper().getQueue();
Method method = MessageQueue.class.getDeclaredMethod("postSyncBarrier");
method.setAccessible(true);
token= (int) method.invoke(queue);
} catch (Exception e) {
e.printStackTrace();
}
}
//移除屏障
@RequiresApi(api = Build.VERSION_CODES.M)
public void removeSyncBarrier(){
try {
Log.d("TAG", "移除屏障");
MessageQueue queue = handler.getLooper().getQueue(); //或则Looper.myQueue()
Method method = MessageQueue.class.getDeclaredMethod("removeSyncBarrier",int.class);
method.setAccessible(true);
method.invoke(queue, token);
} catch (Exception e) {
e.printStackTrace();
}
}
(4)屏障消息源码:
插入屏障消息:
MessageQueue#postSyncBarrier
private int postSyncBarrier(long when) {
synchronized (this) {
final int token = mNextBarrierToken++;
// 屏障消息没有tartget。
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
Message prev = null;
Message p = mMessages;
// 屏障消息入列
if (when != 0) {
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
// 返回一个int的tocken,可以通过tocken撤销屏障
return token;
}
}
postSyncBarrier方法就是用来插入一个屏障到消息队列的,可以看到它很简单,从这个方法我们可以知道如下:
- 屏障消息和普通消息的区别在于屏障没有tartget,普通消息有target是因为它需要将消息分发给对应的target,而屏障不需要被分发,它就是用来挡住普通消息来保证异步消息优先处理的。
- 屏障和普通消息一样可以根据时间来插入到消息队列中的适当位置,并且只会挡住它后面的同步消息的分发。
- postSyncBarrier返回一个int类型的数值,通过这个数值可以撤销屏障。
- postSyncBarrier方法是私有的,如果我们想调用它就得使用反射。
- 插入普通消息会唤醒消息队列,插入屏障不会唤醒消息队列。
工作原理
postSyncBarrier方法屏障就被插入到消息队列中了,那么屏障是如何挡住普通消息只允许异步消息通过的?我们重新看看MessageQueue.next()方法来分析一下:
Message next() {
// ...
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
//1. 如果有消息被插入到消息队列或者超时时间到,就被唤醒,否则阻塞在这。
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吗?
// msg.target == null,则msg为屏障消息,否则队列中的异步消息和同步消息无异
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
// 跳过同步消息:当遍历到消息队列末尾,或者msg是异步消息,则退出循环
} while (msg != null && !msg.isAsynchronous());
}
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;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// 如果没有异步消息就一直休眠,等待被唤醒。
nextPollTimeoutMillis = -1;
}
// ...
}
}
移除屏障
MessageQueue的removeSyncBarrier方法:
public void removeSyncBarrier(int token) {
// Remove a sync barrier token from the queue.
// If the queue is no longer stalled by a barrier then wake it.
synchronized (this) {
Message prev = null;
Message p = mMessages;
//找到token对应的屏障
while (p != null && (p.target != null || p.arg1 != token)) {
prev = p;
p = p.next;
}
final boolean needWake;
//从消息链表中移除
if (prev != null) {
prev.next = p.next;
needWake = false;
} else {
mMessages = p.next;
needWake = mMessages == null || mMessages.target != null;
}
//回收这个Message到对象池中。
p.recycleUnchecked();
// If the loop is quitting then it is already awake.
// We can assume mPtr != 0 when mQuitting is false.
if (needWake && !mQuitting) {
nativeWake(mPtr);//唤醒消息队列
}
}
Handler的源码分析就到这里了;