Android 4.0 input touch解析(一)


前言

在網上看到好多關於android input device流程分析,但是都不全,有的只是從linux內核那邊分析,有的從android上層分析,而且分析的代碼也比較老,都是在android2.3以下,最近在做android4.0下的多點觸摸以及校准程序,多點觸摸的驅動很好寫,在linux內核里面都有現成的例子,照着改就可以了。但是android下的校准程序比較復雜,一種是在android Framework層進行,一種是在linux 內核層進行。

對於校准程序來說,需要全屏校准。但是在android4.0下面,下面的導航欄是system ui畫的,無法去掉,因此在校准程序里面通過display際的小得到分辨率高度比實,差的那部分就是導航欄的高度。如果以小的高度進行校准,但使用實際的高度進行觸摸坐標到屏幕坐標轉換,就會導致觸摸點偏下的問題。

為了解決這個問題,在網上找了很多資料,第一種就是想辦法在校准程序里面得到整個屏幕的分辨率,進而讓校准程序全屏顯示,即把導航欄隱藏,在網上看到又網友用下面例子實現:

1 //for phone
2 getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
3 //for pad View.SYSTEM_UI_FLAG_SHOW_FULLSCREEN= 4    
4 getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_SHOW_FULLSCREEN);

  經過自己實驗,這兩個都無法隱藏下面的導航欄,而且在最新的sdk里面也沒有 SYSTEM_UI_FLAG_SHOW_FULLSCREEN 的定義。第二種就是在jni種通過fb0得到系統的分辨率,這個是真實的分辨率,這種方法需要apkroot或者graphics組權限,才能打開fb0,而且android4.0根據觸摸屏類型是否使用外部顯示分辨率,如果使用外部display的話,那么就不能用fb0的分辨率。為了解決這個問題,把整個input touch流程都看了一邊。廢話少說,進入正題。

 

1、Android input touch 流程

 

  Android inout touch流程分兩部分,一部分是從android framework開始,如何讀取touch設備的事件並分發。一部分是從linux 內核開始,如何從觸摸屏讀取觸摸坐標並送給touch設備。

2、Android framework 層

2.1、文件結構

  首先看看Event Input文件結構吧,在frameworks/base/services/input之下

 

 

2.2、模塊介紹

  • EventHub

  它是系統中所有事件的中央處理站。它管理所有系統中可以識別的輸入設備的輸入事件,此外,當設備增加或刪除時,EventHub將產生相應的輸入事件給系統。EventHub通過getEvents函數,給系統提供一個輸入事件流。它也支持查詢輸入設備當前的狀態(如哪些鍵當前被按下)。而且EventHub還跟蹤每個輸入調入的能力,比如輸入設備的類別,輸入設備支持哪些按鍵。

  • InputReader

  InputReaderEventHub中讀取原始事件數據(RawEvent),並由各個InputMapper處理之后輸入對應的input listener.InputReader擁有一個InputMapper集合。它做的大部分工作在InputReader線程中完成,但是InputReader可以接受任意線程的查詢。為了可管理性,InputReader使用一個簡單的Mutex來保護它的狀態。InputReader擁有一個EventHub對象,但這個對象不是它創建的,而是在創建InputReader時作為參數傳入的。

  • InputDispatcher

  InputDispatcher負責把事件分發給輸入目標,其中的一些功能(如識別輸入目標)由獨立的policy對象控制。

  • InputManager

  InputManager是系統事件處理的核心,它雖然不做具體的事,但管理工作還是要做的,比如接受我們客戶的投訴和索賠要求,或者老板的出氣筒。

  InputManager使用兩個線程:
    1
InputReaderThread叫做"InputReader"線程,它負責讀取並預處理RawEventapplies policy並且把消息送入DispatcherThead管理的隊列中。
    
2InputDispatcherThread叫做"InputDispatcher"線程,它在隊列上等待新的輸入事件,並且異步地把這些事件分發給應用程序。

   InputReaderThread類與InputDispatcherThread類不共享內部狀態,所有的通信都是單向的,從InputReaderThreadInputDispatcherThread。兩個類可以通過InputDispatchPolicy進行交互。

   InputManager類從不與Java交互,而InputDispatchPolicy負責執行所有與系統的外部交互,包括調用DVM業務。

  看看下圖理解input下面幾個模塊的關系

 

