Handler中的奇奇怪怪

背景

了解Handler原理时,有一个疑问handler中的休眠/唤醒不必Java中wait和notify呢,而是调用native办法(nativePollOnce/nativeWake)呢,奇奇怪怪 的又得需求学起来,没事找事的一天嘛。

这样不行吗?wait/notify 伪代码

MessageQueue.java
//调用MessageQueue的next办法获取音讯
Messagenext(){
、、、
synchronized(this){
//这时候查看行列有没有音讯,没有音讯调用this.wait()等候
if(message==null){
this.wait();
}

if(有音讯但音讯未到期){
this.wait(time);
}
}
、、、
}
//调用MessageQueue.enqueueMessage()增加音讯
enqueueMessage(Messagemessage){
、、、
synchronized(this){
//音讯参加行列后会调用this.notity()唤醒next()办法
if(message!=null){
this.notify();
}
}
、、、
}

在学习nativePollOnce/nativeWake前,还需求对Linux相关的常识了解一下。

Linux相关

eventfd

eventfd 是从内核2.6.22开端支撑的一种新的事情等候/告诉机制。用来告诉事情的文件描述符,它不仅能够用于进程间的通信,还能够用户内核发信号给用户层的进程。简而言之:eventfd 便是用来触发事情告诉,它只有一个创立办法:

int eventfd(unsigned int initval, int flags); 表明创立一个 eventfd 文件并回来文件描述符

  • 参数:initval, 初始值

  • 参数:flags

    • EFD_CLOEXEC 会自动封闭这个文件描述符。
    • EFD_NONBLOCK 履行 read / write 操作时,不会堵塞。
    • EFD_SEMAPHORE count 递减 1,每次读到的值为1。

相关操作

  • write(): 其实是履行 add 操作,累加 count值。
  • read(): 依据设置不同的flags标记,读取到不同的值

EFD_SEMAPHORE:读到的值为 1,同时 count 值递减 1。
其他的都是:读取 count 值后置 0

阿西吧什么乱七八糟的,别急看看这个下面这个Demo;

eventfd demo

#include<cstdlib>
#include<inttypes.h>
#include<iostream>
#include<stdint.h>
#include<stdio.h>
#include<stdlib.h>
#include<string>
#include<sys/eventfd.h>
#include<unistd.h>
usingnamespacestd;
intmain(intargc,char*argv[]){
intevent_fd;
if(argc<2){
std::cout<<"pleaseinputllegalargv"<<endl;
exit(EXIT_FAILURE);
}
event_fd=eventfd(0,EFD_NONBLOCK);
if(event_fd==-1){
std::cout<<"createevebtFdfail"<<endl;
exit(EXIT_FAILURE);
}
switch(fork()){
case0:
for(intj=1;j<argc;j++){
longu=atoi(argv[j]);
printf("Childwriting%lutoefd\n",u);
write(event_fd,&u,sizeof(long));
}
printf("Childcompletedwriteloop\n");
exit(EXIT_SUCCESS);
default:
sleep(2);
longu;
printf("Parentabouttoread\n");
read(event_fd,&u,sizeof(long));
printf("Parentsfirstread%lufromefd\n",u);
longu2;
read(event_fd,&u2,sizeof(long));
printf("Parentssecondread%lufromefd\n",u2);
exit(EXIT_SUCCESS);
}
}

⚠️ #include <sys/eventfd.h> 是在Linux操作体系中的,在Mac电脑中是找不到包的,需求装虚拟机或许其他的C++开发软件包,这里推荐一个在线免费的编译C++的软件Lightly

Android Handler中的休眠/唤醒

Q eventfd和socket、pipe、fd_set、有什么区别和联系?

Epoll

epoll是Linux内核为处理大批量文件描述符而改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中,只有少量活泼的情况下的体系CPU利用率。另一点原因便是获取事情的时候,它无须遍历整个被侦听的描述符集,只需遍历那些被内核IO事情异步唤醒而参加Ready行列的描述符调集就行了。提高应用程序功率。 来源自百度百科

epoll API

int epoll_create(int size)

创立 eventpoll 目标,回来一个 epfd,即 eventpoll 句柄。

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)

