前言
在網上看到好多關於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得到系統的分辨率,這個是真實的分辨率,這種方法需要apk有root或者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
InputReader從EventHub中讀取原始事件數據(RawEvent),並由各個InputMapper處理之后輸入對應的input listener.InputReader擁有一個InputMapper集合。它做的大部分工作在InputReader線程中完成,但是InputReader可以接受任意線程的查詢。為了可管理性,InputReader使用一個簡單的Mutex來保護它的狀態。InputReader擁有一個EventHub對象,但這個對象不是它創建的,而是在創建InputReader時作為參數傳入的。
-
InputDispatcher
InputDispatcher負責把事件分發給輸入目標,其中的一些功能(如識別輸入目標)由獨立的policy對象控制。
-
InputManager
InputManager是系統事件處理的核心,它雖然不做具體的事,但管理工作還是要做的,比如接受我們客戶的投訴和索賠要求,或者老板的出氣筒。
InputManager使用兩個線程:
1)InputReaderThread叫做"InputReader"線程,它負責讀取並預處理RawEvent,applies policy並且把消息送入DispatcherThead管理的隊列中。
2)InputDispatcherThread叫做"InputDispatcher"線程,它在隊列上等待新的輸入事件,並且異步地把這些事件分發給應用程序。
InputReaderThread類與InputDispatcherThread類不共享內部狀態,所有的通信都是單向的,從InputReaderThread到InputDispatcherThread。兩個類可以通過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);
至此我們創建了一個java層input設備管理器。
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事件獲取有關。
至此我們創建了一個native層input設備管理器,具體作用見上面說明。
首先是去InputManager.cpp (frameworks\base\services\input) 文件里面InputManager::InputManager調用
1 mDispatcher = new InputDispatcher(dispatcherPolicy); 2 mReader = new InputReader(eventHub, readerPolicy, mDispatcher); 3 initialize();
它創建了InputDispatcher對象,同時也創建了InputReader對象。並分別暫存於mDispatcher和mReader變量中。注意eventHub和mDispatcher都作為參數創建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作為入口函數,以上面的mDispatcherThread或mReaderThread作為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監聽mINotifyFd和mWakeReadPipeFd的變化。
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);
得到設備各種配置,接下設置device的class,就設備的類型
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”存在,就是內部設備,否則是外部設備。如果沒有這個域存在,根據硬件設備的總線判斷,如果是usb和bluetooth 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還沒有添加和初始化,只是放在需要打開設備鏈表里面。接着設置mNeedToSendFinishedDeviceScan為true。因為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 type為DEVICE_ADDED。設置mNeedToSendFinishedDeviceScan為true。每循環一次,capacity減1,capacity等於0,就退出for循環,表明這次getEvents已經取得256個event事件了,返回給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 }
如果mNeedToSendFinishedDeviceScan為true,就把FINISHED_DEVICE_SCAN信息放在event里面,同時capacity減1,capacity等於0,就退出for循環,表明這次getEvents已經取得256個event事件了,返回給inputreader處理。