2.3、線程創建

  SystemServer大家熟悉吧,它是android init進程啟動的,它的任務就是啟動android里面很多服務,並管理起來,如果大家不熟悉,請參考andorid啟動流程分析

  SystemServer.java (frameworks\base\services\java\com\android\server)里面ServerThread::run調用

1 Slog.i(TAG, "Window Manager");
2 wm = WindowManagerService.main(context, power,
3             factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL,
4             !firstBoot);
5 ServiceManager.addService(Context.WINDOW_SERVICE, wm);

  WindowManagerService.java (frameworks\base\services\java\com\android\server\wm) 里面 WindowManagerService main調用

1 WMThread thr = new WMThread(context, pm, haveInputMethods, allowBootMsgs);
2 thr.start();

   接着調用WMThread:: run調用

1 WindowManagerService s = new WindowManagerService(mContext, mPM,
2         mHaveInputMethods, mAllowBootMessages);

   接着在WindowManagerService里面調用

1 mInputManager = new InputManager(context, this);

   至此我們創建了一個javainput設備管理器。

   InputManager.java (frameworks\base\services\java\com\android\server\wm) 里面InputManager調用

1 nativeInit(mContext, mCallbacks, looper.getQueue());

   從下面開始就進入native空間

   com_android_server_InputManager.cpp (frameworks\base\services\jni)里面nativeInit對應android_server_InputManager_nativeInit調用

1 gNativeInputManager = new NativeInputManager(contextObj, callbacksObj, looper);

   在NativeInputManager里面調用

1 sp<EventHub> eventHub = new EventHub();
2 mInputManager = new InputManager(eventHub, this, this);

  這個函數創建一個EventHub對象,然后把它作為參數來創建InputManager對象。特別注意,InputManager是在C++里,具體在InputManager.cpp里。EventHub類在EventHub.cpp里,這個類和input事件獲取有關。

  至此我們創建了一個nativeinput設備管理器,具體作用見上面說明。

  首先是去InputManager.cpp (frameworks\base\services\input) 文件里面InputManager::InputManager調用

1 mDispatcher = new InputDispatcher(dispatcherPolicy);
2 mReader = new InputReader(eventHub, readerPolicy, mDispatcher);
3 initialize();

   它創建了InputDispatcher對象,同時也創建了InputReader對象。並分別暫存於mDispatchermReader變量中。注意eventHubmDispatcher都作為參數創建InputReader對象。后面還用initialize來初始化。下面是initialize函數的定義:

1 void InputManager::initialize() {
2     mReaderThread = new InputReaderThread(mReader);
3     mDispatcherThread = new InputDispatcherThread(mDispatcher);
4 }

  它創建兩個線程對象,一個是InputReaderThread線程對象,負責input事件的獲取;另一個是InputDispatcherThread線程對象,負責input消息的發送。

  (注:以上兩個線程對象都有自己的threadLoop函數,它將在Thread::_threadLoop中被調用,這個Thread::_threadLoop是線程入口函數,線程在Thread::run中被真正地創建

  InputDispatcher.cpp (frameworks\base\services\input) 里面InputDispatcher::InputDispatcher做一些准備工作。

  InputReader.cpp (frameworks\base\services\input)里面InputReader::InputReader做一些准備工作。

 

2.4、線程啟動

在上面講到在WindowManagerService里面調用

1 mInputManager = new InputManager(context, this);

創建input 管理器緊接着調用

1 mInputManager.start();

InputManager.java (frameworks\base\services\java\com\android\server\wm) 里面start調用

1 Slog.i(TAG, "Starting input manager");
2 nativeStart();

  從下面開始就進入native空間

   com_android_server_InputManager.cpp (frameworks\base\services\jni)里面nativeStart對應android_server_InputManager_nativeStart調用

1 status_t result = gNativeInputManager->getInputManager()->start();

   InputManager.cpp (frameworks\base\services\input) 文件里面InputManager::start調用

1 status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);
2 result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);

   上面兩個線程對象是Thread子類,於是繼承它的run方法,Thread::run中,調用createThreadEtc函數,並以Thread::_threadLoop作為入口函數,以上面的mDispatcherThreadmReaderThread作為userdata創建線程,然后會調用threadLoop(),在Thread類中它是虛函數,得由子類來復寫。

  因此會調用InputReader.cpp (frameworks\base\services\input)里面的threadLoopInputReaderThread::threadLoop調用

