之前我们已经分析了Handler的创建和消息的发送流程,基本上已经了解了Handler的运行的原理,整个Handler分为java层和native层,我们通常开发app都是通过java层给Handler发送消息,java层Handler最终的消息获取和分发都是在java层处理的,native主要作为一个epoll机制的阻塞和唤醒作用,但是除了这种通常的java层的Handler的发送流程外,Android还允许我们自定义文件描述符来发送消息,如果对于epoll监听文件描述符不太清楚的话,建议从Handler的第一篇文章看起,前面的文章对这些内容有比较详细的介绍,这里就不多说了。我们这篇文章就从Handler怎么自定义一个文件描述符说起,这里的内容都是接着之前文章的分析的,所以如果直接看这里不太理解的话,可以从[Handler源码解析(一)](https://liqi.site/archives/h-a-n-d-l-e-r-yuan-ma-jie-xi--yi-),[Handler源码解析(二)](https://liqi.site/archives/handler%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%E4%BA%8C)这两篇文章看起,看完再看这篇文章就比较清楚了。好了,下面我们从java层的添加一个自定义文件描述符开始。
# 添加一个被epoll监听的Handler文件描述符
```java
public void addOnFileDescriptorEventListener(@NonNull FileDescriptor fd,
@OnFileDescriptorEventListener.Events int events,
@NonNull OnFileDescriptorEventListener listener) {
if (fd == null) {
throw new IllegalArgumentException("fd must not be null");
}
if (listener == null) {
throw new IllegalArgumentException("listener must not be null");
}
synchronized (this) {
// events是监听类型,listener是回调方法
updateOnFileDescriptorEventListenerLocked(fd, events, listener);
}
}
```
在MessageQueue中有addOnFileDescriptorEventListener方法,通过这个方法可以添加一个自定义的文件描述符,这里有3个参数,第一个就是一个文件描述符,第二个参数是一个监听类型,比如读或者写等等。第三个参数是回调方法,这个回调方法是一个OnFileDescriptorEventListener接口,我们看下这个接口:
```java
public interface OnFileDescriptorEventListener {
public static final int EVENT_INPUT = 1 << 0;
public static final int EVENT_OUTPUT = 1 << 1;
public static final int EVENT_ERROR = 1 << 2;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = { EVENT_INPUT, EVENT_OUTPUT, EVENT_ERROR })
public @interface Events {
}
@Events
int onFileDescriptorEvents(@NonNull FileDescriptor fd, @Events int events);
}
```
这个接口有3个变量,EVENT_INPUT,EVENT_OUTPUT和EVENT_ERROR分别代表3种监听类型,可以看到如果自定义的fd确实比默认的监听类型多了,这里3种类型分别是读,写和错误。下面还声明了一个注解Events,他的值是这三种类型中的一种。最后就是回调方法onFileDescriptorEvents,他有2个参数,第一个是监听的fd,第二个是监听类型,这里第二个参数使用了上面声明的注解,所以只能是三种值中的一种。这个回调方法的返回值也是一个监听的类型,是三种值中的一种,如果返回0的话,那说明了这个监听的fd有异常了,可能被关闭了等原因,这个在下面我们看回调方法的时候还能看到。
我们回到addOnFileDescriptorEventListener方法,这个方法开始先验证下fd,回调方法的正确性,如果一切都正常的话,那就会继续调用updateOnFileDescriptorEventListenerLocked方法,会把三个参数都传入到这个方法中。
```java
private SparseArray<FileDescriptorRecord> mFileDescriptorRecords; // 自定义文件描述符record集合
private void updateOnFileDescriptorEventListenerLocked(FileDescriptor fd, int events,
OnFileDescriptorEventListener listener) {
final int fdNum = fd.getInt$(); // fd的数字
int index = -1;
FileDescriptorRecord record = null;
if (mFileDescriptorRecords != null) { // 如果fdrecords非空
// 看看有没有这个文件描述符
index = mFileDescriptorRecords.indexOfKey(fdNum);
// key 大于0,说明有这个fd
if (index >= 0) {
// 取出这个fd的record
record = mFileDescriptorRecords.valueAt(index);
// 如果record非空,同时监听类型也和需要的一样
if (record != null && record.mEvents == events) {
return;
}
}
}
.............
}
```
这个方法我们分两段来看,每个要添加的自定义fd都需要保存在一个集合中,这里的mFileDescriptorRecords就是一个集合,类型是FileDescriptorRecord,这个是一个结构体,是用来描述一个自定义文件描述符的。我们看下这个结构体:
```java
private static final class FileDescriptorRecord {
public final FileDescriptor mDescriptor;
public int mEvents;
public OnFileDescriptorEventListener mListener;
public int mSeq;
public FileDescriptorRecord(FileDescriptor descriptor,
int events, OnFileDescriptorEventListener listener) {
mDescriptor = descriptor; // fd对象
mEvents = events; // 监听类型
mListener = listener; // 回调方法
}
}
```
这个结构体很简单,其实就是封装了前面传入的三个参数,之后再处理消息的时候会取出来使用,这个处理的部分我们在第二篇分析Handler的文章中已经说了,不熟悉的同学可以在去那里看看,我们这里后面也会提一下,我们回到前面updateOnFileDescriptorEventListenerLocked方法。
首先updateOnFileDescriptorEventListenerLocked方法会判断是否集合中已经有了这个fd了,如果已经有一个监听相同类型的fd了那么就退出,如果还没有的话,就开始要加入了,我们看下半段代码:
```java
...........
if (events != 0) {
events |= OnFileDescriptorEventListener.EVENT_ERROR;
if (record == null) {
if (mFileDescriptorRecords == null) {
mFileDescriptorRecords = new SparseArray<FileDescriptorRecord>();
}
// 创建添加的fd的record对象
record = new FileDescriptorRecord(fd, events, listener);
// 放入record集合中
mFileDescriptorRecords.put(fdNum, record);
} else {
// 到这里reco非空,说明record的集合中已经存在这个fd对象了,更新下他的listener和events
record.mListener = listener;
record.mEvents = events;
record.mSeq += 1;
}
// 调用native的设置自定义fd方法,添加到c++层中
nativeSetFileDescriptorEvents(mPtr, fdNum, events);
} else if (record != null) {
// 到这里events为0,没有0这个监听类型。
// 由于这里record非空,所以上面必然是已经执行过赋值操作了,所以mFileDescriptorRecords非空
record.mEvents = 0;
// 从fd的record集合中移除
mFileDescriptorRecords.removeAt(index);
}
```
这里后半段代码如果传入的监听类型events为0,说明这个监听类型是异常的,而目前集合中已经有了fd相同的文件描述符(监听类型是非0的),所以说明需要把这个fd的监听类型更新为0,由于0是异常的,所以要从自定义fd集合中移除掉。
# native层添加java层传过来的自定义消息
如果监听类型非0,那么首先加上一个监听错误的类型,如果当前集合中没有这个fd,那么就创建一个自定义fd的类FileDescriptorRecord,加入到集合中。如果集合中已经有了这个fd,那么会更新下这个fd的三个字段为传入的值。这样的话,java层的初始化就完成了,最后会调用nativeSetFileDescriptorEvents方法,开始native层的初始化,nativeSetFileDescriptorEvents这个方法有三个参数,第一个是native层MessageQueue的地址,第二个是要添加的fd,第三个是监听类型。我们接下去就去native层看一下:
```c++
static void android_os_MessageQueue_nativeSetFileDescriptorEvents(JNIEnv* env, jclass clazz,
jlong ptr, jint fd, jint events) {
// 获取这个线程的c++层的messagequeue
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
// 设置自定义fd和监听类型
nativeMessageQueue->setFileDescriptorEvents(fd, events);
}
```
nativeSetFileDescriptorEvents方法最终会调用到android_os_MessageQueue.cpp的android_os_MessageQueue_nativeSetFileDescriptorEvents方法,这里取出传入native层的MessageQueue,然后调用他的setFileDescriptorEvents方法,参数是fd和监听类型:
```c++
// 添加c++层的自定义fd
void NativeMessageQueue::setFileDescriptorEvents(int fd, int events) {
if (events) { // 监听类型不能为0
int looperEvents = 0;
if (events & CALLBACK_EVENT_INPUT) { // read监听
looperEvents |= Looper::EVENT_INPUT;
}
if (events & CALLBACK_EVENT_OUTPUT) { //write监听
looperEvents |= Looper::EVENT_OUTPUT;
}
// 调用c++层添加fd方法
// 参数1 fd
// 参数2 表示有回调方法
// 参数3 监听类型
// 参数4 是一个LooperCallback类型,这里this是MessageQueue,他是基础LooperCallback的
// 参数5 调用回调方法后的监听类型
mLooper->addFd(fd, Looper::POLL_CALLBACK, looperEvents, this,
reinterpret_cast<void*>(events));
} else { // 监听类型为0说明错误,从looper的mRequests集合中移除
mLooper->removeFd(fd);
}
}
```
这里会判断传入的监听类型events是否为0,如果为0说明fd异常,所以会调用native的Looper的removeFd方法移除fd,这个我们在前面一篇文章中在分析分发自定义fd的时候也遇到过了,这里就不多说了,可以去看前面一篇文章。如果这里监听类型是正常的话,会判断有没有监听读和写,如果有的话会设置到新的一个监听变量looperEvents中,最后会调用addFd方法继续添加自定义fd。这里addFd方法有五个参数,第一个是fd,第二个POLL_CALLBACK表示这个fd是有回调参数的,第三个是监听类型,第四个是个LooperCallback,这个我们在之前的文章中也介绍过,就是具体的回调方法,这个LooperCallback我们在之前的文章中也介绍过,这里在说一下:
```c++
class LooperCallback : public virtual RefBase {
protected:
virtual ~LooperCallback();
public:
virtual int handleEvent(int fd, int events, void* data) = 0;
};
```
这个LooperCallback是一个类,里面主要有个handleEvent方法,这个方法有三个参数,fd,监听类型和返回的监听类型,我们在分析自定义fd消息分发方法的时候已经看过调用这个方法了。回到前面方法,这里我么那是在native层的MessageQueue调用的,这里调用addFd方法中,这个LooperCallback参数传入了this,所以说native层的MessageQueue就是一个LooperCallback,这个我们看下Native层的Message的定义:
```c++
class NativeMessageQueue : public MessageQueue, public LooperCallback {
...........
}
```
这里native层的MessageQueue实际是NativeMessageQueue,这个我们在分析创建Handler的时候已经知道了,他的父类有LooperCallback,所以它确实是一个LooperCallback类(c++是可以多继承的,这里提一下)。既然他继承了LooperCallback,那么肯定有实现其中handleEvent方法,我们找一下:
```c++
int NativeMessageQueue::handleEvent(int fd, int looperEvents, void* data) {
.......
}
```
果然有这个方法,可以知道最后自定义fd的回调方法其实就是在这里了,这个方法我们稍后在具体看,这里我们找到了自定义fd的回调方法。我们回到setFileDescriptorEvents方法,addFd方法最后一个参数是添加这个自定义fd的时候,epoll监听的类型,之后在上面的handleEvent回调方法执行完后,会用来做比对,如果不一样就说明发生了异常,会把自定义fd从保存的集合中移除。我们这里遗留了一个回调方法handleEvent没分析,我们稍后回头在看这个方法,接下来我们先到addFd方法中看一下:
```c++
// 添加自定义fd方法
// 参数1 fd
// 参数2 表示是否有回调
// 参数3 监听类型
// 参数4 回调方法
// 参数5 调用回调方法后的监听类型
int Looper::addFd(int fd, int ident, int events, const sp<LooperCallback>& callback, void* data) {
#if DEBUG_CALLBACKS
ALOGD("%p ~ addFd - fd=%d, ident=%d, events=0x%x, callback=%p, data=%p", this, fd, ident,
events, callback.get(), data);
#endif
if (!callback.get()) { // 如果没有回调方法
if (! mAllowNonCallbacks) { // 是否允许没有回调方法
ALOGE("Invalid attempt to set NULL callback but not allowed for this looper.");
return -1;
}
if (ident < 0) {
ALOGE("Invalid attempt to set NULL callback with ident < 0.");
return -1;
}
} else { // 有回调方法,设置ident为POLL_CALLBACK
ident = POLL_CALLBACK;
}
{ // acquire lock
AutoMutex _l(mLock);
Request request;
request.fd = fd;
request.ident = ident; // 是否有回调方法
request.events = events; // 监听类型
request.seq = mNextRequestSeq++; // 自定义fd被修改过几次
request.callback = callback; // 回调方法
request.data = data; // 调用回调方法后的监听类型
if (mNextRequestSeq == -1) mNextRequestSeq = 0; // reserve sequence number -1
struct epoll_event eventItem;
request.initEventItem(&eventItem); // 初始化一个epoll监听结构体
ssize_t requestIndex = mRequests.indexOfKey(fd); // 是否已经有了这个fd
if (requestIndex < 0) { // 如果没有添加到epoll中
int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, & eventItem);
if (epollResult < 0) {
ALOGE("Error adding epoll events for fd %d: %s", fd, strerror(errno));
return -1;
}
mRequests.add(fd, request); // 把这个自定义fd添加到Request的map中
} else { // 走到这里,说明已经添加过了这个自定义fd,修改这个fd
int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_MOD, fd, & eventItem);
if (epollResult < 0) { // 修改报错
if (errno == ENOENT) { // 原因是没有这个fd
ALOGD("%p ~ addFd - EPOLL_CTL_MOD failed due to file descriptor "
"being recycled, falling back on EPOLL_CTL_ADD: %s",
this, strerror(errno));
#endif
// 添加
epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, & eventItem);
if (epollResult < 0) {
ALOGE("Error modifying or adding epoll events for fd %d: %s",
fd, strerror(errno));
return -1;
}
// 标记下epoll需要重新创建,然后唤醒阻塞线程,之后执行到pollonce-pollInner里面会重新创建
scheduleEpollRebuildLocked();
} else {
ALOGE("Error modifying epoll events for fd %d: %s", fd, strerror(errno));
return -1;
}
}
mRequests.replaceValueAt(requestIndex, request); // 从Request的map中替换这个自定义fd
}
} // release lock
return 1;
}
```
这个方法主要会把自定义fd添加到相关的集合,把这个fd相关的数据,比如回调方法之类的封装为Request对象,加入到Requests结合中,这个集合在epoll被唤醒后会解析为Response对象,之后会分发这个对象进行消息的处理,这个流程我们在之前分析消息分发的流程中都已经看过了,这里也不说了。这里我们分析下这个方法的代码。
首先这个的参数callback就是回调方法,如果回调方法是空的话,那么要看看mAllowNonCallbacks是否为true,mAllowNonCallbacks的值表示是否允许回调方法为空,如果不允许的话,那么就返回。接着这里ident参数前面我们看到了赋值是POLL_CALLBACK,值为-2,他也表示是需要执行一个回调方法的,如果他的值小于0,说明需要回调方法,这里callback为空的话也会退出。如果callback非空的话,说明有回调方法的,还是会把ident赋值为POLL_CALLBACK,表示这个消息会处理一个回调方法。
接着就会创建一个Request结构体,这个结构体我们之前也介绍过了,他主要就是封装了前面传过来的一些参数,fd,回调方法等等之类的,这些参数上面介绍过了,这里多说了。之后就开始要往epoll里面添加监听的fd了,在添加前,首先初始化一个epoll_event结构体,这个结构体在之前分析初始化epoll时候也看到过,对于的是一个fd的信息,这个调用前面Request的参数来初始化这个epoll_event:
```c++
void Looper::Request::initEventItem(struct epoll_event* eventItem) const {
int epollEvents = 0;
if (events & EVENT_INPUT) epollEvents |= EPOLLIN;
if (events & EVENT_OUTPUT) epollEvents |= EPOLLOUT;
memset(eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
eventItem->events = epollEvents;
eventItem->data.fd = fd;
}
```
我们看到了主要就是把fd,监听类型赋值给epoll_event,这个看过前面分析文章的同学应该比较清楚,不太清楚的话,可以看下前面分析epoll的部分。回到上面方法,介绍会从Requests集合中看看到底有没有这个fd,如果没有的话调用epoll的epoll_ctl方法把这个自定义fd加入到epoll中,这个方法也这里也不细说了,都是之前分析过的东西,最后在把Request加入到集合中,这样一个自定义fd就完成了。
如果当前Request集合中已经有了这个fd,那么同样会调用epoll的epoll_ctl方法,不过第二个参数是EPOLL_CTL_MOD表示修改当前监听的fd,如果修改成功的话,会修改Request集合中存在的这个fd的结构体,修改为当前这个最新的。如果修改错误的话,需要看下错误的原因,如果错误是epoll中没有这个fd,那么说明之前的fd可能从epoll中被移除了,那么还是调用epoll_ctl方法重新添加这个fd,然后会调用scheduleEpollRebuildLocked方法唤醒epoll,scheduleEpollRebuildLocked这个方法具体我们之前看过,主要就是会修改mEpollRebuildRequired这个值为true,这样在epoll被唤醒后会重新初始化epoll。这里只所以要重新初始化,根据Android的说明,可能会遗留一些无效的fd句柄在epoll中,所以最好重新初始化一下。如果不是应该修改一个不存在的fd的话,直接退出就可以了。
这样添加一个自定义fd在native中的流程也完成了,现在Request集合中已经包含了这些fd被唤醒后需要的必要信息了,比如对应的fd,比如回调方法,这样在epoll被唤醒后会取出这些信息,来执行回调函数,这些唤醒后的流程在分析Handler的第二篇文章里已经都分析过了,这样也不往下说多了。
添加自定义fd的流程说完了,我们在回头看下前面遗留了一个native层的回调方法:
```c++
int NativeMessageQueue::handleEvent(int fd, int looperEvents, void* data) {
int events = 0;
if (looperEvents & Looper::EVENT_INPUT) { // read监听
events |= CALLBACK_EVENT_INPUT;
}
if (looperEvents & Looper::EVENT_OUTPUT) { // write监听
events |= CALLBACK_EVENT_OUTPUT;
}
// 设置遇到错误,挂起,或者fd无效的监听
if (looperEvents & (Looper::EVENT_ERROR | Looper::EVENT_HANGUP | Looper::EVENT_INVALID)) {
events |= CALLBACK_EVENT_ERROR;
}
int oldWatchedEvents = reinterpret_cast<intptr_t>(data);
// 调用回调方法后的监听类型
// mPollObj是JNIEnv,这个方法是在NativeMessageQueue::pollOnce后才被调用的
// 所以mPollObj肯定非空
int newWatchedEvents = mPollEnv->CallIntMethod(mPollObj,
gMessageQueueClassInfo.dispatchEvents, fd, events);
if (!newWatchedEvents) { // 监听类型不能为0
return 0; // unregister the fd
}
// 如果调用回调方法后的监听类型和之前的不一样,那么重新设置这个fd的监听类型
if (newWatchedEvents != oldWatchedEvents) {
setFileDescriptorEvents(fd, newWatchedEvents);
}
return 1;
}
```
这个是自定义fd消息被唤醒后,调用的回调方法。这里首先看一下监听的类型是否有读写,错误等,有的话会赋值给events。之后data是添加自定义fd时候设置的值,取出这个添加时候的值保存在oldWatchedEvents中。接着就会从native反向调用java层的方法gMessageQueueClassInfo.dispatchEvents,这个java层的方法还记得是在哪里注册的吗,我们回到注册的地方看一下:
```c++
int register_android_os_MessageQueue(JNIEnv* env) {
// 注册native方法
int res = RegisterMethodsOrDie(env, "android/os/MessageQueue", gMessageQueueMethods,
NELEM(gMessageQueueMethods));
// 赋值java的MessageQueue类
jclass clazz = FindClassOrDie(env, "android/os/MessageQueue");
// 把java的mptr赋值给这里的mPtr,mPtr即C++的MessageQueue地址
gMessageQueueClassInfo.mPtr = GetFieldIDOrDie(env, clazz, "mPtr", "J");
// 把java的dispatchEvents方法赋值给这里的dispatchEvents
gMessageQueueClassInfo.dispatchEvents = GetMethodIDOrDie(env, clazz,
"dispatchEvents", "(II)I");
return res;
}
```
这个方法在我们之前分析JNI注册方法的时候,已经分析过了,这里在来看一下。方法最后的GetMethodIDOrDie看到吗,这个是获取clazz类中的dispatchEvents方法,clazz这里被声明为MessageQueue类,所以是调用java层MessageQueue的dispatchEvents方法,所以前面handleEvent会调用到java层的dispatchEvents方法,这个方法返回的是最终这个fd监听的类型,这个java层的方法我们稍后来看,这里可以看到这里可能会因为fd被关闭等原因导致监听的fd失效或者本身业务逻辑上的一些需求需要改变监听的类型,所以这个java层的回调方法会返回一个新的监听类型,如果监听类型为0,说明这个fd已经是无效的了,所以会返回。否则如果新返回的监听类型和之前的不一样,那么会再调用setFileDescriptorEvents方法更新这个fd在epoll中的监听,这个方法的过程又回到前面分析的流程了。我们在回到java层,看下回调的dispatchEvents这个方法:
```java
private int dispatchEvents(int fd, int events) {
// Get the file descriptor record and any state that might change.
final FileDescriptorRecord record;
final int oldWatchedEvents;
final OnFileDescriptorEventListener listener;
final int seq;
synchronized (this) {
// 获取fd对应的record
record = mFileDescriptorRecords.get(fd);
if (record == null) {
return 0; // spurious, no listener registered
}
oldWatchedEvents = record.mEvents; // 获取监听类型
events &= oldWatchedEvents; // filter events based on current watched set
if (events == 0) { // 之前注册这个监听类型和现在调用传入的监听类型不一样了(&操作为0了),return
return oldWatchedEvents; // spurious, watched events changed
}
listener = record.mListener; // 回调方法
seq = record.mSeq; // record的序列
}
// Invoke the listener outside of the lock.
// 调用这个回调方法,
int newWatchedEvents = listener.onFileDescriptorEvents(
record.mDescriptor, events);
if (newWatchedEvents != 0) {// 回调方法返回的监听类型不为0
newWatchedEvents |= OnFileDescriptorEventListener.EVENT_ERROR;
}
// Update the file descriptor record if the listener changed the set of
// events to watch and the listener itself hasn't been updated since.
if (newWatchedEvents != oldWatchedEvents) { // 新的监听类型和老的不一样
synchronized (this) {
// 获取这个fd对应的record
int index = mFileDescriptorRecords.indexOfKey(fd);
// 判断下record集合中取出的fd对象是否一样
if (index >= 0 && mFileDescriptorRecords.valueAt(index) == record
&& record.mSeq == seq) {
// 一样的话就更新监听类型
record.mEvents = newWatchedEvents;
// 如果监听类型是0,说明fd不存在了,从record集合中移除
if (newWatchedEvents == 0) {
mFileDescriptorRecords.removeAt(index);
}
}
}
}
// Return the new set of events to watch for native code to take care of.
return newWatchedEvents; // 返回新的监听类型
}
```
这个方法会先取出之前保存的java层的fd对应的封装对象FileDescriptorRecord,如果没有这个对象就返回0。否则会对象之前添加在jave层的监听类型和线性传入的监听类型,如果完全不一样,说明和之前添加的不一样了,那么返回之前保存的,可能需要重新再native层注册了。如果有某些监听类型是一样的,那么取出那些一样的监听类型,会调用OnFileDescriptorEventListener的onFileDescriptorEvents方法,这个方法是有开发者来实现的,在当初添加java层自定义fd的时候传入的,返回的结果是一个新的监听类型,这个具体的逻辑就根据开发者自己来实现了,返回新的监听类型后,在和老的监听类型对比,如果一样那就返回一样的这个类型。否则不一样的话,那就先更新下java层保存的这个fd的封装对象中的监听类型,改成这个新的,如果是0的话,说明这个fd无效了,从fd的集合中移除,最后还会把这个新的监听类型返回给native层,native会根据这个值决定是否要更新native层的这个fd的监听类型。
至此整个自定义fd的流程就结束了,可以看出自定义fd不像默认的Handler消息机制,epoll被唤醒后会返回到java层,然后获取message在进行分发,自定义fd可以从java层发起请求,最后实现的回调函数也可以在java层,但是整个消息获取和分发都是在native层的,一般我们在java开发也不太用得到自定义fd,他主要还是面向native层使用的,我们知道有自定义fd就可以了,万一遇到有这个需求的时候可以想得到他。
我们平常使用Handler大部分情况都是在jave层开发app时候使用的,所以都是通过java层的Handler来发送消息和处理消息的,但是既然Handler有native层,所以在native层也是可以使用Handler发送消息的,虽然我们一般直接在native层开发并且使用Handler的情况不多,但是有些系统模块也是会使用到native层的Handler,比如我们熟悉的View相关的模块就会使用native层的Handler来发送消息,我们之后有机会会再聊一聊View的整个体系,所以我们还是了解一下关于native层Handler的发送消息的部分,拓宽下视野,说不定哪天就遇到了需要的地方。
# native层的Handler
在之前的分析中,我们知道了Looper和MessageQueue在java层和native层都各自有一份,同样的Handler在java层和native层也是都存在的,我们先看下native层的Handler的定义:
```c++
// Looper.h
class MessageHandler : public virtual RefBase {
protected:
virtual ~MessageHandler();
public:
/**
* Handles a message.
*/
virtual void handleMessage(const Message& message) = 0;
};
```
native层的Handler是MessageHandler类,这个类中主要有一个虚方法handleMessage,这个方法也就是native层处理消息的回调方法,具体在实际使用中,不直接使用MessageHandler,会把这个MessageHandler包装为一个弱引用类型的WeakMessageHandler类,这个在c++中就是智能指针,就类似于java中的弱引用,我们看下WeakMessageHandler这个类:
```c++
class WeakMessageHandler : public MessageHandler {
protected:
virtual ~WeakMessageHandler();
public:
WeakMessageHandler(const wp<MessageHandler>& handler);
virtual void handleMessage(const Message& message);
private:
wp<MessageHandler> mHandler;
};
```
这个类的构造方法就是一个MessageHandler,这里把MessageHandler封装为一个指针智能,也即若引用。具体handleMessage的实现,依赖于每个实现模块自己的逻辑需求,最后处理消息的回调就会调用到handleMessage这个方法,这个回调的过程,我们在前一篇文章分析epoll被唤醒后的消息处理时候已经看到过了,还不太清楚的话可以去看看第二篇分析Handler的文章,不过我们在后面分析完发送消息的流程后会再提一下,以便让整个分析的流程保持一点完整性。
我们知道了native层Handler的类了,接下去我们看下native层是怎么发消息的。native层发送消息有sendMessage和sendMessageDelayed,这两个方法大同小异,和java层的意义一样,最后调用的流程也都一样,sendMessageDelayed可以设置一个延迟执行的时间,这个大家应该都能懂,我们就只看sendMessage方法,这个方法在Looper类中:
```c++
void Looper::sendMessage(const sp<MessageHandler>& handler, const Message& message) {
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
sendMessageAtTime(now, handler, message);
}
```
这里sendMessage方法有2个参数,第一个就是一个MessageHandler类,第二个是具体的一个Message,这个Message我们在之前的文章中介绍过了,他里面主要就是一个what的字段,表示这个消息是具体的哪一个,含义和java层的一样。之后会继续调用sendMessageAtTime方法,传入上面这2个参数外,在把当前系统时间也传入,我们继续看sendMessageAtTime方法:
```c++
void Looper::sendMessageAtTime(nsecs_t uptime, const sp<MessageHandler>& handler,
const Message& message) {
#if DEBUG_CALLBACKS
ALOGD("%p ~ sendMessageAtTime - uptime=%" PRId64 ", handler=%p, what=%d",
this, uptime, handler.get(), message.what);
#endif
size_t i = 0;
{ // acquire lock
AutoMutex _l(mLock);
size_t messageCount = mMessageEnvelopes.size();
while (i < messageCount && uptime >= mMessageEnvelopes.itemAt(i).uptime) {
i += 1;
}
MessageEnvelope messageEnvelope(uptime, handler, message);
mMessageEnvelopes.insertAt(messageEnvelope, i, 1);
if (mSendingMessage) {
return;
}
} // release lock
// Wake the poll loop only when we enqueue a new message at the head.
if (i == 0) {
wake();
}
}
```
这个发送方法其实也很简单,这里有个mMessageEnvelopes容器,之前我们也分析过,他的类型是MessageEnvelope,这个是个结构体:
```c++
struct MessageEnvelope {
MessageEnvelope() : uptime(0) { }
MessageEnvelope(nsecs_t u, const sp<MessageHandler> h,
const Message& m) : uptime(u), handler(h), message(m) {
}
nsecs_t uptime;
sp<MessageHandler> handler;
Message message;
};
```
可以看到他里面就保留了三个参数,就是sendMessageAtTime方法中的三个参数。sendMessageAtTime方法会遍历mMessageEnvelopes这个容器,然后根据MessageEnvelope对象的uptime,按照时间顺序把当前这个消息插入到mMessageEnvelopes中,这个逻辑和java层插入消息是一样,只不过这里要比java层简单,没有什么同步屏障之类的消息,所以只要找到合适的位置,插入就可以了。接着如果mSendingMessage为true,之前为true之前我们分析消息分发流程的时候看过了,表示正在执行一个native层的消息回调方法,所以这里添加完新的消息后,直接返回就可以了,新的消息在消息处理那边后面会取出来再处理的。如果这里i为0,表示这个是第一个添加到native中的消息,那么就会执行wake方法唤醒epoll,让epoll那里去取消息。
这个就是native层发送消息的方法了,其实相比java层来说,还是容易一些的。除了添加外,还有移除消息的方法,其实也很简单,我们就看一眼代码,也没什么好多说的。
```c++
void Looper::removeMessages(const sp<MessageHandler>& handler) {
#if DEBUG_CALLBACKS
ALOGD("%p ~ removeMessages - handler=%p", this, handler.get());
#endif
{ // acquire lock
AutoMutex _l(mLock);
for (size_t i = mMessageEnvelopes.size(); i != 0; ) {
const MessageEnvelope& messageEnvelope = mMessageEnvelopes.itemAt(--i);
if (messageEnvelope.handler == handler) {
mMessageEnvelopes.removeAt(i);
}
}
} // release lock
}
void Looper::removeMessages(const sp<MessageHandler>& handler, int what) {
#if DEBUG_CALLBACKS
ALOGD("%p ~ removeMessages - handler=%p, what=%d", this, handler.get(), what);
#endif
{ // acquire lock
AutoMutex _l(mLock);
for (size_t i = mMessageEnvelopes.size(); i != 0; ) {
const MessageEnvelope& messageEnvelope = mMessageEnvelopes.itemAt(--i);
if (messageEnvelope.handler == handler
&& messageEnvelope.message.what == what) {
mMessageEnvelopes.removeAt(i);
}
}
} // release lock
}
```
这里是两个不同参数的移除方法,一个是根据Handler,另一个是同时根据handler和what,具体代码很简单,相信大家一看就懂,也就不说了。
最后我们在回到之前分析过的Looper.cpp的pollInner方法中,在这个方法中会唤醒epoll,之后取出消息进行分发执行。这个方法我们之前已经分析过了,这里主要重新再看一下和native相关的部分,之前由于对native消息还不太熟悉,可能看代码理解的没那么深入,现在我们在分析过native层的发送消息后,在来看一下这里分发处理消息,相信对这块的理解会更深了。
```c++
int Looper::pollInner(int timeoutMillis) {
..........
mNextMessageUptime = LLONG_MAX;
while (mMessageEnvelopes.size() != 0) { // 这段是处理C++层的默认fd的消息
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
// 获取第一个消息
const MessageEnvelope& messageEnvelope = mMessageEnvelopes.itemAt(0);
if (messageEnvelope.uptime <= now) { // 如果这个消息是到时候了
{ // obtain handler
// 获取这个消息的Handler
sp<MessageHandler> handler = messageEnvelope.handler;
// 或者这个消息
Message message = messageEnvelope.message;
mMessageEnvelopes.removeAt(0); // 从消息集合中移除
mSendingMessage = true;
mLock.unlock();
#if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS
ALOGD("%p ~ pollOnce - sending message: handler=%p, what=%d",
this, handler.get(), message.what);
#endif
handler->handleMessage(message); // 调用c++的Handler处理方法
} // release handler
mLock.lock(); // 解锁
mSendingMessage = false;
result = POLL_CALLBACK; // 触发fd变化的监听类型
} else { // 这个消息还没到时间,把他的时间设置给下一次时间mNextMessageUptime
// The last message left at the head of the queue determines the next wakeup time.
mNextMessageUptime = messageEnvelope.uptime;
break;
}
}
............
}
```
这里我们略去了所有和native层消息无关的代码,只看最后的消息分发部分。由于我们之前添加消息的时候已经按时间顺序排序好了,所以这里取出消息的时候就是从头开始顺序取消息,如果遇到一个消息的时间是晚于当前系统时间的,那么下一次的唤醒事件就设置为这个消息的uptime。如果这个消息的uptime是小于当前时间的,说明可以被执行了,那么取出他的MessageHandler和Message,然后从MessageEnvelopes容器中把这个消息移除并且设置mSendingMessage为true,表示当前正在执行一个native层的消息,之后就调用MessageHandler的handleMessage执行消息了,handleMessage方法是调用者实现的,执行完成后把mSendingMessage重新置为false,最后result赋值为POLL_CALLBACK表示当前epoll唤醒后已经执行了一个消息了,这后面的流程就不再往下说了,之前分析pollInner方法的时候都已经说过了,不太熟悉的同学可以去看[Handler源码解析(二)](https://liqi.site/archives/handler%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90%E4%BA%8C)这篇文章,已经做了详细的分析了。
到这里native层的消息分发也已经说完了,我们平时开发app的时候可能会native层的消息分发接触的不多,但是Android系统中是有用到的,比如在view模块的源码中就有用到native层的Handler消息分发,比如在其中有用到InputQueue这个类:
```c++
class InputQueue : public AInputQueue, public MessageHandler {
.........
protected:
virtual void handleMessage(const Message& message);
............
}
```
这里就简单看一眼InputQueue这个类的结构,可以看到他是一个MessageHandler,并且他也继承了MessageHandler的handleMessage,具体实现我们这里就不再看了,后面我们会再分析和View相关的源码的,到那个时候我们再详细看这块。以上仅仅就是举个例子,使我们知道native层的Handler也是有使用的,我们既然分析了Handler的源码就尽可能把涉及到的部分都了解一下,也算做一点指示储备。
好了,通过三篇关于Handler的源码分析,我们从Handler的实现核心机制epoll的讲解,到普通java层Handler的发送和分发处理原理,再到拓展的添加自定义文件描述符到epoll以及native层的Handler消息的分发分析,我们基本对Handler的整体流程都有了比较深入的理解了,通过对底层源码的理解,相信对我们在工作中的开发会有更大的帮助。Handler模块的系列分析暂时就到这里了,虽然三篇文章讲了不少,但是还有很多细节的东西没有讲到,如果将来有需要补充的会在继续更新,那么这篇文章就到这里了,我们下篇文章再见。
Handler源码解析(三)