对eventpoll履行的操作,回来值:成功 0;失利 -1

  • epfd 对一个 eventPoll 进行操作

  • op 表明要履行的操作,包括 :

    • EPOLL_CTL_ADD:注册新的fd到epfd中;
    • EPOLL_CTL_MOD:修改现已注册的fd的监听事情;
    • EPOLL_CTL_DEL:从epfd中删去一个fd;
  • fd 表明被监听的文件描述符;

  • event 表明要被监听的事情,包括:

    • EPOLLIN(表明被监听的fd有能够读的数据)
    • EPOLLOUT(表明被监听的fd有能够写的数据)
    • EPOLLPRI(表明有可读的紧迫数据)
    • EPOLLERR(对应的fd产生反常)
    • EPOLLHUP(对应的fd被挂断)
    • EPOLLET(设置EPOLL为边际触发)
    • EPOLLONESHOT(只监听一次)

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)

回来值:监听到的产生的事情数 等候 epfd 监听的 fd 所产生对应的事情。

  • epfd 表明 eventpoll句柄;
  • events 表明回传处理事情的数组;
  • maxevents 表明每次能处理的最大事情数;
  • timeout:等候 IO 的超时时刻,-1 表明一向堵塞直到来 IO 被唤醒,大于 0 表明堵塞指定的时刻后被唤醒

epoll 运用示例

创立一个管道,运用 epoll 监听管道读端,然后进入堵塞:

#include<iostream>
#include<stdio.h>
#include<string>
#include<sys/epoll.h>
#include<sys/eventfd.h>
#include<unistd.h>

usingnamespacestd;
intmain(intargc,char*argv[]){
if(argc<2){
exit(EXIT_FAILURE);
}

intevent_fd;
intepoll_fd;

event_fd=eventfd(0,EFD_NONBLOCK);
if(event_fd==-1){
std::cout<<"createevebtFdfail";
exit(EXIT_FAILURE);
}

epoll_fd=epoll_create(8);

if(epoll_fd<0){
std::cout<<"createepollFdfail";
}

structepoll_eventread_event;
read_event.events=EPOLLIN;

epoll_ctl(epoll_fd,EPOLL_CTL_ADD,event_fd,&read_event);

switch(fork()){
case0:
for(intj=1;j<argc;j++){
longu=atoi(argv[j]);
sleep(1);
printf("Childwriting%lutoefd\n",u);
write(event_fd,&u,sizeof(long));
}
printf("Childcompletedwriteloop\n");
exit(EXIT_SUCCESS);

default:
printf("Parentabouttoread\n");
structepoll_eventevents[16];
intret;
while(1){
ret=epoll_wait(epoll_fd,events,1,-1);
printf("Parentepoll_waitreturnret:%d\n",ret);
if(ret>0){
longu;
read(event_fd,&u,sizeof(long));
printf("Parentsread%lufromefd\n",u);
}
}

exit(EXIT_SUCCESS);
}
}

结果

Android Handler中的休眠/唤醒

Handler 中的 epoll 源码剖析

主要剖析 MessageQueue.java 中的三个 native 函数:

privatenativestaticlongnativeInit();//初始化
privatenativevoidnativePollOnce(longptr,inttimeoutMillis);//堵塞
privatenativestaticvoidnativeWake(longptr);//唤醒

「nativeInit 回来long,这是为什么?」 预知一下,或许这个能够回答咱们的问题

nativeInit

首要来看 nativeInit 办法,nativeInit 在 MessageQueue 结构函数中被调用,其回来了一个底层目标的指针

MessageQueue(booleanquitAllowed){
mQuitAllowed=quitAllowed;
mPtr=nativeInit();//保存NativeMessageQueue
}
//android_os_MessageQueue.cpp
staticjlongandroid_os_MessageQueue_nativeInit(JNIEnv*env,jclassclazz){
NativeMessageQueue*nativeMessageQueue=newNativeMessageQueue();
...
returnreinterpret_cast<jlong>(nativeMessageQueue);
}

einterpret_cast<type-id> (expression)