1 mReader->loopOnce();

  mReader就是上面創建的inputreader對象,作為參數傳給mReaderThread

  InputReader::loopOnce調用

1 count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);

 得到input 輸入事件processEventsLocked處理input輸入事件

 因此會調用InputDispatcher.cpp (frameworks\base\services\input)里面的threadLoopInputDispatcherThread::threadLoop調用

1 mDispatcher->dispatchOnce ();

  

  mDispatcher就是上面創建的InputDispatcher對象,作為參數傳給mDispatcherThread。

  InputDispatcher::dispatchOnce調用dispatchOnceInnerLocked(&nextWakeupTime)

  dispatchOnceInnerLocked函數處理input輸入消息,mLooper->pollOnce是等待下一次輸入事件。

1 mLooper->pollOnce(timeoutMillis):

   這個請看Looper.cpp文件中的Looper::pollOnce()函數。Looper里主要通過linux管道方式實現進程間通信,通過epoll機制實現外界事件請求作出響應。

  至此整個android input event框架已經運轉起來了,好像到現在還沒有提到touch,別着急,且看下面的分析。

2.5、event初始化

還記得android_server_InputManager_nativeInit里面創建sp<EventHub> eventHub = new EventHub();

EventHub.cpp (frameworks\base\services\input) 里面

 1 EventHub::EventHub(void) :
 2         mBuiltInKeyboardId(-1), 
 3         mNextDeviceId(1),
 4         mOpeningDevices(0), //表示需要打開的設備鏈表,為NULL
 5         mClosingDevices(0), //表示需要關閉的設備鏈表,為NULL
 6         mNeedToSendFinishedDeviceScan(false), //表示需要發送設備掃描完成,默認為0
 7         mNeedToReopenDevices(false),  //表示需要重新打開設備,默認為0
 8         mNeedToScanDevices(true), //表示需要掃描設備,默認為1
 9         mNeedToSendHeadPhoneEvent(false), 
10         mNeedToSendMicroPhoneEvent(false), 
11         mHeadsetDeviceId(-1),
12         mPendingEventCount(0), //表示需要處理event個數,默認為0
13         mPendingEventIndex(0),  //表示當前需要處理event的索引,默認為0
14         mPendingINotify(false) { //表示需要處理的通知,默認為0
15     acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID);
16 
17     mNumCpus = sysconf(_SC_NPROCESSORS_ONLN);
18 
19     mEpollFd = epoll_create(EPOLL_SIZE_HINT); //epoll實例,在EventHub::EventHub中初始化此例,所有輸入事件通過epoll_wait來獲取
20     //創建mINotifyFd,用於監控/dev/input目錄下刪除和創建設備節點的事件
21     mINotifyFd = inotify_init();
22     int result = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);
23 
24     struct epoll_event eventItem;
25     memset(&eventItem, 0, sizeof(eventItem));
26     eventItem.events = EPOLLIN;
27     eventItem.data.u32 = EPOLL_ID_INOTIFY;
28     result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem); //將mINotifyFd注冊到mEpollFd里面,通過epoll來監聽mINotifyFd的變化
29     // 創建喚醒管道,並設置為非阻塞,如果向mWakeWritePipeFd寫,那么mWakeReadPipeFd就會有變化
30     int wakeFds[2];
31     result = pipe(wakeFds);
32     mWakeReadPipeFd = wakeFds[0];
33     mWakeWritePipeFd = wakeFds[1];
34     result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);
35     result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);
36     //將mWakeReadPipeFd注冊到mEpollFd里面,通過epoll來監聽mWakeReadPipeFd的變化
37     eventItem.data.u32 = EPOLL_ID_WAKE;
38     result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &eventItem);
39 }
40          

