天天看點

Input輸入系統(一)輸入事件系統架構Input系統啟動流程InputReader

輸入事件系統架構

Android事件輸入系統主要負責鍵盤、觸屏、滑鼠等輸入裝置的事件輸入及向焦點視窗和焦點視圖的事件派發,插入,過濾,攔截等功能。Android支援的輸入裝置主要有:鍵盤、滑鼠、觸摸屏、軌迹球、遊戲搖杆/搖桿、繪圖闆等。

Android系統中輸入系統主要包括如下幾部分:View,InputDispatcher,InputReader,EventHub ,kernel幾部分,他們之間的關系如下:

Input輸入系統(一)輸入事件系統架構Input系統啟動流程InputReader

與其他子產品的互動關系如下:

Input輸入系統(一)輸入事件系統架構Input系統啟動流程InputReader

Input系統啟動流程

Input在SystemServer中的startOtherServices方法中啟動,在啟動過程中主要過程如下:

Input輸入系統(一)輸入事件系統架構Input系統啟動流程InputReader

主要代碼如下:

    startOtherServices(){
            inputManager = new InputManagerService(context);
            inputManager.setWindowManagerCallbacks(wm.getInputMonitor());
            inputManager.start();
      ......
      }
           

建立InputMangerService對象

 public InputManagerService(Context context) {
        this.mContext = context;
        //建立一個負責處理DisplayThread線程中的Message的Handler
        this.mHandler = new InputManagerHandler(DisplayThread.get().getLooper());
​
        mUseDevInputEventForAudioJack =
                context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);
        Slog.i(TAG, "Initializing input manager, mUseDevInputEventForAudioJack="
                + mUseDevInputEventForAudioJack);
        //用mPtr儲存native層的InputManagerService
        mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());
​
        String doubleTouchGestureEnablePath = context.getResources().getString(
                R.string.config_doubleTouchGestureEnableFile);
        mDoubleTouchGestureEnableFile = TextUtils.isEmpty(doubleTouchGestureEnablePath) ? null :
            new File(doubleTouchGestureEnablePath);
        //初始化完成後将Service添加到LocalServices,通過Map以鍵值對的形式存儲
        LocalServices.addService(InputManagerInternal.class, new LocalService());
    }
           

初始化native層的InputManagerService

NativeInputManager::NativeInputManager(jobject contextObj,
        jobject serviceObj, const sp<Looper>& looper) :
        mLooper(looper), mInteractive(true) {
    JNIEnv* env = jniEnv();
    //将Java層的Context和InputManagerService轉換為native層的Context和InputManagerService存儲在mContextObj和mServiceObj中
    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;
        mLocked.pointerCapture = false;
    }
    mInteractive = true;
   //建立EventHub
    sp<EventHub> eventHub = new EventHub();
    //建立InputManager
    mInputManager = new InputManager(eventHub, this, this);
}
           

EventHub的建立(見2.1)

InputManager的建立

//建立一個InputDispatcher對象用于分發事件,一個InputReader對象用于讀事件并把事件交給InputDispatcher分發,然後調用initialize()初始化,其實也就是建立了InputReaderThread和InputDispatcherThread
InputManager::InputManager(
        const sp<InputReaderInterface>& reader,
        const sp<InputDispatcherInterface>& dispatcher) :
        mReader(reader),
        mDispatcher(dispatcher) {
    initialize();
}
//InputDispatcher和InputReader的建立都相對簡單。InputDispatcher會建立自己線程的Looper,以及設定根據傳入的dispatchPolicy設定分發規則。InputReader則會将傳入的InputDispatcher封裝為監聽對象存起來,做一些資料初始化就結束了
​
void InputManager::initialize() {
    mReaderThread = new InputReaderThread(mReader);
    mDispatcherThread = new InputDispatcherThread(mDispatcher);
}
           

InputManagerService的start()

   public void start() {
        Slog.i(TAG, "Starting input manager");
        //調用nativeStart方法,其實就是調用InputManager的start()方法
        nativeStart(mPtr);
​
        // 将InputManagerService交給WatchDog監控
        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);
        //主動調用updateXXX方法更新(初始化)
        updatePointerSpeedFromSettings();
        updateShowTouchesFromSettings();
        updateAccessibilityLargePointerFromSettings();
    
           

Input涉及主要線程

從上述結構圖中看到Input系統涉及到的主要線程為:

InputReader線程:通過EventHub從/dev/input節點擷取事件,轉換成EventEntry事件加入到InputDispatcher的mInboundQueue。當IMS.start啟動時啟動

InputDispatcher線程:從mInboundQueue隊列取出事件,轉換成DispatchEntry事件加入到connection的outboundQueue隊列。再然後開始處理分發事件,取出outbound隊列,放入waitQueue.

UI線程:(DisplayThread)建立socket pair,分别位于InputDispatcher線程和focused視窗所在程序的UI主線程,可互相通信。

a:UI主線程:通setFdEvents() 監聽socket用戶端,收到消息後回調NativeInputEventReceiver();

