Android輸入系統(二)IMS的啟動過程和輸入事件的處理

劉望舒發表於2019-02-14

本文首發於 劉望舒的部落格
地址:liuwangshu.cn/framework/i…

關聯絡列
解析WMS系列
深入理解JNI系列
輸入系統系列

前言

在上一篇文章中,我們學習了IMS的誕生(建立),IMS建立後還會進行啟動,這篇文章我們來學習IMS的啟動過程和輸入事件的處理。

1.IMS的啟動過程

IMS的建立在SystemServer的startOtherServices方法中,不瞭解請檢視Android輸入系統(一)輸入事件傳遞流程和InputManagerService的誕生這篇文章。
frameworks/base/services/java/com/android/server/SystemServer.java

 private void startOtherServices() {
 ...
         traceBeginAndSlog("StartInputManagerService");
         inputManager = new InputManagerService(context);
         traceEnd();
 ...
         traceBeginAndSlog("StartInputManager");
         inputManager.setWindowManagerCallbacks(wm.getInputMonitor());
         inputManager.start();
         traceEnd();

}
複製程式碼

建立IMS後就會緊接著執行IMS的啟動。IMS的start方法如下所示。
frameworks/base/services/core/java/com/android/server/input/InputManagerService.java

 public void start() {
        Slog.i(TAG, "Starting input manager");
        nativeStart(mPtr);
        // Add ourself to the Watchdog monitors.
        Watchdog.getInstance().addMonitor(this);
      ...
    }
複製程式碼

IMS的start方法中,會將自身新增到Watchdog中進行監控,用於定時檢測系統關鍵服務(AMS和WMS等)是否可能發生死鎖。
nativeStart方法對應的JNI層的函式是什麼呢?檢視com_android_server_input_InputManagerService的gInputManagerMethods陣列,不理解JNI的可以檢視深入理解JNI系列文章。
frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp

static const JNINativeMethod gInputManagerMethods[] = {
...
   { "nativeStart", "(J)V",
            (void*) nativeStart },
...
}
複製程式碼

nativeStart方法對應的JNI函式為nativeStart:
frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp

static void nativeStart(JNIEnv* env, jclass /* clazz */, jlong ptr) {
    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
    status_t result = im->getInputManager()->start();//1
    if (result) {
        jniThrowRuntimeException(env, "Input manager could not be started.");
    }
}
複製程式碼

用reinterpret_cast操作符將jlong型別的ptr強制轉換為原型別(NativeInputManager指標型別)。註釋1處會呼叫InputManager的start函式。
frameworks/native/services/inputflinger/InputManager.cpp

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;
}
複製程式碼

可以看到InputManager的start函式執行了InputReaderThread和InputDispatcherThread,這兩個執行緒在Android輸入系統(一)輸入事件傳遞流程和InputManagerService的誕生提到過,它們在InputManager的建構函式中被建立,其中InputReaderThread中執行了InputReader, InputDispatcherThread中執行了InputDispatcher。

2.InputDispatcher的啟動過程

先來回顧下InputDispatcher和InputReader是在哪建立的,InputManager的建構函式如下所示。
frameworks/native/services/inputflinger/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();
}
複製程式碼

可以看到InputDispatcher和InputReader是有關聯的,InputDispatcher會作為一個引數傳入到InputReader中。
InputDispatcher是在InputReader之前建立的,這個順序不能改變,因為要確保InputReader將加工後的輸入事件交給InputDispatcher時,InputDispatcher已經被建立。
InputDispatcher的定義如下所示。
frameworks/native/services/inputflinger/InputDispatcher.h

class InputDispatcherThread : public Thread {
public:
    explicit InputDispatcherThread(const sp<InputDispatcherInterface>& dispatcher);
    ~InputDispatcherThread();
private:
    virtual bool threadLoop();
    sp<InputDispatcherInterface> mDispatcher;
};
}
複製程式碼

