Android输入驱动分析


Init-----------zygote---------system-server-------------------windosmanager ------------------------------------------------------------UEventObserver 
------------------------------------------------------------InputDeviceRead 
-------------------------------------------------------------InputDispatcher 
-------------------------------------------------------------DisplayEventThr 
-------------------------------------------------------------ActivityManager 

EventHub: 
而事件的传入是从EventHub开始的,EventHub是事件的抽象结构,维护着系统设备的运行情况,设备类型包括KeyboardTouchScreenTraceBall。它在系统启动的时候会通过open_device方法将系统提供的输入设备都增加到这个抽象结构中,并维护一个所有输入设备的文件描述符,如果输入设备是键盘的话还会读取/system/usr/keylayout/目录下对应键盘设备的映射文件,另外getEvent方法是对EventHub中的设备文件描述符使用poll操作等侍驱动层事件的发生,如果发生的事件是键盘事件,则调用Map函数按照映射文件转换成相应的键值并将扫描码和键码返回给KeyInputQueue。 
KeyLayoutMap主要是读取键盘映射文件并将键盘扫描码和键码进行转换 

frameworks\base\core\jni\server\com_android_server_KeyInputQueue.cpp 
EventHubKeyinputQueueJNI接口层 


KeyinputQueue: 
在线程InputDeviceReader中会根据事件的类型以及事件值进行判断处理,从而确定这个事件对应的设备状态是否发生了改变并相应的改变对这个设备的描述结构InputDevice。 
getEvent:在给定时间段时看是否有事件发生,如果有的话返回true否则false。 

Windowmanager: 
(frameworks/base/services/java/com/android/server/windowmanagerservice.java) 
进程Windowmanager会创建一个线程(InputDispatcherThread),在这个线程里从事件队列中读取发生的事件(QueuedEventev =mQueue.getEvent()),并根据读取到事件类型的不同分成三类(KEYBOARDTOUCHSCREENTRACKBALL),分别进行处理,例如键盘事件会调用dispatchKey((KeyEvent)ev.event,0, 0)以将事件通过Binder发送给具有焦点的窗口应用程序,然后调用mQueue.recycleEvent(ev)继续等侍键盘事件的发生;如果是触摸屏事件则调用dispatchPointer(ev,(MotionEvent)ev.event, 0,0),这里会根据事件的种类(UPDOWNMOVEOUT_SIDE等)进行判断并处理,比如Cancel或将事件发送到具有权限的指定的窗口中去

Android输入事件流程 

EventHub 

EventHub对输入设备进行了封装。输入设备驱动程序对用户空间应用程序提供一些设备文件,这些设备文件放在/dev/input里面。 

EventHub扫描/dev/input下所有设备文件,并打开它们。 

C代码 

  1. bool EventHub::openPlatformInput(void)  

  2. {  

  3. ...  

  4.     mFDCount = 1;  

  5.     mFDs = (pollfd *)calloc(1, sizeof(mFDs[0]));  

  6.     mDevices = (device_t **)calloc(1, sizeof(mDevices[0]));  

  7.     mFDs[0].events = POLLIN;  

  8.     mDevices[0] = NULL;  

  9.   

  10.     res = scan_dir(device_path);  

  11. ...  

  12.     return true;  

  13. }  



EventHub对外提供了一个函数用于从输入设备文件中读取数据。 