至此EventHub對象以及構造完成了,mEpollFd監聽mINotifyFdmWakeReadPipeFd的變化。

 2.6、讀取事件

在上面2.4節最后我們看到InputReaderThread線程里面會循環調用InputReader::loopOnce 接着調用

1 count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);

這里的mEventHub就是上節實例化的eventhub,我們來看getEvents

EventHub.cpp (frameworks\base\services\input) 里面EventHub::getEvents

 1   for (;;) {
 2         nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
 3         // Reopen input devices if needed.
 4         // 檢查mNeedToReopenDevices是否為ture,如果為true,在closeAllDevicesLocked里面關閉所有打開的硬件設備描述符,並把需要刪除的設備放在
      mClosingDevices鏈表里面,如果這個設備是在mOpeningDevices里面,就忽略跳過,並刪除eventhub層的device對象。然后設置mNeedToScanDevices為true,
      因為mNeedToReopenDevices默認為false,所以不會執行這段代碼
5 if (mNeedToReopenDevices) { 6 mNeedToReopenDevices = false; 7 LOGI("Reopening all input devices due to a configuration change."); 8 closeAllDevicesLocked(); 9 mNeedToScanDevices = true; 10 break; // return to the caller before we actually rescan 11 } 12 13 // Report any devices that had last been added/removed. 14 // 檢查mClosingDevices鏈表是否存在,如果存在,循環把需要刪除的設備信息放在event里面,同時設置event type為DEVICE_REMOVED,
      並刪除eventhub層的device對象。設置mNeedToSendFinishedDeviceScan為true。每循環一次,capacity減1,capacity等於0,就退出for循環,
      表明這次getEvents已經取得256個event事件了,返回給inputreader處理。因為一開始mClosingDevices不存在,所以不會執行這段代碼,
      只有上面的closeAllDevicesLocked執行了,才會執行這段代碼。
15 while (mClosingDevices) { 16 Device* device = mClosingDevices; 17 LOGV("Reporting device closed: id=%d, name=%s\n", 18 device->id, device->path.string()); 19 mClosingDevices = device->next; 20 event->when = now; 21 event->deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id; 22 event->type = DEVICE_REMOVED; 23 event += 1; 24 delete device; 25 mNeedToSendFinishedDeviceScan = true; 26 if (--capacity == 0) { 27 break; 28 } 29 } 30 31 // 檢查mNeedToScanDevices是否為true,如果為true,就執行設備掃描。在scanDevicesLocked里面,會打開/dev/input目錄,並把循環調用
      openDeviceLocked,在openDeviceLocked里面int fd = open(devicePath, O_RDWR)打開一個input 設備
32 if (mNeedToScanDevices) { 33 mNeedToScanDevices = false; 34 scanDevicesLocked(); 35 mNeedToSendFinishedDeviceScan = true; 36 } 37 38 // Check to see if the device is on our excluded list 39 // 判斷這個設備是否已經存在,如果存在,就關閉退出。 40 for (size_t i = 0; i < mExcludedDevices.size(); i++) { 41 const String8& item = mExcludedDevices.itemAt(i); 42 if (identifier.name == item) { 43 LOGI("ignoring event id %s driver %s\n", devicePath, item.string()); 44 close(fd); 45 return -1; 46 } 47   }

下面得到設備一系列信息。

 1 // 創建一個eventhub層的device對象
 2   Device* device = new Device(fd, deviceId, String8(devicePath), identifier);
 3 
 4 // Load the configuration file for the device.
 5 // 得到設備的idc配置文件,這就是為什么android4.0需要idc文件
 6     loadConfigurationLocked(device);
 7 
 8 // Figure out the kinds of events the device reports.
 9     ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(device->keyBitmask)), device->keyBitmask);
10     ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(device->absBitmask)), device->absBitmask);
11     ioctl(fd, EVIOCGBIT(EV_REL, sizeof(device->relBitmask)), device->relBitmask);
12     ioctl(fd, EVIOCGBIT(EV_SW, sizeof(device->swBitmask)), device->swBitmask);
13     ioctl(fd, EVIOCGBIT(EV_LED, sizeof(device->ledBitmask)), device->ledBitmask);
14     ioctl(fd, EVIOCGPROP(sizeof(device->propBitmask)), device->propBitmask);