InputDispatcher.h中定義了threadLoop純虛擬函式,InputDispatcher繼承了Thread。native的Thread內部有一個迴圈,當執行緒執行時,會呼叫threadLoop函式,如果它返回true並且沒有呼叫requestExit函式,就會接著迴圈呼叫threadLoop函式。
檢視InputDispatcherThread的threadLoop函式是如何實現的。
frameworks/native/services/inputflinger/InputDispatcher.cpp

bool InputDispatcherThread::threadLoop() {
    mDispatcher->dispatchOnce();
    return true;
}
複製程式碼

threadLoop函式中只呼叫了InputDispatcher的dispatchOnce函式:
frameworks/native/services/inputflinger/InputDispatcher.cpp

void InputDispatcher::dispatchOnce() {
    nsecs_t nextWakeupTime = LONG_LONG_MAX;
    { // acquire lock
        AutoMutex _l(mLock);
        mDispatcherIsAliveCondition.broadcast();
        if (!haveCommandsLocked()) {//1
            dispatchOnceInnerLocked(&nextWakeupTime);//2
        }
        if (runCommandsLockedInterruptible()) {
            nextWakeupTime = LONG_LONG_MIN;
        }
    } // release lock
    nsecs_t currentTime = now();//3
    int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);//4
    mLooper->pollOnce(timeoutMillis);
}
複製程式碼

註釋1處用於檢查InputDispatcher的快取佇列中是否有等待處理 的命令,如果沒有就會執行註釋2處的dispatchOnceInnerLocked函式,用來將輸入事件分發給合適的Window。註釋3處獲取當前的時間,結合註釋4處,得出InputDispatcher需要睡眠的時間為timeoutMillis。最後呼叫Looper的pollOnce函式使InputDispatcher進入睡眠狀態,並將它的最長的睡眠的時間設定為timeoutMillis。當有輸入事件產生時,InputReader就會將睡眠狀態的InputDispatcher
喚醒,InputDispatcher會重新開始分發輸入事件。
那麼InputReader是如何喚醒InputDispatcher的呢? 我們接著往下看。

3.InputReader處理事件過程

InputReader是在InputReaderThread中啟動的,InputReaderThread和InputDispatcherThread的定義是類似的,也是繼承了Thread並定義了threadLoop純虛擬函式。如果處理的事件為鍵盤輸入事件,則呼叫時序圖如下所示。

Android輸入系統(二)IMS的啟動過程和輸入事件的處理

InputReaderThread的threadLoop函式如下所示。
frameworks/native/services/inputflinger/InputReader.cpp

bool InputReaderThread::threadLoop() {
    mReader->loopOnce();
    return true;
}
複製程式碼

threadLoop函式中只呼叫了InputReader的loopOnce函式:
frameworks/native/services/inputflinger/InputReader.cpp

void InputReader::loopOnce() {
  ...
    //通過EventHub的getEvents函式獲取事件資訊存在mEventBuffer中
    size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);//1
    { // acquire lock
        AutoMutex _l(mLock);
        mReaderIsAliveCondition.broadcast();
        if (count) {
            //如果有事件資訊,呼叫processEventsLocked函式對事件進行加工處理
            processEventsLocked(mEventBuffer, count);//2
        }
    ...
}
複製程式碼

註釋1處呼叫EventHub的getEvents函式來獲取裝置節點的事件資訊到mEventBuffer中,事件資訊主要有兩種,一種是裝置節點的增刪事件(裝置事件),一種是原始輸入事件。註釋2處的processEventsLocked函式用於對mEventBuffer中的原始輸入事件資訊進行加工處理,加工後的輸入事件會交由InputDispatcher來處理,processEventsLocked函式如下所示。
frameworks/native/services/inputflinger/InputReader.cpp

void InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) {
    //遍歷所有的事件
    for (const RawEvent* rawEvent = rawEvents; count;) {
        int32_t type = rawEvent->type;
        size_t batchSize = 1;
        //事件型別分為原始輸入事件和裝置事件,這個條件語句對原始輸入事件進行處理
        if (type < EventHubInterface::FIRST_SYNTHETIC_EVENT) {
            int32_t deviceId = rawEvent->deviceId;
            while (batchSize < count) {
                if (rawEvent[batchSize].type >= EventHubInterface::FIRST_SYNTHETIC_EVENT
                        || rawEvent[batchSize].deviceId != deviceId) {
                    break;
                }
                batchSize += 1;
            }
#if DEBUG_RAW_EVENTS
            ALOGD("BatchSize: %d Count: %d", batchSize, count);
#endif
         //處理deviceId所對應的裝置的原始輸入事件
            processEventsForDeviceLocked(deviceId, rawEvent, batchSize);//1
        } else {
        //對裝置事件進行處理
            switch (rawEvent->type) {
            case EventHubInterface::DEVICE_ADDED:
                addDeviceLocked(rawEvent->when, rawEvent->deviceId);
                break;
            case EventHubInterface::DEVICE_REMOVED:
                removeDeviceLocked(rawEvent->when, rawEvent->deviceId);
                break;
            case EventHubInterface::FINISHED_DEVICE_SCAN:
                handleConfigurationChangedLocked(rawEvent->when);
                break;
            default:
                ALOG_ASSERT(false); // can`t happen
                break;
            }
        }
        count -= batchSize;
        rawEvent += batchSize;
    }
}
複製程式碼

InputReader的processEventsLocked函式首先遍歷了所有的事件,這些事件用RawEvent物件來表示,將原始
輸入事件和裝置事件分開處理,其中裝置事件分為DEVICE_ADDED、DEVICE_REMOVED和FINISHED_DEVICE_SCAN,這些事件是在EventHub的getEvent函式中生成的。如果是DEVICE_ADDED事件(裝置新增事件),InputReader會新建InputDevice物件,用來儲存裝置資訊,並且會將InputDevice儲存在
KeyedVector型別的容器mDevices中。
同一個裝置的輸入事件交給processEventsForDeviceLocked函式來處理。
frameworks/native/services/inputflinger/InputReader.cpp

void InputReader::processEventsForDeviceLocked(int32_t deviceId,
        const RawEvent* rawEvents, size_t count) {
    ssize_t deviceIndex = mDevices.indexOfKey(deviceId);//1
    if (deviceIndex < 0) {
        ALOGW("Discarding event for unknown deviceId %d.", deviceId);
        return;
    }
    InputDevice* device = mDevices.valueAt(deviceIndex);//2
    if (device->isIgnored()) {
        //ALOGD("Discarding event for ignored deviceId %d.", deviceId);
        return;
    }
    device->process(rawEvents, count);
}
複製程式碼

註釋1處根據deviceId從mDevices中獲取對應的deviceIndex,註釋2處再根據這個deviceIndex從mDevices中獲取對應的InputDevice。最後會呼叫InputDevice的process函式:
frameworks/native/services/inputflinger/InputReader.cpp

void InputDevice::process(const RawEvent* rawEvents, size_t count) {*
    size_t numMappers = mMappers.size();
    //遍歷處理該InputDevice所有的事件
    for (const RawEvent* rawEvent = rawEvents; count--; rawEvent++) {
#if DEBUG_RAW_EVENTS
        ALOGD("Input event: device=%d type=0x%04x code=0x%04x value=0x%08x when=%lld",
                rawEvent->deviceId, rawEvent->type, rawEvent->code, rawEvent->value,
                rawEvent->when);
#endif
        //mDropUntilNextSync的值預設為false,如果裝置的輸入事件緩衝區溢位,這個值會置為true。
        if (mDropUntilNextSync) {
            ...
        } else {
            for (size_t i = 0; i < numMappers; i++) {//1
                InputMapper* mapper = mMappers[i];
                mapper->process(rawEvent);//2
            }
        }
    }
}
複製程式碼

首先會遍歷InputDevice中的所有的事件,真正加工原始輸入事件的是InputMapper物件,由於原始輸入事件的型別很多,因此在InputMapper有很多子類,用於加工不同的原始輸入事件,比如KeyboardInputMapper用於處理鍵盤輸入事件,TouchInputMapper用於處理觸控輸入事件。
註釋1處遍歷所有的InputMapper,在註釋2處將原始輸入事件交由這些InputMapper來處理,至於是哪個InputMapper來處理,InputReader並不關心。
這裡就以處理鍵盤輸入事件為例,KeyboardInputMapper的process函式如下所示。
frameworks/native/services/inputflinger/InputReader.cpp

