Android Input子系統:Input進程的創建,監聽線程的啟動
本文主要從系統源碼的角度帶你一步步了解Android Input子系統。
從我個人的理解來看,Android的Input系統其實就是系統級的事件處理、分發框架,它需要的功能模塊大致有:事件讀取、事件分類、事件分發。那么我們就從整個Input系統的輸入源入手,了解事件是如何被輸入到Input系統中的。
在看代碼前我們先想一想,如果要我們設計一個事件分發框架的輸入讀取模塊,要考慮到哪些子模塊:
- 事件生成模塊(當用戶對設備進行操作產生InputEvent,硬件產生中斷將事件交給驅動,驅動交給內核,內核交給framework)
- 事件監聽模塊(這里就很像設計一個服務器,為了及時響應來自客戶端的請求,則需要啟動一個線程監聽)
- 事件讀取模塊
- 事件分發模塊
那么現在我們最起碼可以知道整個學習的起點了,就是Input系統中,負責監聽的線程是誰,監聽的過程中它們做了什么。 在開始之前,給大家分享一張我根據本文內容畫的圖:
InputManagerService初始化概覽
首先,有幾點共識我們都可以達成:
- Android Framework層的Service(Java)都是由system_server進程創建的(由于沒有fork,因此都運行在system_server進程中)
- Service創建后就會交給運行在system_server進程中的ServiceManager管理。
因此對于InputManagerService的創建,我們可以在SystemServer的startOtherServices()方法中找到,該方法做了以下事情:
- 創建InputManagerService對象
- 將它交給ServiceManager管理
- 將WindowManagerService的InputMonitor注冊到InputManagerService中作為窗口響應事件后的回調
- 完成以上工作后啟動InputManagerService。
- SystemServer.javastartOtherServices(){
- ……
- inputManager = new InputManagerService(context);
- ……
- inputManager.setWindowManagerCallbacks(wm.getInputMonitor());
- inputManager.start();
- ……
- }
接下來我們就逐部分學習相應的處理。
InputManagerService對象的創建
創建InputManagerService對象時會完成以下工作:
- 創建一個負責處理DisplayThread線程中的Message的Handler
- 調用nativeInit初始化native層的InputManagerService,初始化的時候傳入了DisplayThread的消息隊列
- 用mPtr保存native層的InputManagerService
- 初始化完成后將Service添加到LocalServices,通過Map以鍵值對的形式存儲
- InputManagerService.javapublic InputManagerService(Context context) { this.mContext = context; this.mHandler = new InputManagerHandler(DisplayThread.get().getLooper());
- mUseDevInputEventForAudioJack =
- context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);
- Slog.i(TAG, "Initializing input manager, mUseDevInputEventForAudioJack="
- + mUseDevInputEventForAudioJack);
- mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());
- LocalServices.addService(InputManagerInternal.class, new LocalService());
- }
這里可能有人就會問了,為什么InputManagerService要和DisplayThread綁定在一起?大家不妨想想,InputEvent無論如何被獲取、歸類、分發,最終還是要被處理,也就意味著最終它的處理結果都要在UI上體現,那么InputManagerService自然要選擇和UI親近一些的線程在一起了。
但是問題又來了,應用都是運行在自己的主線程里的,難道InputManagerService要一個個綁定么,還是一個個輪詢?這些做法都太過低效,那換個辦法,可不可以和某個管理或非常親近所有應用UI的線程綁定在一起呢?
答案是什么,我在這里先不說,大家可以利用自己的知識想想。
初始化native層的InputManagerService
在nativeInit函數中,將Java層的MessageQueue轉換為native層的MessageQueue,然后再取出Looper用于NativeInputManager的初始化。可見這里的重頭戲就是NativeInputManager的創建,這個過程做了以下事情:
- 將Java層的Context和InputManagerService轉換為native層的Context和InputManagerService存儲在mContextObj和mServiceObj中
- 初始化變量
- 創建EventHub
- 創建InputManager
- com_android_server_input_InputManagerService.cpp
- NativeInputManager::NativeInputManager(jobject contextObj,
- jobject serviceObj, const sp<Looper>& looper) :
- mLooper(looper), mInteractive(true) {
- JNIEnv* env = jniEnv();
- mContextObj = env->NewGlobalRef(contextObj);
- mServiceObj = env->NewGlobalRef(serviceObj);
- { AutoMutex _l(mLock);
- mLocked.systemUiVisibility = ASYSTEM_UI_VISIBILITY_STATUS_BAR_VISIBLE;
- mLocked.pointerSpeed = 0;
- mLocked.pointerGesturesEnabled = true;
- mLocked.showTouches = false;
- }
- mInteractive = true;
- sp<EventHub> eventHub = new EventHub();
- mInputManager = new InputManager(eventHub, this, this);
- }
EventHub
看到這里很多人就會想,EventHub是什么?取英語釋義來看,它的意思是事件樞紐。我們在文章開頭的時候也提到過,Input系統的事件來源于驅動/內核,那么我們可以猜測EventHub是處理來自驅動/內核的元事件的樞紐。接下來就在源碼中驗證我們的想法吧。
EventHub的創建過程中做了以下事情:
- 創建mEpollFd用于監聽是否有數據(有無事件)可讀
- 創建mINotifyFd將它注冊到DEVICE_PATH(這里路徑就是/dev/input)節點,并將它交給內核用于監聽該設備節點的增刪數據事件。那么只要有數據增刪的事件到來,epoll_wait()就會返回,使得EventHub能收到來自系統的通知,并獲取事件的詳細信息
- 調用epoll_ctl函數將mEpollFd和mINotifyFd注冊到epoll中
- 定義int wakeFd[2]作為事件傳輸管道的讀寫兩端,并將讀端注冊到epoll中讓mEpollFd監聽
- EventHub.cpp
- EventHub::EventHub(void) :
- mBuiltInKeyboardId(NO_BUILT_IN_KEYBOARD), mNextDeviceId(1), mControllerNumbers(),
- mOpeningDevices(0), mClosingDevices(0),
- mNeedToSendFinishedDeviceScan(false),
- mNeedToReopenDevices(false), mNeedToScanDevices(true),
- mPendingEventCount(0), mPendingEventIndex(0), mPendingINotify(false) {
- acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID);
- mEpollFd = epoll_create(EPOLL_SIZE_HINT);
- LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance. errno=%d", errno);
- mINotifyFd = inotify_init();
- int result = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);
- ……
- result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem);
- ……
- int wakeFds[2];
- result = pipe(wakeFds);
- ……
- mWakeReadPipeFd = wakeFds[0];
- mWakeWritePipeFd = wakeFds[1];
- result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);
- ……
- result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);
- ……
- result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &eventItem);
- ……
- }
那么這里拋出一個問題:為什么要把管道的讀端注冊到epoll中?假如EventHub因為getEvents讀不到事件而阻塞在epoll_wait()里,而我們沒有綁定讀端的話,我們要怎么喚醒EventHub?如果綁定了管道的讀端,我們就可以通過向管道的寫端寫數據從而讓EventHub因為得到管道寫端的數據而被喚醒。
InputManager的創建
接下來繼續說InputManager的創建,它的創建就簡單多了,創建一個InputDispatcher對象用于分發事件,一個InputReader對象用于讀事件并把事件交給InputDispatcher分發,,然后調用initialize()初始化,其實也就是創建了InputReaderThread和InputDispatcherThread。
- InputManager.cpp
- InputManager::InputManager( const sp<EventHubInterface>& eventHub, const sp<InputReaderPolicyInterface>& readerPolicy, const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
- mDispatcher = new InputDispatcher(dispatcherPolicy);
- mReader = new InputReader(eventHub, readerPolicy, mDispatcher);
- initialize();
- }void InputManager::initialize() {
- mReaderThread = new InputReaderThread(mReader);
- mDispatcherThread = new InputDispatcherThread(mDispatcher);
- }
InputDispatcher和InputReader的創建都相對簡單。InputDispatcher會創建自己線程的Looper,以及設置根據傳入的dispatchPolicy設置分發規則。InputReader則會將傳入的InputDispatcher封裝為監聽對象存起來,做一些數據初始化就結束了。
至此,InputManagerService對象的初始化就完成了,根據開頭說的,接下來就會調用InputManagerService的start()方法。
監聽線程InputReader和InputDispatcher的啟動
在start()方法中,做了以下事情:
- 調用nativeStart方法,其實就是調用InputManager的start()方法
- 將InputManagerService交給WatchDog監控
- 注冊觸控點速度、顯示觸控的觀察者,并注冊廣播監控它們
- 主動調用updateXXX方法更新(初始化)
- InputManagerService.javapublic void start() {
- Slog.i(TAG, "Starting input manager");
- nativeStart(mPtr); // Add ourself to the Watchdog monitors.
- Watchdog.getInstance().addMonitor(this);
- registerPointerSpeedSettingObserver();
- registerShowTouchesSettingObserver();
- registerAccessibilityLargePointerSettingObserver();
- mContext.registerReceiver(new BroadcastReceiver() { @Override
- public void onReceive(Context context, Intent intent) {
- updatePointerSpeedFromSettings();
- updateShowTouchesFromSettings();
- updateAccessibilityLargePointerFromSettings();
- }
- }, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mHandler);
- updatePointerSpeedFromSettings();
- updateShowTouchesFromSettings();
- updateAccessibilityLargePointerFromSettings();
- }
顯而易見這里最值得關注的就是InputManager的start()方法了,可惜這個方法并不值得我們如此關心,因為它做的事情很簡單,就是啟動InputDispatcherThread和InputReaderThread開始監聽。
- status_t InputManager::start() {
- status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY); if (result) {
- ALOGE("Could not start InputDispatcher thread due to error %d.", result); return result;
- }
- result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY); if (result) {
- ALOGE("Could not start InputReader thread due to error %d.", result);
- mDispatcherThread->requestExit(); return result;
- } return OK;
- }
那么InputReaderThread線程是怎么和EventHub關聯起來的呢?
對于InputReadThread:
- 啟動后循環執行mReader->loopOnce()
- loopOnce()中會調用mEventHub->getEvents讀取事件
- 讀到了事件就會調用processEventsLocked處理事件
- 處理完成后調用getInputDevicesLocked獲取輸入設備信息
- 調用mPolicy->notifyInputDevicesChanged函數利用InputManagerService的代理通過Handler發送MSG_DELIVER_INPUT_DEVICES_CHANGED消息,通知輸入設備發生了變化
- ***調用mQueuedListener->flush(),將事件隊列中的所有事件交給在InputReader中注冊過的InputDispatcher
- bool InputReaderThread::threadLoop() {
- mReader->loopOnce(); return true;
- }void InputReader::loopOnce() {
- ……
- size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
- { // acquire lock
- AutoMutex _l(mLock);
- mReaderIsAliveCondition.broadcast(); if (count) {
- processEventsLocked(mEventBuffer, count);
- }
- …… if (oldGeneration != mGeneration) {
- inputDevicesChanged = true;
- getInputDevicesLocked(inputDevices);
- }
- } // release lock
- // Send out a message that the describes the changed input devices.
- if (inputDevicesChanged) {
- mPolicy->notifyInputDevicesChanged(inputDevices);
- }
- ……
- mQueuedListener->flush();
- }
至此,Input系統有關事件輸入模塊的學習就結束了,在后續的文章中會繼續學習Input系統的事件歸類、分發流程,感興趣的朋友可以留意關注。