导读:
本文打算分2步骤来讲解Handler,首先要有一个整体的流程说明,看看他的从Java 到kernel的完整调用过程,由于Handler还算是代码比较简单,逻辑比较清楚,所以这个过程也是相对清晰。
第二个步骤就是习题时间,作为训练,解答市面上关于Handler比较好的问题。
面向读者:好奇Handler具体实现流程的人,需要寻找Hook点,所以必须了解整个流程的人,准备面试的人。
第一步:Handler机制的整体流程
Handler API的调用就类似拿着一个handler的对象到处去发消息,然后在我们创建的Handler会实现一个接受消息的方法,并在这里写好我们的处理逻辑。
Handler所扮演的角色就类似一个管理类,提供给调用方使用,而他的api主要目的也很明确,就是提供给你发消息和接受处理消息的能力,但这些能力的实现却跟handler没有什么关系。
1.1 首先我们看发消息的流程。
Handler的构造器有很多,分别有三个参数,Looper,Callback, async:Boolean, 具体的意思,我们第二步骤再看,先看跟Looper相关的.
public Handler(Looper looper) {
this(looper, null, false);
}
可以指定Looper,那么不指定的肯定就是当前线程的Looper了,因为Looper使用了TreadLocal来保证每个线程都是唯一性的嘛。
public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
可以看到Handler上来就保存了Looper和Looper中的MsgQ对象,接着可以发现所有发消息的逻辑最后都会会聚到这个方法:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
有一个msg.target 用Handler对象自身this赋值了,So,所有的Handler发送的msg都携带着发送者的信息,也就是作为发送方的Handler对象,这个作用后面再说,我们先看主流程。
最后都是走的MsgQ的方法,看来真正的发送逻辑和Looper关系不大,而是全靠MsgQ.
我们继续看MessageQueue的这个入队方法。
boolean enqueueMessage(Message msg, long when) {
//对象锁,保证多线程发消息的同步性
synchronized (this) {
msg.when = when;
//下面要表演单链表插入新节点到头部
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
//如果是全新的消息或者时间最早的话直接作为新头部
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
//死循环找到尾节点或者时间小于的节点跳出,可见是按照先后顺序排序的单链表
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
//p是上面遍历跳出的正确插入位置,在p前面插入msg消息
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
//暂时先忽略needWake
if (needWake) {
//调用native层传入的mPtr指针代表着这个MsgQ的对象,提供给cpp调用
nativeWake(mPtr);
}
}
return true;
}
这个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();
}
刚才注释说过了,这个ptr代表着Java层的MsgQ对象,强转一下,最后调用了Looper.cpp的wake方法。
void Looper::wake() {
#if DEBUG_POLL_AND_WAKE
ALOGD("%p ~ wake", this);
#endif
ssize_t nWrite;
do {
//这里使用了pipe管道,mWakeWritePipeFd代表管道的写端描述符,可以理解为一个flag
nWrite = write(mWakeWritePipeFd, "W", 1);
} while (nWrite == -1 && errno == EINTR);
if (nWrite != 1) {
if (errno != EAGAIN) {
ALOGW("Could not write wake signal, errno=%d", errno);
}
}
}
至此发送的工作结束,一会儿分析接收消息的流程,小总结一下:
发送消息Java层只跟MsgQ有关,Handler -> MsgQ.java -> MsgQ.cpp -> Looper.cpp -> linux kernel的管道,而MsgQ本质是一个按照时间从前到后排序的单链表,由于MsgQ是Looper的,Looper是线程私有的,所以除了Handler的调用,后面的所有流程都是线程私有的,代表了一个线程。
1.2 我们再看接受消息的流程。
我们都知道再Handler类实现的方法fun handleMessage(Msg)可以接受到消息,但这个消息是从哪里来的呢, 发送的时候最后Looper.cpp调用了Linux 内核的管道,这里需要了解一下pipe和epoll的知识,这是一种多复用监听IO机制,描述一下就是,当管道里有人写入了,会唤醒线程并通知epoll来读数据,如果管道被读光了,而暂时也没有写入,那么读的线程会BLOCK,释放掉CPU资源。那么读到了,kernel就会通知到native层,在通知到Java层,我们反过看源码,从handleMessage来逆向观察,首先寻找handlerMessage是在哪调用的:
/**
* Handle system messages here.
*/
public void dispatchMessage(Message msg) {
if (msg.callback !=