void KeyboardInputMapper::process(const RawEvent* rawEvent) {
    switch (rawEvent->type) {
    case EV_KEY: {//1
        int32_t scanCode = rawEvent->code;
        int32_t usageCode = mCurrentHidUsage;
        mCurrentHidUsage = 0;
        if (isKeyboardOrGamepadKey(scanCode)) {
            processKey(rawEvent->when, rawEvent->value != 0, scanCode, usageCode);//2
        }
        break;
    }
   ...
    }
}
複製程式碼

註釋1處,如果事件的型別為按鍵型別的事件,就會呼叫註釋2處的KeyboardInputMapper的processKey函式。
frameworks/native/services/inputflinger/InputReader.cpp

void KeyboardInputMapper::processKey(nsecs_t when, bool down, int32_t scanCode,
        int32_t usageCode) {
   ...
    NotifyKeyArgs args(when, getDeviceId(), mSource, policyFlags,
            down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP,
            AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, keyMetaState, downTime);
    getListener()->notifyKey(&args);//1
}
複製程式碼

processKey函式會將加工後的鍵盤輸入事件封裝為NotifyKeyArgs,將NotifyKeyArgs通知給InputListenerInterface。
InputDispatcher繼承了InputDispatcherInterface,而InputDispatcherInterface繼承了InputListenerInterface,因此註釋1處實際上是呼叫了InputDispatcher的notifyKey函式,將NotifyKeyArgs交給InputDispatcher處理。
frameworks/native/services/inputflinger/InputDispatcher.cpp

void InputDispatcher::notifyKey(const NotifyKeyArgs* args) {
  ...
    bool needWake;
    { // acquire lock
        mLock.lock();
        if (shouldSendKeyToInputFilterLocked(args)) {
            mLock.unlock();
            policyFlags |= POLICY_FLAG_FILTERED;
            if (!mPolicy->filterInputEvent(&event, policyFlags)) {
                return; // event was consumed by the filter
            }
            mLock.lock();
        }
        int32_t repeatCount = 0;
        KeyEntry* newEntry = new KeyEntry(args->eventTime,
                args->deviceId, args->source, policyFlags,
                args->action, flags, keyCode, args->scanCode,
                metaState, repeatCount, args->downTime);//1
        needWake = enqueueInboundEventLocked(newEntry);//2
        mLock.unlock();
    } // release lock
    if (needWake) {
        mLooper->wake();
    }
}
複製程式碼

程式碼塊中採用Mutex互斥鎖的形式,在註釋1處根據NotifyKeyArgs,重新封裝一個KeyEntry物件,代表一次按鍵資料。註釋2處根據KeyEntry,來判斷是否需要將睡眠中的InputDispatcher喚醒,如果需要,就呼叫Looper的wake函式進行喚醒,InputDispatcher被喚醒後就會重新對輸入事件的分發,具體的回頭檢視第2小節。

總結

本文涉及到了四個關鍵的類,分別是IMS、EventHub、InputDispatcher和InputReader,它們做了如下的工作:

  1. IMS啟動了InputDispatcherThread和InputReaderThread,分別用來執行InputDispatcher和InputReader。
  2. InputDispatcher先於InputReader被建立,InputDispatcher的dispatchOnceInnerLocked函式用來將事件分發給合適的Window。InputDispatcher沒有輸入事件處理時會進入睡眠狀態,等待InputReader通知喚醒。
  3. InputReader通過EventHub的getEvents函式獲取事件資訊,如果是原始輸入事件,就將這些原始輸入事件交由不同的InputMapper來處理,最終交由InputDispatcher來進行分發。
  4. InputDispatcher的notifyKey函式中會根據按鍵資料來判斷InputDispatcher是否要被喚醒,InputDispatcher被喚醒後,會重新呼叫dispatchOnceInnerLocked函式將輸入事件分發給合適的Window。
Android輸入系統(二)IMS的啟動過程和輸入事件的處理

相關文章