type-id 必须是一个指针、引用、算术类型、函数指针或许成员指针。它能够把一个指针转换成一个整数,也能够把一个整数转换成一个指针(先把一个指针转换成一个整数,再把该整数转换成原类型的指针,还能够得到原先的指针值
回来值是NativeMessageQueue,而 NativeMessageQueue 初始化时会创立一个底层的 Looper 目标:

NativeMessageQueue::NativeMessageQueue():
mPollEnv(NULL),mPollObj(NULL),mExceptionObj(NULL){
mLooper=Looper::getForThread();
if(mLooper==NULL){
mLooper=newLooper(false);
Looper::setForThread(mLooper);
}
}

Looper 的结构函数如下:

Looper::Looper(boolallowNonCallbacks):
mAllowNonCallbacks(allowNonCallbacks),...{
mWakeEventFd=eventfd(0,EFD_NONBLOCK|EFD_CLOEXEC);
...
rebuildEpollLocked();
}

有没有了解的感觉,这和咱们的Epoll的demo很相似,首要经过创立eventFd, ,专门用于事情告诉。接着来看 rebuildEpollLocked 办法:

voidLooper::rebuildEpollLocked(){
mEpollFd=epoll_create(EPOLL_SIZE_HINT);
structepoll_eventeventItem;
memset(&eventItem,0,sizeof(epoll_event));
eventItem.events=EPOLLIN;
eventItem.data.fd=mWakeEventFd;
intresult=epoll_ctl(mEpollFd,EPOLL_CTL_ADD,mWakeEventFd,&eventItem);
...
}

能够看到咱们现已了解的 epoll 操作了:经过 epoll_create 创立 epoll 目标,然后调用 epoll_ctl 增加 mWakeEventFd 为要监听的文件描述符。

nativePollOnce

之前学习 Handler 机制时多次看到过 nativePollOnce 办法,也知道它会进入休眠,下面就具体看看它的原理。对应的底层调用同样是在 android_os_MessageQueue.cpp 中:

staticvoidandroid_os_MessageQueue_nativePollOnce(JNIEnv*env,jobjectobj,
jlongptr,jinttimeoutMillis){
NativeMessageQueue*nativeMessageQueue=reinterpret_cast<NativeMessageQueue*>(ptr);
nativeMessageQueue->pollOnce(env,obj,timeoutMillis);
}

voidNativeMessageQueue::pollOnce(JNIEnv*env,jobjectpollObj,inttimeoutMillis){
mLooper->pollOnce(timeoutMillis);
...
}

能够看到完成同样是在 Looper.cpp 中,接着来看 Looper 的 pollOnce 办法:

intLooper::pollOnce(inttimeoutMillis,int*outFd,int*outEvents,void**outData){
for(;;){
...
result=pollInner(timeoutMillis);
}
}

intLooper::pollInner(inttimeoutMillis){
...
structepoll_eventeventItems[EPOLL_MAX_EVENTS];
inteventCount=epoll_wait(mEpollFd,eventItems,EPOLL_MAX_EVENTS,timeoutMillis);
...

至此经过调用 epoll_wait 办法,当时线程进入休眠,等候被唤醒。

nativeWake

最终来看如何经过 nativeWake 唤醒线程,首要是 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();
  }

与 nativeInit、nativePollOnce 相同,最终完成都是在 Looper.cpp 中,Looper 的 wake 办法如下:

voidLooper::wake(){
uint64_tinc=1;
ssize_tnWrite=TEMP_FAILURE_RETRY(write(mWakeEventFd,&inc,sizeof(uint64_t)));
if(nWrite!=sizeof(uint64_t)){
if(errno!=EAGAIN){
LOG_ALWAYS_FATAL("Couldnotwritewakesignaltofd%d:%s",
mWakeEventFd,strerror(errno));
}
}
}

其中关键逻辑是对 mWakeEventFd 发起写入操作,然后唤醒 nativePollOnce 中经过 epoll_wait 进入休眠的线程。

Object.wait/notify源码剖析

TODO 这里的源码剖析藏着今后吧

结束

回答刚开端的疑问handler中的休眠/唤醒不必Java中wait和notify呢,而是调用nativePollOnce/nativeWake呢?

MessageQueue.java
//调用MessageQueue的next办法获取音讯
Messagenext(){
、、、
synchronized(this){
//这时候查看行列有没有音讯,没有音讯调用this.wait()等候
if(message==null){
this.wait();
}

if(有音讯但音讯未到期){
this.wait(time);
}
}
、、、
}
//调用MessageQueue.enqueueMessage()增加音讯
enqueueMessage(Messagemessage){
、、、
synchronized(this){
//音讯参加行列后会调用this.notity()唤醒next()办法
if(message!=null){
this.notify();
}
}
、、、
}

private native static long nativeInit(); 回来值是nativeMessage目标,堵塞时会将mPtr当成参数nativePollOnce();

如果单纯用object.wait,那关于native层的音讯是处理不到的,行列闲暇时不能只判别Java层的MessageQueue,nativePollOnce去判别Native层,若大家都闲暇,办法会堵塞到native的epoll_wait()办法中,等候唤醒。 单纯用wait和notify,只能处理java层的音讯,关于体系的音讯不能处理。