得到設備各種配置,接下設置deviceclass,就設備的類型

 1     // See if this is a touch pad.
 2     // Is this a new modern multi-touch driver?
 3     if (test_bit(ABS_MT_POSITION_X, device->absBitmask)
 4             && test_bit(ABS_MT_POSITION_Y, device->absBitmask)) {
 5         // Some joysticks such as the PS3 controller report axes that conflict
 6         // with the ABS_MT range.  Try to confirm that the device really is
 7         // a touch screen.
 8         if (test_bit(BTN_TOUCH, device->keyBitmask) || !haveGamepadButtons) {
 9             device->classes |= INPUT_DEVICE_CLASS_TOUCH | INPUT_DEVICE_CLASS_TOUCH_MT;
10         }
11     // Is this an old style single-touch driver?
12     } else if (test_bit(BTN_TOUCH, device->keyBitmask)
13             && test_bit(ABS_X, device->absBitmask)
14             && test_bit(ABS_Y, device->absBitmask)) {
15         device->classes |= INPUT_DEVICE_CLASS_TOUCH;
16     }

上面就是根據驅動程序里面的設置來判斷input device是多點觸摸還是單點觸摸,現在是不是看到和觸摸屏有點關系了。

1     // Determine whether the device is external or internal.
2     if (isExternalDeviceLocked(device)) {
3         device->classes |= INPUT_DEVICE_CLASS_EXTERNAL;
4     }

判斷是不是外部設備,根據兩個條件判斷,一是在idc文件里面如果有“device.internal”存在,就是內部設備,否則是外部設備。如果沒有這個域存在,根據硬件設備的總線判斷,如果是usbbluetooth bus,就是外部設備。這個留着后面有作用。

1     if (epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, &eventItem))
2     // 將設備加入到mEpollFd監控里面
3 
4     device->next = mOpeningDevices;
5     mOpeningDevices = device;
6     // 將設備加入需要打開設備鏈表里面

至此,/dev/input/下面所有的設備對於linux層都已經打開,並且都添加到了mEpollFd監控里面,但是android層面的device還沒有添加和初始化,只是放在需要打開設備鏈表里面。接着設置mNeedToSendFinishedDeviceScantrue。因為mNeedToScanDevices初始化為true,因此第一次進入getEvents就會執行這部分代碼。

 1       while (mOpeningDevices != NULL) {
 2             Device* device = mOpeningDevices;
 3             LOGV("Reporting device opened: id=%d, name=%s\n",
 4                  device->id, device->path.string());
 5             mOpeningDevices = device->next;
 6             event->when = now;
 7             event->deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id;
 8             event->type = DEVICE_ADDED;
 9             event += 1;
10             mNeedToSendFinishedDeviceScan = true;
11             if (--capacity == 0) {
12                 break;
13             }
14         }

檢查mOpeningDevices鏈表是否存在,如果存在,循環把需要添加的設備信息放在event里面,同時設置event typeDEVICE_ADDED。設置mNeedToSendFinishedDeviceScantrue。每循環一次,capacity1capacity等於0,就退出for循環,表明這次getEvents已經取得256event事件了,返回給inputreader處理。因為一開始會執行mNeedToScanDevices 代碼,只要/dev/input下面有設備節點存在,mOpeningDevices也會存在,所以開始就會執行這段代碼。

1         if (mNeedToSendFinishedDeviceScan) {
2             mNeedToSendFinishedDeviceScan = false;
3             event->when = now;
4             event->type = FINISHED_DEVICE_SCAN;
5             event += 1;
6             if (--capacity == 0) {
7                 break;
8             }
9         }        

如果mNeedToSendFinishedDeviceScantrue,就把FINISHED_DEVICE_SCAN信息放在event里面,同時capacity1capacity等於0,就退出for循環,表明這次getEvents已經取得256event事件了,返回給inputreader處理。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM