Android(安卓)如何获取keyboard和TP消息 分享
分析一下Android是如何读取按键及TouchPanel的驱动的。主要在
$(ANDROID_DIR)/frameworks/base/libs/ui/EventHub.cpp 这个文件中,这是在HAL层,将一步步分析Android上层是如何接受事件的。 一,先看一下AndroidHAL ClassEventHub在$(ANDROID_DIR)/frameworks/base/include/ui/eventhub.h定义. i.scan_dir(constchar*dirname)//dirname="/dev/input" 扫描dirname目录,该目录下有event0,event1...,等设备. ii.open_device(devname); 打开/dev/input/event0,/dev/input/event1等设备. 这里以打开/dev/input/event0设备为例,分析按键的底层处理. for(attempt=0;attempt<10;attempt++){fd=open(deviceName,O_RDWR);
if(fd>=0)break;
usleep(100);
} 首先会打开传进来的设备.然后会获取version,id等信息. if(ioctl(fd,EVIOCGNAME(sizeof(name)-1),&name)<1){
//fprintf(stderr,"couldnotgetdevicenamefor%s,%s/n",deviceName,strerror(errno));
name[0]='/0'
}
获取drivername,在这里也就是/dev/input/evevnt0,也就是要到Driver里面去读取. 这个名字很重要,之后要与keyboardmap相匹配. 这里返回的值是:name="wayland_m_ebook_key_input" 为什么会返回这个值?请看event0的linuxdriver. wayland_m_ebook_keypad_probe()函数中,有以下语句: gpio_key_input->name="wayland_m_ebook_key_input". 所以这个值是在这个时候设置的。 intdevid=0;
while(devid<mNumDevicesById){
if(mDevicesById[devid].device==NULL){
break;
}
devid++;
}
if(devid>=mNumDevicesById){
device_ent*new_devids=(device_ent*)realloc(mDevicesById,
sizeof(mDevicesById[0])*(devid+1));
if(new_devids==NULL){
LOGE("outofmemory");
return-1;
}
mDevicesById=new_devids;
mNumDevicesById=devid+1;
mDevicesById[devid].device=NULL;
mDevicesById[devid].seq=0;
} 分配newdevice,将device信息保存至mDeviceById[]数组中. mNumDevicesById:device的数量 mDevicesById:devive的信息 new_mFDs=(pollfd*)realloc(mFDs,sizeof(mFDs[0])*(mFDCount+1));
new_devices=(device_t**)realloc(mDevices,sizeof(mDevices[0])*(mFDCount+1));
为new_mFDs,mFDs分配空间,以备之后保存每个event(x)的fd. mFDs[mFDCount].fd=fd;
mFDs[mFDCount].events=POLLIN;
将fd放到mFDs数组中. //Seeifthisisakeyboard,andclassifyit.
uint8_tkey_bitmask[(KEY_MAX+1)/8];
memset(key_bitmask,0,sizeof(key_bitmask));
LOGV("Gettingkeys...");
if(ioctl(fd,EVIOCGBIT(EV_KEY,sizeof(key_bitmask)),key_bitmask)>=0){
//LOGI("MAP/n");
//for(inti=0;i<((KEY_MAX+1)/8);i++){
//LOGI("%d:0x%02x/n",i,key_bitmask[i]);
//}
for(inti=0;i<((BTN_MISC+7)/8);i++){
if(key_bitmask[i]!=0){
device->classes|=CLASS_KEYBOARD;
break;
}
}
if((device->classes&CLASS_KEYBOARD)!=0){
device->keyBitmask=newuint8_t[sizeof(key_bitmask)];
if(device->keyBitmask!=NULL){
memcpy(device->keyBitmask,key_bitmask,sizeof(key_bitmask));
}else{
deletedevice;
LOGE("outofmemoryallocatingkeybitmask");
return-1;
}
}
}
如果是keyboard的设备. if(test_bit(BTN_TOUCH,key_bitmask)){
uint8_tabs_bitmask[(ABS_MAX+1)/8];
memset(abs_bitmask,0,sizeof(abs_bitmask));
LOGV("Gettingabsolutecontrollers...");
if(ioctl(fd,EVIOCGBIT(EV_ABS,sizeof(abs_bitmask)),abs_bitmask)>=0)
{
if(test_bit(ABS_X,abs_bitmask)&&test_bit(ABS_Y,abs_bitmask)){
device->classes|=CLASS_TOUCHSCREEN;
}
} } 继续分析Android是如何进行keyboard映射的: chartmpfn[sizeof(name)];
charkeylayoutFilename[300]; //amoredescriptivename
device->name=name; //replaceallthespaceswithunderscores
strcpy(tmpfn,name);
for(char*p=strchr(tmpfn,'');p&&*p;p=strchr(tmpfn,''))
*p='_' //findthe.klfileweneedforthisdevice
constchar*root=getenv("ANDROID_ROOT");
snprintf(keylayoutFilename,sizeof(keylayoutFilename),
"%s/usr/keylayout/%s.kl",root,tmpfn);
booldefaultKeymap=false;
if(access(keylayoutFilename,R_OK)){
snprintf(keylayoutFilename,sizeof(keylayoutFilename),
"%s/usr/keylayout/%s",root,"qwerty.kl");
defaultKeymap=true;
} ANDROID_ROOT一般都设置为/system tmpfn也就是name中的内容。而name=wayland_m_ebook_key_input 所以keylayoutFilename=/system/usr/keylayout/wayland_m_ebook_key_input.kl. 这个文件保存有按键的信息。可以在这个文件中修改按键的键值。 如果没有这个文件则会去读define的qwerty.kl. device->layoutMap->load(keylayoutFilename); load/system/usr/keylayout/wayland_m_ebook_key_input.kl这个文件进行分析。 KeyLayoutMap::load在KeyLayoutMap.cpp中实现。 把按键的映射关系保存在:KeyedVector<int32_t,Key>m_keys;中。 iii.EventHub::getEvent() 主要通过read()函数读取按键事件及进行Map键值映射。
二、再来看看jni层 com_android_server_KeyInputQueue.cpp 看看其中几行的代码: staticJNINativeMethodgInputMethods[]={
/*name,signature,funcPtr*/
{"readEvent","(Landroid/view/RawInputEvent;)Z",
(void*)android_server_KeyInputQueue_readEvent}, 可以看出,上层(即framework层)调用的接口是readEvent,实现函数是本文件的android_server_KeyInputQueue_readEvent(). 这个函数调用了getEvent读取事件。也就是EventHub.cpp中的EventHub::getEvent(). readEvent调用hub->getEvent读了取事件,然后转换成JAVA的结构。 三、再看看jni层对应的java层(经常封装成.jar档案) KeyInputQueue.java 在 frameworks/base/services/java/com/android/server/KeyInputQueue.java里创建了 一个InputDeviceReader线程,它循环的读取事件,然后把事件放入事件队列里,包括key/touchpanel.
四、事件分发
输入事件分发线程在frameworks/base/services/java/com/android/server/WindowManagerService.java里创建了一个输入事件分发线程,它负责把事件分发到相应的窗口上去。
按键触摸屏流程分析:
WindowManagerService类的构造函数
WindowManagerService()
mQueue=newKeyQ();
因为WindowManagerService.java(frameworks/base/services/java/com/android/server)中有:
privateclassKeyQextendsKeyInputQueueimplementsKeyInputQueue.FilterCallback
KeyQ是抽象类KeyInputQueue的实现,所以newKeyQ类的时候实际上在KeyInputQueue类中创建了一个线程InputDeviceReader专门用来从设备读取按键事件.
代码:
publicvoid run(){
//在循环中调用: readEvent(ev);
...
send= preprocessEvent(di,ev);
//实际调用的是KeyQ类的preprocessEvent函数
...
intkeycode= rotateKeyCodeLocked(ev.keycode);
int[]map= mKeyRotationMap;
for(inti=0;i<N;i+=2 ){
if(map== keyCode)
returnmap[i+1 ];
}//
addLocked(di,curTime,ev.flags,RawInputEvent.CLASS_KEYBOARD,
newKeyEvent(di,di.mDownTime,curTime,down,keycode, 0 ,scancode,...));
QueuedEventev= obtainLocked(device,when,flags,classType,event);
}
};
readEvent()实际上调用的是com_android_server_KeyInputQueue.cpp(frameworks/base/services/jni)中的
staticjbooleanandroid_server_KeyInputQueue_readEvent(JNIEnv*env,jobjectclazz,jobjectevent)来读取事件,
boolres=hub-& gt;getEvent(&deviceId,&type,&scancode,&keycode,&flags,&value,&when) 调用的是EventHub.cpp(frameworks/base/libs/ui)中的:
boolEventHub::getEvent(int32_t*outDeviceId,int32_t*outType,
int32_t*outScancode,int32_t*outKeycode,uint32_t*outFlags,
int32_t*outValue,nsecs_t*outWhen)
在函数中调用了读设备操作:res=read(mFDs.fd,&iev,sizeof(iev));
在构造函数WindowManagerService()调用newKeyQ()以后接着调用了:
mInputThread=newInputDispatcherThread();
...
mInputThread.start();
来启动一个线程InputDispatcherThread
run()
process();
QueuedEventev=mQueue.getEvent(...)
因为WindowManagerService类中:finalKeyQmQueue;
所以实际上InputDispatcherThread线程实际上从KeyQ的事件队列中读取按键事件,在process()方法中进行处理事件。
switch(ev.classType)
caseRawInputEvent.CLASS_KEYBOARD:
...
dispatchKey((KeyEvent)ev.event,0,0);
mQueue.recycleEvent(ev);
break;
caseRawInputEvent.CLASS_TOUCHSCREEN:
//Log.i(TAG,"Readnextevent"+ev);
dispatchPointer(ev,(MotionEvent)ev.event,0,0);
break;
caseRawInputEvent.CLASS_TRACKBALL:
dispatchTrackball(ev,(MotionEvent)ev.event,0,0);
break;
===============================================================
补充一些内容:
在写程序时,需要捕获KEYCODE_HOME、KEYCODE_ENDCALL、KEYCODE_POWER这几个按键,但是这几个按键系统做了特殊处理,
在进行dispatch之前做了一些操作,HOME除了Keygaurd之外,不分发给任何其他APP,ENDCALL和POWER也类似,所以需要我们系统
处理之前进行处理。
我的做法是自己定义一个FLAG,在自己的程序中添加此FLAG,然后在WindowManagerServices.java中获取当前窗口的FLAG属性,如果是我
们自己设置的那个FLAG,则不进行特殊处理,直接分发按键消息到我们的APP当中,由APP自己处理。
这部分代码最好添加在
@Override
booleanpreprocessEvent(InputDevicedevice,RawInputEventevent)
方法中,这个方法是KeyInputQueue中的一个虚函数,在处理按键事件之前的一个“预处理”。
PS:对HOME键的处理好像必需要修改PhoneWindowManager.java中的interceptKeyTi方法,具体可以参考对KeyGuard程序的处理。
更多相关文章
- Unity调用Android配置方法
- Android(安卓)Power Management
- Android应用程序启动过程源代码分析
- 利用HTML5开发Android
- unity与Android相互调用
- android WebView总结
- Android截屏浅析
- Android属性之build.prop,及property_get/property_set && Androi
- Linux/Android——input系统之 kernel层 与 frameworks层交互 (