b:InputDispatcher線程: 通過IMS.registerInputChannel(),監聽socket服務端,收到消息後回調handleReceiveCallback

InputReader

InputReader同樣是system_server的子線程,負責輸入裝置的管理和輸入事件的處理,主要的作用如下:

  1. 從EventHub中獲得底層事件
  2. 處理輸入裝置添加、删除事件,管理裝置的InputMapper
  3. 處理輸入事件,由裝置對應的各個InputMapper處理之後傳遞InputDispatcher進行分發
Input輸入系統(一)輸入事件系統架構Input系統啟動流程InputReader

從上述結構圖看到:

InputReaderThread線程是和EventHub關聯是通過InputReaderThread線程啟動後,會循環執行mReader->looperonce,loopOnce()中會調用mEventHub->getEvents讀取事件,讀取事件後讀到了事件就會調用processEventsLocked處理事件處理完成後調用getInputDevicesLocked擷取輸入裝置資訊調用mPolicy->notifyInputDevicesChanged函數利用InputManagerService的代理通過Handler發送MSG_DELIVER_INPUT_DEVICES_CHANGED消息,通知輸入裝置發生了變化最後調用mQueuedListener->flush(),将事件隊列中的所有事件交給在InputReader中注冊過的InputDispatcher

EventHub

EventHub為輸入系統的最底層子產品,輸入事件的源頭,此類主要的作用為:

►監控輸入裝置添加、删除事件

►擷取新增裝置的資訊,如裝置的類别、支援哪些按鍵、鍵盤映射表等。

►擷取input_event,封裝成RawEvent傳遞給InputReader

►支援查詢輸入裝置目前的狀态。

●組合鍵狀态

●是否有LED燈或支援振動

new EventHub

EventHub的建立過程中做了以下事情:

建立mEpollFd用于監聽是否有資料(有無事件)可讀

建立mINotifyFd将它注冊到DEVICE_PATH(這裡路徑就是/dev/input)節點,并将它交給核心用于監聽該裝置節點的增删資料事件。那麼隻要有資料增删的事件到來,epoll_wait()就會傳回,使得EventHub能收到來自系統的通知,并擷取事件的詳細資訊

調用epoll_ctl函數将mEpollFd和mINotifyFd注冊到epoll中

定義int wakeFd[2]作為事件傳輸管道的讀寫兩端,并将讀端注冊到epoll中讓mEpollFd監聽

相關代碼如下:

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_SIZE_HINT:8
    mEpollFd = epoll_create(EPOLL_SIZE_HINT);
  
   //建立mINotifyFd将它注冊到DEVICE_PATH(這裡路徑就是/dev/input)節點,并将它交給核心用于監聽該裝置節點的增删資料事件。那麼隻要有資料增删的事件到來,epoll_wait()就會傳回,使得EventHub能收到來自系統的通知,并擷取事件的詳細資訊
    mINotifyFd = inotify_init();
    int result = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);
    LOG_ALWAYS_FATAL_IF(result < 0, "Could not register INotify for %s.  errno=%d",
            DEVICE_PATH, errno);
​
    struct epoll_event eventItem;
    memset(&eventItem, 0, sizeof(eventItem));//epoll_event這個結構體清空
    eventItem.events = EPOLLIN;//監測的事件類型為EPOLLIN
    eventItem.data.u32 = EPOLL_ID_INOTIFY;
    //調用epoll_ctl函數将mEpollFd和mINotifyFd注冊到epoll中,監測的fd為:mINotifyFd
    result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem);
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not add INotify to epoll instance.  errno=%d", errno);
    //作為事件傳輸管道的讀寫兩端,并将讀端注冊到epoll中讓mEpollFd監聽
    int wakeFds[2];
    result = pipe(wakeFds);
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not create wake pipe.  errno=%d", errno);
​
    mWakeReadPipeFd = wakeFds[0];//建立讀取事件的通道
    mWakeWritePipeFd = wakeFds[1];//建立寫入事件的通道
​
    result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);
    result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);
    eventItem.data.u32 = EPOLL_ID_WAKE;
    result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &eventItem);
    int major, minor;
    getLinuxRelease(&major, &minor);
    // EPOLLWAKEUP was introduced in kernel 3.5
    mUsingEpollWakeup = major > 3 || (major == 3 && minor >= 5);
}
           
Input輸入系統(一)輸入事件系統架構Input系統啟動流程InputReader

那麼這裡抛出一個問題:為什麼要把管道的讀端注冊到epoll中?假如EventHub因為getEvents讀不到事件而阻塞在epoll_wait()裡,而我們沒有綁定讀端的話,我們要怎麼喚醒EventHub?如果綁定了管道的讀端,我們就可以通過向管道的寫端寫資料進而讓EventHub因為得到管道寫端的資料而被喚醒。

EventHub如何監聽裝置的插拔和讀取底層的事件,主要通過inotify和epoll

Input輸入系統(一)輸入事件系統架構Input系統啟動流程InputReader