[Android] 输入系统(一)
2015-08-22 03:00
309 查看
Android输入系统是人与机器交互最主要的手段。我们通过按键或者触碰屏幕,会先经由linux产生中断,进行统一的处理过后,转换成Android能识别的事件信息,然后Android的输入系统去获取事件,分发给上层用户程序进行处理。
下面在细分一下输入事件在Android系统中的流程:
从图上能看到,输入事件有四个处理的地方:
InputReaderThread
InputDispatcherThread
WindowInputEventReceiver
handleReceiverCallback
上面四个地方按功能来划分,其中:
InputReaderThread负责从输入设备中获取事件,事件加入inboundQueue队列。
InputDispatcherThread负责把inboundQueue中的事件信息取出,并且从系统中获取该事件所需要分发到的目标(窗口),把事件与目标分别整合成分发项,把分发项加入outboundQueue。另外,这里还是事件的分发端,负责把outboundQueue中的事件取出,通过InputChannel进行分发。分发完成后把该事件入waitQueue。
WindowInputEventReceiver是事件的接收端。事件会在这里被onTouch这类回调函数处理
handleReceiveCallback用于接收处理过后的反馈信息,事件在WindowInputEventReceiver端被处理成功或者失败,将会通过InputChannel返回Handled或者UNHandled消息。handleReceiveCallback接收到消息后将会对waitQueue中的事件进行出队列处理。
InputManager的构造函数如下:
InputManager::InputManager(
constsp<EventHubInterface>&eventHub,
constsp<InputReaderPolicyInterface>&readerPolicy,
constsp<InputDispatcherPolicyInterface>&dispatcherPolicy){
mDispatcher=newInputDispatcher(dispatcherPolicy);
mReader=newInputReader(eventHub,readerPolicy,mDispatcher);
initialize();
}
可以看到构造了InputDispatcher与InputReader两个类,这两个类是功能类,分别为InputDispatcherThread与InputReaderThread提供功能。另外,在构建InputReader的时候,把mDispatcher传递了进去,用于构建QueueInputListener。在这里可以提前说明一下这个成员的作用:把输入事件添加到inboundQueue。
构造函数最后调用了initialize,构建InputReaderThread、InputDispatcherThread。
voidInputManager::initialize(){
mReaderThread=newInputReaderThread(mReader);
mDispatcherThread=newInputDispatcherThread(mDispatcher);
}
InputManager的start用于启动InputReaderThread与InputDispatcherThread这两个线程。
status_tInputManager::start(){
status_tresult=mDispatcherThread->run("InputDispatcher",PRIORITY_URGENT_DISPLAY);
if(result){
ALOGE("CouldnotstartInputDispatcherthreadduetoerror%d.",result);
returnresult;
}
result=mReaderThread->run("InputReader",PRIORITY_URGENT_DISPLAY);
if(result){
ALOGE("CouldnotstartInputReaderthreadduetoerror%d.",result);
mDispatcherThread->requestExit();
returnresult;
}
returnOK;
}
boolInputReaderThread::threadLoop(){
mReader->loopOnce();
returntrue;
}
mReader即在构建InputReaderThread时传进来的InputReader,负责实现读取输入事件所需要的各种功能。InputReader::loopOnce用于读取一次输入事件。其中,读取一次包含三个主要动作:
获取输入事件
处理输入事件
输入数据flush
voidInputReader::loopOnce(){
size_tcount=mEventHub->getEvents(timeoutMillis,mEventBuffer,EVENT_BUFFER_SIZE);
{//acquirelock
processEventsLocked(mEventBuffer,count);
}
mQueuedListener->flush();
}
EPOLL_ID_INORIFY.输入设备打开或者删除的事件
EPOLL_ID_WAKE.管道发送过来的模拟事件
EPOLL_IN.按键,触摸这类实际操作事件
EPOLL_ID_INOTIFY,用于监控某个目录(子目录)下是否有新增或者删除文件,在这里用于监视/dev/input,这个是输入设备文件所在的目录,如果有新增设备,则会在该目录内创建新文件;如果删除设备,则该目录的相应文件会被删除。
if(eventItem.data.u32==EPOLL_ID_INOTIFY){
if(eventItem.events&EPOLLIN){
mPendingINotify=true;
}else{
ALOGW("Receivedunexpectedepollevent0x%08xforINotify.",eventItem.events);
}
continue;
}
......
if(mPendingINotify&&mPendingEventIndex>=mPendingEventCount){
mPendingINotify=false;
readNotifyLocked();
deviceChanged=true;
}
status_tEventHub::readNotifyLocked(){
if(event->mask&IN_CREATE){
openDeviceLocked(devname);
}else{
ALOGI("Removingdevice'%s'duetoinotifyevent\n",devname);
closeDeviceByPathLocked(devname);
}
}
EPOLL_ID_WAKE,EventHub有维护一个pipe,当pipe的写入端按照适当格式写入时间后,getEvents可以通过pipe的读取端获取这个虚拟事件
if(eventItem.data.u32==EPOLL_ID_WAKE){
if(eventItem.events&EPOLLIN){
ALOGV("awokenafterwake()");
awoken=true;
charbuffer[16];
ssize_tnRead;
do{
nRead=read(mWakeReadPipeFd,buffer,sizeof(buffer));
}while((nRead==-1&&errno==EINTR)||nRead==sizeof(buffer));
}else{
ALOGW("Receivedunexpectedepollevent0x%08xforwakereadpipe.",
eventItem.events);
}
continue;
}
EPOLL_IN,用于监控设备文件的输入状态,当我们按键或者触摸设备时,我们就能获得EPOLL_IN状态,从而到该设备读取输入事件
if(eventItem.events&EPOLLIN){
int32_treadSize=read(device->fd,readBuffer,
sizeof(structinput_event)*capacity);
event->when=now;
event->deviceId=deviceId;
event->type=iev.type;
event->code=iev.code;
event->value=iev.value;
event+=1;
capacity-=1;
}
监听事件用的是epoll_wait,由于epoll_wait一次能获取的事件可能会有多个,所以一次的getEvents需要对所获得的每个事件都进行上述代码的打包操作,最后返回事件数组。
intpollResult=epoll_wait(mEpollFd,mPendingEventItems,EPOLL_MAX_EVENTS,timeoutMillis);
按键、触摸事件
设备增加、删除事件
voidInputReader::processEventsLocked(constRawEvent*rawEvents,size_tcount){
for(constRawEvent*rawEvent=rawEvents;count;){
int32_ttype=rawEvent->type;
size_tbatchSize=1;
if(type<EventHubInterface::FIRST_SYNTHETIC_EVENT){
int32_tdeviceId=rawEvent->deviceId;
while(batchSize<count){
if(rawEvent[batchSize].type>=EventHubInterface::FIRST_SYNTHETIC_EVENT
||rawEvent[batchSize].deviceId!=deviceId){
break;
}
batchSize+=1;
}
#ifDEBUG_RAW_EVENTS
ALOGD("BatchSize:%dCount:%d",batchSize,count);
#endif
processEventsForDeviceLocked(deviceId,rawEvent,batchSize);
}else{
switch(rawEvent->type){
caseEventHubInterface::DEVICE_ADDED:
addDeviceLocked(rawEvent->when,rawEvent->deviceId);
break;
caseEventHubInterface::DEVICE_REMOVED:
removeDeviceLocked(rawEvent->when,rawEvent->deviceId);
break;
caseEventHubInterface::FINISHED_DEVICE_SCAN:
handleConfigurationChangedLocked(rawEvent->when);
break;
default:
ALOG_ASSERT(false);//can'thappen
break;
}
}
count-=batchSize;
rawEvent+=batchSize;
}
}
在处理按键、触摸事件时,会根据他们设备的类型调用不同的process函数进行处理。对于触摸事件,基本上只是进行赋值,而按键事件则需要通过映射,把从设备文件读取进来的值转换成Android上层能统一处理的按键事件。
voidInputReader::processEventsForDeviceLocked(int32_tdeviceId,
constRawEvent*rawEvents,size_tcount){
InputDevice*device=mDevices.valueAt(deviceIndex);
device->process(rawEvents,count);
}
voidKeyboardInputMapper::process(constRawEvent*rawEvent){
switch(rawEvent->type){
caseEV_KEY:{
if(getEventHub()->mapKey(getDeviceId(),scanCode,usageCode,&keyCode,&flags)){
keyCode=AKEYCODE_UNKNOWN;
flags=0;
}
processKey(rawEvent->when,rawEvent->value!=0,keyCode,scanCode,flags);
}
break;
}
}
上面的mapKey对按键进行了映射处理,processKey用于区分按键的按下或者松开。在processKey的最后,会把事件打包成NotifyKeyArgs,然后通过QueueInputListener把事件push进mArgQueue。由于这里是一个事件数组,所以mArgQueue是必须的。
voidKeyboardInputMapper::processKey(nsecs_twhen,booldown,int32_tkeyCode,
int32_tscanCode,uint32_tpolicyFlags){
if(down){
...
}else{
...
}
NotifyKeyArgsargs(when,getDeviceId(),mSource,policyFlags,
down?AKEY_EVENT_ACTION_DOWN:AKEY_EVENT_ACTION_UP,
AKEY_EVENT_FLAG_FROM_SYSTEM,keyCode,scanCode,newMetaState,downTime);
getListener()->notifyKey(&args);
}
voidQueuedInputListener::notifyKey(constNotifyKeyArgs*args){
mArgsQueue.push(newNotifyKeyArgs(*args));
}
voidQueuedInputListener::flush(){
size_tcount=mArgsQueue.size();
for(size_ti=0;i<count;i++){
NotifyArgs*args=mArgsQueue[i];
args->notify(mInnerListener);
deleteargs;
}
mArgsQueue.clear();
}
voidNotifyKeyArgs::notify(constsp<InputListenerInterface>&listener)const{
listener->notifyKey(this);
}
以notifyKey为例,其目的实际上是把事件队列加入mInboundQueue,但是在入mInboundQueue队列之前,调用了interceptKeyBeforeQueueing,该函数通过jni,调用到PhoneWindowManager的interceptKeyBeforeQueueing。而在入了mInboundQueue队列后,就会调用wake函数去唤醒InputDispatcherThread。下一步就是InputDispatcherThread的工作了。
voidInputDispatcher::notifyKey(constNotifyKeyArgs*args){
mPolicy->interceptKeyBeforeQueueing(&event,/*byref*/policyFlags);
needWake=enqueueInboundEventLocked(newEntry);
if(needWake){
mLooper->wake();
}
}
boolInputDispatcherThread::threadLoop(){
mDispatcher->dispatchOnce();
returntrue;
}
每次分发,调用的都是dispatchOnce,其内部调用dispatchOnceInnerLocked进行分发后,线程会调用pollOnce进入睡眠,等待下次InputReaderThread的wake操作
voidInputDispatcher::dispatchOnce(){
dispatchOnceInnerLocked(&nextWakeupTime);
mLooper->pollOnce(timeoutMillis);
}
分发的过程可以大概分成以下几个步骤:
从mInboundQueue的队列头取出事件
特殊事件的处理,如POLICY_FLAG_PASS_TO_USER这类事件能直接发送到用户,类似于电量不足的这类事件:当电量低于20%时,直接往上层发送事件,而不用知道当前是在哪个Activity
一般事件的处理,进行分发
voidInputDispatcher::dispatchOnceInnerLocked(nsecs_t*nextWakeupTime){
mPendingEvent=mInboundQueue.dequeueAtHead();
//Pokeuseractivityforthisevent.
if(mPendingEvent->policyFlags&POLICY_FLAG_PASS_TO_USER){
pokeUserActivityLocked(mPendingEvent);
}
switch(mPendingEvent->type){
caseEventEntry::TYPE_KEY:{
done=dispatchKeyLocked(currentTime,typedEntry,&dropReason,nextWakeupTime);
break;
}
}
}
分发事件,肯定需要知道事件要分发到哪里,即分发的目标窗口,不过目标窗口可能不止一个。
boolInputDispatcher::dispatchKeyLocked(nsecs_tcurrentTime,KeyEntry*entry,
DropReason*dropReason,nsecs_t*nextWakeupTime){
int32_tinjectionResult=findFocusedWindowTargetsLocked(currentTime,
entry,inputTargets,nextWakeupTime);
//Dispatchthekey.
dispatchEventLocked(currentTime,entry,inputTargets);
returntrue;
}
由于可能存在多个目标窗口,所以需要对每个目标窗口都进行事件分发
voidInputDispatcher::dispatchEventLocked(nsecs_tcurrentTime,
EventEntry*eventEntry,constVector<InputTarget>&inputTargets){
for(size_ti=0;i<inputTargets.size();i++){
prepareDispatchCycleLocked(currentTime,connection,eventEntry,&inputTarget);
}
}
在分发前的准备,就是把事件入outboundQueue队列,不过请注意,这里的队列不同于inboundQueue,因为outboundQueue是窗口相关的,窗口跟InputDispatcherThread间建立起一个连接(connection),该outboundQueue就是connection的成员。
voidInputDispatcher::prepareDispatchCycleLocked(nsecs_tcurrentTime,
constsp<Connection>&connection,EventEntry*eventEntry,constInputTarget*inputTarget){
//Notsplitting.Enqueuedispatchentriesfortheeventasis.
enqueueDispatchEntriesLocked(currentTime,connection,eventEntry,inputTarget);
}
voidInputDispatcher::enqueueDispatchEntriesLocked(nsecs_tcurrentTime,
constsp<Connection>&connection,EventEntry*eventEntry,constInputTarget*inputTarget){
boolwasEmpty=connection->outboundQueue.isEmpty();
//Enqueuedispatchentriesfortherequestedmodes.
enqueueDispatchEntryLocked(connection,eventEntry,inputTarget,
InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT);
enqueueDispatchEntryLocked(connection,eventEntry,inputTarget,
InputTarget::FLAG_DISPATCH_AS_OUTSIDE);
enqueueDispatchEntryLocked(connection,eventEntry,inputTarget,
InputTarget::FLAG_DISPATCH_AS_HOVER_ENTER);
enqueueDispatchEntryLocked(connection,eventEntry,inputTarget,
InputTarget::FLAG_DISPATCH_AS_IS);
enqueueDispatchEntryLocked(connection,eventEntry,inputTarget,
InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT);
enqueueDispatchEntryLocked(connection,eventEntry,inputTarget,
InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER);
//Iftheoutboundqueuewaspreviouslyempty,startthedispatchcyclegoing.
if(wasEmpty&&!connection->outboundQueue.isEmpty()){
startDispatchCycleLocked(currentTime,connection);
}
}
voidInputDispatcher::enqueueDispatchEntryLocked(
constsp<Connection>&connection,EventEntry*eventEntry,constInputTarget*inputTarget,
int32_tdispatchMode){
//Enqueuethedispatchentry.
connection->outboundQueue.enqueueAtTail(dispatchEntry);
}
在准备完成后就会调用startDispatchCycleLocked进行事件分发,startDispatchCycleLocked这个函数的主体是一个while循环,在循环体内会执行下面三个主要步骤:
调用connection的inputPublisher来发出事件
把事件从outboundQueue队列中移除
把事件加入waitQueue队列,当事件在处理完成后返回,就会从waitQueue中删除该事件
voidInputDispatcher::startDispatchCycleLocked(nsecs_tcurrentTime,
constsp<Connection>&connection){
while(connection->status==Connection::STATUS_NORMAL
&&!connection->outboundQueue.isEmpty()){
DispatchEntry*dispatchEntry=connection->outboundQueue.head;
switch(eventEntry->type){
caseEventEntry::TYPE_KEY:{
KeyEntry*keyEntry=static_cast<KeyEntry*>(eventEntry);
//Publishthekeyevent.
status=connection->inputPublisher.publishKeyEvent(dispatchEntry->seq,
keyEntry->deviceId,keyEntry->source,
dispatchEntry->resolvedAction,dispatchEntry->resolvedFlags,
keyEntry->keyCode,keyEntry->scanCode,
keyEntry->metaState,keyEntry->repeatCount,keyEntry->downTime,
keyEntry->eventTime);
}
}
//Re-enqueuetheeventonthewaitqueue.
connection->outboundQueue.dequeue(dispatchEntry);
traceOutboundQueueLengthLocked(connection);
connection->waitQueue.enqueueAtTail(dispatchEntry);
traceWaitQueueLengthLocked(connection);
}
}
我们来看一下inputPublisher的publishKeyEvent的实现,最后也是调用socket的send接口来实现。
status_tInputPublisher::publishKeyEvent(
uint32_tseq,
int32_tdeviceId,
int32_tsource,
int32_taction,
int32_tflags,
int32_tkeyCode,
int32_tscanCode,
int32_tmetaState,
int32_trepeatCount,
nsecs_tdownTime,
nsecs_teventTime){
InputMessagemsg;
msg.header.type=InputMessage::TYPE_KEY;
msg.body.key.seq=seq;
msg.body.key.deviceId=deviceId;
msg.body.key.source=source;
msg.body.key.action=action;
msg.body.key.flags=flags;
msg.body.key.keyCode=keyCode;
msg.body.key.scanCode=scanCode;
msg.body.key.metaState=metaState;
msg.body.key.repeatCount=repeatCount;
msg.body.key.downTime=downTime;
msg.body.key.eventTime=eventTime;
returnmChannel->sendMessage(&msg);
}
status_tInputChannel::sendMessage(constInputMessage*msg){
do{
nWrite=::send(mFd,msg,msgLength,MSG_DONTWAIT|MSG_NOSIGNAL);
}while(nWrite==-1&&errno==EINTR);
}
总体的流程如下
下面在细分一下输入事件在Android系统中的流程:
从图上能看到,输入事件有四个处理的地方:
InputReaderThread
InputDispatcherThread
WindowInputEventReceiver
handleReceiverCallback
上面四个地方按功能来划分,其中:
InputReaderThread负责从输入设备中获取事件,事件加入inboundQueue队列。
InputDispatcherThread负责把inboundQueue中的事件信息取出,并且从系统中获取该事件所需要分发到的目标(窗口),把事件与目标分别整合成分发项,把分发项加入outboundQueue。另外,这里还是事件的分发端,负责把outboundQueue中的事件取出,通过InputChannel进行分发。分发完成后把该事件入waitQueue。
WindowInputEventReceiver是事件的接收端。事件会在这里被onTouch这类回调函数处理
handleReceiveCallback用于接收处理过后的反馈信息,事件在WindowInputEventReceiver端被处理成功或者失败,将会通过InputChannel返回Handled或者UNHandled消息。handleReceiveCallback接收到消息后将会对waitQueue中的事件进行出队列处理。
InputManager
InputManager用于启动InputReaderThread与InputDispatcherThread,会在system_server初始化的时候被创建并且调用InputManager的start方法启动这两个线程。InputManager的构造函数如下:
可以看到构造了InputDispatcher与InputReader两个类,这两个类是功能类,分别为InputDispatcherThread与InputReaderThread提供功能。另外,在构建InputReader的时候,把mDispatcher传递了进去,用于构建QueueInputListener。在这里可以提前说明一下这个成员的作用:把输入事件添加到inboundQueue。
构造函数最后调用了initialize,构建InputReaderThread、InputDispatcherThread。
InputManager的start用于启动InputReaderThread与InputDispatcherThread这两个线程。
InputReaderThread
InputReaderThread是用来从输入设备中读取输入事件的,首先看一下该线程的threadLoop函数mReader即在构建InputReaderThread时传进来的InputReader,负责实现读取输入事件所需要的各种功能。InputReader::loopOnce用于读取一次输入事件。其中,读取一次包含三个主要动作:
获取输入事件
处理输入事件
输入数据flush
1.获取输入事件getEvents
几乎所有与输入有关的事件都会从这里获得。其中包含:EPOLL_ID_INORIFY.输入设备打开或者删除的事件
EPOLL_ID_WAKE.管道发送过来的模拟事件
EPOLL_IN.按键,触摸这类实际操作事件
EPOLL_ID_INOTIFY,用于监控某个目录(子目录)下是否有新增或者删除文件,在这里用于监视/dev/input,这个是输入设备文件所在的目录,如果有新增设备,则会在该目录内创建新文件;如果删除设备,则该目录的相应文件会被删除。
EPOLL_ID_WAKE,EventHub有维护一个pipe,当pipe的写入端按照适当格式写入时间后,getEvents可以通过pipe的读取端获取这个虚拟事件
EPOLL_IN,用于监控设备文件的输入状态,当我们按键或者触摸设备时,我们就能获得EPOLL_IN状态,从而到该设备读取输入事件
监听事件用的是epoll_wait,由于epoll_wait一次能获取的事件可能会有多个,所以一次的getEvents需要对所获得的每个事件都进行上述代码的打包操作,最后返回事件数组。
2.处理输入事件processEventsLocked
由getEvents获得的事件数组会在这个函数内进行处理,其中事件数组中的事件大致可以分为两类,在这个函数将他们分开处理按键、触摸事件
设备增加、删除事件
在处理按键、触摸事件时,会根据他们设备的类型调用不同的process函数进行处理。对于触摸事件,基本上只是进行赋值,而按键事件则需要通过映射,把从设备文件读取进来的值转换成Android上层能统一处理的按键事件。
上面的mapKey对按键进行了映射处理,processKey用于区分按键的按下或者松开。在processKey的最后,会把事件打包成NotifyKeyArgs,然后通过QueueInputListener把事件push进mArgQueue。由于这里是一个事件数组,所以mArgQueue是必须的。
3.输入数据flush
在事件数组都push进mArgQueue之后,就需要把mArgQueue队列给推送出去进行下一步的操作,mQueuedListener->flush();就是负责进行队列的推送。还记得我们最开始说的”在构建InputReader的时候,把mDispatcher传递了进去,用于构建QueueInputListener”,我们这里的flush最终就是调用了InputDispatcher的notifyKey以notifyKey为例,其目的实际上是把事件队列加入mInboundQueue,但是在入mInboundQueue队列之前,调用了interceptKeyBeforeQueueing,该函数通过jni,调用到PhoneWindowManager的interceptKeyBeforeQueueing。而在入了mInboundQueue队列后,就会调用wake函数去唤醒InputDispatcherThread。下一步就是InputDispatcherThread的工作了。
InputDispatcherThread
InputDispatcherThread是用来进行事件分发的线程。内部也是调用InputDispatcher来实现所需要的功能。每次分发,调用的都是dispatchOnce,其内部调用dispatchOnceInnerLocked进行分发后,线程会调用pollOnce进入睡眠,等待下次InputReaderThread的wake操作
分发的过程可以大概分成以下几个步骤:
从mInboundQueue的队列头取出事件
特殊事件的处理,如POLICY_FLAG_PASS_TO_USER这类事件能直接发送到用户,类似于电量不足的这类事件:当电量低于20%时,直接往上层发送事件,而不用知道当前是在哪个Activity
一般事件的处理,进行分发
分发事件,肯定需要知道事件要分发到哪里,即分发的目标窗口,不过目标窗口可能不止一个。
由于可能存在多个目标窗口,所以需要对每个目标窗口都进行事件分发
在分发前的准备,就是把事件入outboundQueue队列,不过请注意,这里的队列不同于inboundQueue,因为outboundQueue是窗口相关的,窗口跟InputDispatcherThread间建立起一个连接(connection),该outboundQueue就是connection的成员。
在准备完成后就会调用startDispatchCycleLocked进行事件分发,startDispatchCycleLocked这个函数的主体是一个while循环,在循环体内会执行下面三个主要步骤:
调用connection的inputPublisher来发出事件
把事件从outboundQueue队列中移除
把事件加入waitQueue队列,当事件在处理完成后返回,就会从waitQueue中删除该事件
我们来看一下inputPublisher的publishKeyEvent的实现,最后也是调用socket的send接口来实现。
总体的流程如下
相关文章推荐
- Android自定义EditText样式
- android 动态创建控件并设置布局
- Android 利用addView 动态给Activity添加View组件
- android学习笔记
- Android原生Launcher的简要介绍及学习计划
- android:layout_weight 权重
- 第 2 章 Android 与 MVC 设计模式
- 介绍几个好用的android自定义控件
- android dp 和px的区别
- android:使用SurfaceView游戏开发简单屏幕适配方法
- android:给控件加边框
- Android动画精讲一:从setTranslationX谈属性动画和view动画的区别
- 解决Android文档打开缓慢问题
- Android屏幕自适应解决办法
- Android应用界面组件
- Android开发之sdk更新问题Failed to fetch URL
- android给View设置上下左右边框
- android给View设置上下左右边框
- Android Studio使用教程
- android进阶之GridView