C代码 

  1. bool EventHub::getEvent(int32_t* outDeviceId, int32_t* outType,  

  2.         int32_t* outScancode, int32_t* outKeycode, uint32_t *outFlags,  

  3.         int32_t* outValue, nsecs_t* outWhen)  

  4. {  

  5.     ...  

  6.     while(1) {  

  7.   

  8.         // First, report any devices that had last been added/removed.  

  9.         if (mClosingDevices != NULL) {  

  10.             device_t* device = mClosingDevices;  

  11.             LOGV("Reporting device closed: id=0x%x, name=%s\n",  

  12.                  device->id, device->path.string());  

  13.             mClosingDevices = device->next;  

  14.             *outDeviceId = device->id;  

  15.             if (*outDeviceId == mFirstKeyboardId) *outDeviceId = 0;  

  16.             *outType = DEVICE_REMOVED;  

  17.             delete device;  

  18.             return true;  

  19.         }  

  20.         if (mOpeningDevices != NULL) {  

  21.             device_t* device = mOpeningDevices;  

  22.             LOGV("Reporting device opened: id=0x%x, name=%s\n",  

  23.                  device->id, device->path.string());  

  24.             mOpeningDevices = device->next;  

  25.             *outDeviceId = device->id;  

  26.             if (*outDeviceId == mFirstKeyboardId) *outDeviceId = 0;  

  27.             *outType = DEVICE_ADDED;  

  28.             return true;  

  29.         }  

  30.   

  31.         release_wake_lock(WAKE_LOCK_ID);  

  32.   

  33.         pollres = poll(mFDs, mFDCount, -1);  

  34.   

  35.         acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID);  

  36.   

  37.         if (pollres <= 0) {  

  38.             if (errno != EINTR) {  

  39.                 LOGW("select failed (errno=%d)\n", errno);  

  40.                 usleep(100000);  

  41.             }  

  42.             continue;  

  43.         }  

  44.   

  45.         for(i = 1; i < mFDCount; i++) {  

  46.             if(mFDs[i].revents) {  

  47.                 LOGV("revents for %d = 0x%08x", i, mFDs[i].revents);  

  48.                 if(mFDs[i].revents & POLLIN) {  

  49.                     res = read(mFDs[i].fd, &iev, sizeof(iev));  

  50.                     if (res == sizeof(iev)) {  

  51.                         LOGV("%s got: t0=%d, t1=%d, type=%d, code=%d, v=%d",  

  52.                              mDevices[i]->path.string(),  

  53.                              (int) iev.time.tv_sec, (int) iev.time.tv_usec,  

  54.                              iev.type, iev.code, iev.value);  

  55.                         *outDeviceId = mDevices[i]->id;  

  56.                         if (*outDeviceId == mFirstKeyboardId) *outDeviceId = 0;  

  57.                         *outType = iev.type;  

  58.                         *outScancode = iev.code;  

  59.                         if (iev.type == EV_KEY) {  

  60.                             err = mDevices[i]->layoutMap->map(iev.code, outKeycode, outFlags);  

  61.                             LOGV("iev.code=%d outKeycode=%d outFlags=0x%08x err=%d\n",  

  62.                                 iev.code, *outKeycode, *outFlags, err);  

  63.                             if (err != 0) {  

  64.                                 *outKeycode = 0;  

  65.                                 *outFlags = 0;  

  66.                             }  

  67.                         else {  

  68.                             *outKeycode = iev.code;  

  69.                         }  

  70.                         *outValue = iev.value;  

  71.                         *outWhen = s2ns(iev.time.tv_sec) + us2ns(iev.time.tv_usec);  

  72.                         return true;  

  73.                     else {  

  74.                         if (res<0) {  

  75.                             LOGW("could not get event (errno=%d)", errno);  

  76.                         else {  

  77.                             LOGE("could not get event (wrong size: %d)", res);  

  78.                         }  

  79.                         continue;  

  80.                     }  

  81.                 }  

  82.             }  

  83.         }  

  84.     ...  

  85. }  




对于按键事件,调用mDevices[i]->layoutMap->map进行映射。映射实际是由KeyLayoutMap::map完成的,KeyLayoutMap类里读取配置文件qwerty.kl,由配置文件qwerty.kl决定键值的映射关系。你可以通过修改./development/emulator/keymaps/qwerty.kl来改变键值的映射关系。 
JNI函数 

frameworks/base/services/jni/com_android_server_KeyInputQueue.cpp文件中,向JAVA提供了函数android_server_KeyInputQueue_readEvent,用于读取输入设备事件。 

C代码 

  1. static jboolean  

  2. android_server_KeyInputQueue_readEvent(JNIEnv* env, jobject clazz,  

  3.                                           jobject event)  

  4. {  

  5.     gLock.lock();  

  6.     sp hub = gHub;  

  7.     if (hub == NULL) {  

  8.         hub = new EventHub;  

  9.         gHub = hub;  

  10.     }  

  11.     gLock.unlock();  

  12.   

  13.     int32_t deviceId;  

  14.     int32_t type;  

  15.     int32_t scancode, keycode;  

  16.     uint32_t flags;  

  17.     int32_t value;  

  18.     nsecs_t when;  

  19.     bool res = hub->getEvent(&deviceId, &type, &scancode, &keycode,  

  20.             &flags, &value, &when);  

  21.   

  22.     env->SetIntField(event, gInputOffsets.mDeviceId, (jint)deviceId);  

  23.     env->SetIntField(event, gInputOffsets.mType, (jint)type);  

  24.     env->SetIntField(event, gInputOffsets.mScancode, (jint)scancode);  

  25.     env->SetIntField(event, gInputOffsets.mKeycode, (jint)keycode);  

  26.     env->SetIntField(event, gInputOffsets.mFlags, (jint)flags);  

  27.     env->SetIntField(event, gInputOffsets.mValue, value);  

  28.     env->SetLongField(event, gInputOffsets.mWhen,  

  29.                         (jlong)(nanoseconds_to_milliseconds(when)));  

  30.   

  31.     return res;  

  32. }  



readEvent调用hub->getEvent读了取事件,然后转换成JAVA的结构。 
事件中转线程 

frameworks/base/services/java/com/android/server/KeyInputQueue.java里创建了一个线程,它循环的读取事件,然后把事件放入事件队列里。 

Java代码 

  1. Thread mThread = new Thread("InputDeviceReader") {  

  2.         public void run() {  

  3.             android.os.Process.setThreadPriority(  

  4.                     android.os.Process.THREAD_PRIORITY_URGENT_DISPLAY);  

  5.   

  6.             try {  

  7.                 RawInputEvent ev = new RawInputEvent();  

  8.                 while (true) {  

  9.                     InputDevice di;  

  10.   

  11.                     readEvent(ev);  

  12.   

  13.                     send = preprocessEvent(di, ev);  

  14.                     addLocked(di, curTime, ev.flags, ..., me);  

  15.                 }  

  16.         }  

  17.     };  



输入事件分发线程 

frameworks/base/services/java/com/android/server/WindowManagerService.java里创建了一个输入事件分发线程,它负责把事件分发到相应的窗口上去。 

Java代码 

  1. mQueue.getEvent  

  2. dispatchKey/dispatchPointer/dispatchTrackball  





按键,触摸屏流程分析 

按键触摸屏流程分析
WindowManagerService类的构造函数 
WindowManagerService() 
 mQueue = new KeyQ(); 
因为WindowManagerService.java(frameworks\base\services\java\com\android\server)中有:    
privateclass KeyQ extends KeyInputQueue 
KeyQ是抽象类 KeyInputQueue的实现,所以newKeyQ类的时候实际上在KeyInputQueue类中创建了 
一个线程 InputDeviceReader专门用来从设备读取按键事件,代码: 
ThreadmThread = new Thread("InputDeviceReader") { 
 public void run() 
 { 
       在循环中调用:readEvent(ev); 
   ... 
   send = preprocessEvent(di, ev); 
       实际调用的是KeyQ类的preprocessEvent函数 
   ... 
   int keycode = rotateKeyCodeLocked(ev.keycode); 
     int[] map = mKeyRotationMap; 
     for (int i=0; i<N; i+=2) 
     { 
       if (map[i] == keyCode) 
         return map[i+1]; 
     } // 
   addLocked(di, curTime,ev.flags,RawInputEvent.CLASS_KEYBOARD,newKeyEvent(di, di.mDownTime,curTime, down,keycode, 0, scancode,...)); 
     QueuedEvent ev = obtainLocked(device, when, flags, classType,event); 
 } 


readEvent()实际上调用的是com_android_server_KeyInputQueue.cpp(frameworks\base\services\jni)中的: 
staticjboolean android_server_KeyInputQueue_readEvent(JNIEnv* env, jobjectclazz,jobject event) 
 bool res = hub->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[i].fd, &iev, sizeof(iev)); 


在构造函数WindowManagerService()调用newKeyQ()以后接着调用了: 
 mInputThread = new InputDispatcherThread();       
 ...     
 mInputThread.start(); 
来启动一个线程 InputDispatcherThread 
run() 
 process(); 
   QueuedEvent ev = mQueue.getEvent(...) 
因为WindowManagerService类中:finalKeyQ mQueue; 
所以实际上InputDispatcherThread线程实际上从 KeyQ的事件队列中读取按键事件。 
switch(ev.classType) 
 case RawInputEvent.CLASS_KEYBOARD: 
   ... 
   dispatchKey((KeyEvent)ev.event, 0, 0); 
   mQueue.recycleEvent(ev); 
   break; 
 case RawInputEvent.CLASS_TOUCHSCREEN: 
   //Log.i(TAG, "Read next event " + ev); 
   dispatchPointer(ev, (MotionEvent)ev.event, 0, 0); 
   break; 

=============================================================== 


KeyInputQueue.java(frameworks\base\services\java\com\android\server): 
的线程 ThreadmThread = new Thread("InputDeviceReader")本地调用: 
readEvent(ev);读取按键。readEvent调用的是文件: 
com_android_server_KeyInputQueue.cpp(frameworks\base\services\jni)中的函数: 
staticjboolean android_server_KeyInputQueue_readEvent(JNIEnv* env, jobjectclazz, 
                                         jobject event) 
android_server_KeyInputQueue_readEvent中有: 
hub= new EventHub; 
boolres = hub->getEvent(&deviceId, &type, &scancode,&keycode, 
           &flags, &value, &when); 

hub->getEvent调用的是 
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) 
读取按键。 

classRefBase::weakref_impl : publicRefBase::weakref_type 



在系统启动后,android会通过 
staticconst char *device_path = "/dev/input"; 
boolEventHub::openPlatformInput(void) 
 res = scan_dir(device_path); 


通过下面的函数打开设备。 
intEventHub::open_device(const char *deviceName) 

 ... 
 fd = open(deviceName, O_RDWR); 
 ... 
 mFDs[mFDCount].fd = fd; 
 mFDs[mFDCount].events = POLLIN; 
 ... 
 ioctl(mFDs[mFDCount].fd, EVIOCGNAME(sizeof(devname)-1), devname); 
 ... 
 const char* root = getenv("ANDROID_ROOT"); 
 snprintf(keylayoutFilename,sizeof(keylayoutFilename), 
                "%s/usr/keylayout/%s.kl", root, tmpfn); 
 ... 
 device->layoutMap->load(keylayoutFilename); 
 ... 

打开设备的时候,如果device->classes&CLASS_KEYBOARD不等于0表明是键盘。 
常用输入设备的定义有: 
enum{ 
       CLASS_KEYBOARD      = 0x00000001,//键盘 
       CLASS_ALPHAKEY      = 0x00000002,// 
       CLASS_TOUCHSCREEN   = 0x00000004, //触摸屏 
       CLASS_TRACKBALL     = 0x00000008  //轨迹球 
   }; 
打开键盘设备的时候通过上面的 ioctl获得设备名称,命令字EVIOCGNAME的定义在文件: 
kernel/include/linux/input.h中。 
#defineEVIOCGNAME(len)   _IOC(_IOC_READ, 'E', 0x06, len) /* getdevice name */ 
在内核键盘驱动文件drivers/input/keyboard/pxa27x_keypad.c中定义了设备名称:pxa27x-keypad 
staticstruct platform_driver pxa27x_keypad_driver = { 
   .probe        =pxa27x_keypad_probe, 
   .remove        =__devexit_p(pxa27x_keypad_remove), 
   .suspend    = pxa27x_keypad_suspend, 
   .resume        =pxa27x_keypad_resume, 
   .driver        = { 
       .name    = "pxa27x-keypad", 
       .owner    = THIS_MODULE, 
   }, 
}; 
ANDROID_ROOT为环境变量,在android的命令模式下通过printenv可以知道它为:system 
所以keylayoutFilename为:/system/usr/keylayout/pxa27x-keypad.kl 
pxa27x-keypad.kl定义了按键映射,具体内容如下: 
---------------------- 
#NUMERIC KEYS 3x4 
key2   1 
key3   2 
key4   3 
key5   4 
key6   5 
key7   6 
key8   7 
key9   8 
key10  9 
key11  0 
key83  POUND 
key55  STAR 

#FUNCTIONAL KEYS 
key231  MENU       WAKE_DROPPED 
key192  BACK          WAKE_DROPPED 
key193  HOME       WAKE 
key107  DEL        WAKE 
key102  CALL       WAKE_DROPPED 
key158  ENDCALL     WAKE_DROPPED 
key28   DPAD_CENTER     WAKE 
key115  VOLUME_UP 
key114  VOLUME_DOWN 
---------------------- 
如果没有定义键盘映射文件,那么默认使用系统的/system/usr/keylayout/qwerty.kl 
可以修改/system/usr/keylayout/qwerty.kl文件改变Android公司的按键映射。 

device->layoutMap->load(keylayoutFilename)调用的是文件: 
KeyLayoutMap.cpp(frameworks\base\libs\ui)中的函数: 
status_tKeyLayoutMap::load(const char* filename)通过解析pxa27x-keypad.kl 
把按键的映射关系保存在:KeyedVector<int32_t,Key>m_keys;中。 
当获得按键事件以后调用: 
status_tKeyLayoutMap::map(int32_t scancode, int32_t *keycode, uint32_t*flags) 
由映射关系KeyedVector<int32_t,Key>m_keys把扫描码转换成andorid上层可以识别的按键。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值