Android 消息机制(native层)

冰岩作坊 April 19, 2023

书接上文:Android 消息机制(Java层)native层中一些重要的类:

nativeInit()

MessageQueue在Looper的构造器中创建,在MessageQueue的构造器中会执行下面代码:

1
MessageQueue(boolean quitAllowed) 

在这里会执行native层的nativeInit方法,并将其返回值保存到mPtr中,下面再看一下native层中这个方法的实现:

1
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz

可以发现在这里创建了一个NativeMessageQueue对象,增加了其引用计数,并将这个对象的指针强制转换为Long类型返回,也就是说java层保存了native层nativeMessageQueue的指针,下面再看一下NativeMessageQueue的构造函数:

1
NativeMessageQueue::NativeMessageQueue()     : mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) }

在这个构造器中实现了线程中Looper的初始化

下面看一下native层中Looper的构造函数:

1
Looper::Looper(bool allowNonCallbacks) :        mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),        mPolling(false), mEpollFd(-1), mEpollRebuildRequired(false),        mNextRequestSeq(0), mResponseIndex(0), mNextMessageUptime(LLONG_MAX) 

其中mWakeEventFd是文件描述符,在linux中打开或创建一个文件都会返回一个文件描述符,读写文件需要使用这个文件描述符来指定待读写的文件,所以文件描述符就是指代被打开的文件,所有文件的IO操作都需要这个文件描述符

1
void Looper::rebuildEpollLocked()     //2、创建一个新的epoll文件描述符,并注册wake管道    mEpollFd = epoll_create(EPOLL_SIZE_HINT);//EPOLL_SIZE_HINT8    struct epoll_event eventItem;    memset(& eventItem, 0, sizeof(epoll_event)); //置空eventItem    //3、设置监听事件类型和需要监听的文件描述符    eventItem.events = EPOLLIN;//监听可读事件(EPOLLIN)    eventItem.data.fd = mWakeEventFd;//设置唤醒事件的fd(mWakeEventFd)    //4、将唤醒事件fd(mWakeEventFd)添加到epoll文件描述符(mEpollFd),并监听唤醒事件fd(mWakeEventFd)    int result = epoll_ctl(mEpollFdEPOLL_CTL_ADDmWakeEventFd, & eventItem);           //5、将各种事件,如键盘、鼠标等事件的fd添加到epoll文件描述符(mEpollFd),进行监听    for (size_t i = 0; i < mRequests.size(); i++)     }}}

在这个方法中使用了epoll机制,下面是对epoll机制的一些简单了解:

那么这一部分总的来说就是以下几个步骤:

nativePollOnce

这个方法的调用在messagequeue.next中

1
Message next()     //... }

next方法返回一个Message对象,在没有消息时nativePollOnce会进入阻塞

下面看一下这个函数的源码:

1
static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,        jlong ptr, jint timeoutMillis) 

这个方法首先从java层中获取到保存的nativeMessageQueue对象,然后再调用pollOnce方法,在这个方法内部又调用了Looper.pollOnce方法,下面看一下这个方法的实现

1
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData)         //处理内部轮询        result = pollInner(timeoutMillis);    }}

该方法也是一个死循环,如果result不等于0,那么就会返回到java层,那么看一下这个result的赋值操作,

1
int Looper::pollInner(int timeoutMillis)  else         } else     }        //2、下面是处理Native的Message    Done:;    mNextMessageUptime = LLONG_MAX;    //mMessageEnvelopes是一个Vector集合,它代表着native中的消息队列    while (mMessageEnvelopes.size() != 0)             mLock.lock();            mSendingMessage = false;            //result等于POLL_CALLBACK,表示某个监听事件被触发            result = POLL_CALLBACK;        } else     }    //释放锁    mLock.unlock();    //...}

这个方法内执行的逻辑大致可分为三步:

  1. 执行epoll_wait方法,等待事件发生或超时
    如果文件描述符监听的任何时间发生,那么epoll_wait就会监听到,并将事件放入到事件集合中,并返回发生的时间数目,timeOut就是从java层中传过来的nextPollTimeOutMilis,当值为-1时表示java层的消息队列中没有消息,会一直等待下去,当值为0时就会立即返回,另外epoll机制只会把发生了的事件放入到事件集合中。

  2. 遍历事件集合,检测是哪个文件描述符发生了IO事件

  3. 处理native层的message
    这里面就有一个结构体:

1
class Looper : public RefBase         MessageEnvelope(nsecs_t u, const sp h, const Messagem) : uptime(u), handler(h), message(m) {}        nsecs_t uptime;        //收信人handler        sp handler;        //信息内容message        Message message;    };       //...}

native层中的message存放在messageEnvelop中,然后进入循环中,如果到达执行时间,就会调用MessageHandler的handleMessage方法处理消息

nativeWake

这个方法执行在消息入队时

1
boolean enqueueMessage(Message msg, long when)     }}

这个方法最终会调用到native层的Looper.wake方法

1
void Looper::wake() 

这个方法其实是使用write方法向管道中写入字符inc,如果写入失败就重复写入,直到写入成功为止,这个字符起到一个通知作用,用来通知线程处理消息

java层和native层通过MessageQueue的JNI进行连接,从而使得MessageQueue既能处理java层又能处理native层