天天看點

Android Framework 輸入子系統(04)InputReader解讀

該系列文章總綱連結:專題分綱目錄 Android Framework 輸入子系統

本章關鍵點總結 & 說明:

Android Framework 輸入子系統(04)InputReader解讀

本章節隻需要關注➕讀取 & 流程,同時對于代碼分析,橫向表示縱向思維,一層層遞進調用,縱向表示步驟,是并列的執行順序。下面這張圖是本子產品放大的部分,如下所示:

Android Framework 輸入子系統(04)InputReader解讀

這樣就會清晰好多。

1  宏觀分析InputReader

這裡從InputManager的start函數,延續上一章節的分析,代碼如下:

status_t InputManager::start() {
    status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);
    //...
    result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);
    //...
    return OK;
}
           

InputManager啟動了一個InputReaderThread和InputDispatcherThread來讀取和分發輸入消息,調用它們的run方法後,會進入threadLoop函數(隻要threadLoop函數傳回true,該函數就會循環執行)。是以接下來直接分析InputReaderThread的threadLoop函數,實作如下:

bool InputReaderThread::threadLoop() {
    mReader->loopOnce();
    return true;
}
           

這裡繼續分析loopOnce,實作如下:

void InputReader::loopOnce() {
    int32_t oldGeneration;
    int32_t timeoutMillis;
    bool inputDevicesChanged = false;
    Vector<InputDeviceInfo> inputDevices;
    //...
    //關鍵點1 通過EventHub擷取事件清單
    size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);

    { // acquire lock
        AutoMutex _l(mLock);
        mReaderIsAliveCondition.broadcast();
        if (count) {
            //關鍵點2 對事件進行 加工處理
            processEventsLocked(mEventBuffer, count);
        }
        //...
    } // release lock

    // Send out a message that the describes the changed input devices.
    if (inputDevicesChanged) {
        mPolicy->notifyInputDevicesChanged(inputDevices);
    }
    //關鍵點3 釋出事件到InputDispatcher中
    mQueuedListener->flush();
}
           

是以對于讀取資料的環節,我們需要注重的就是3個關鍵點:

  1. 從EventHub中擷取事件清單。事件分為兩類:裝置節點中讀取的原始輸入事件,輸入裝置可用性變化事件(裝置事件)。
  2. 通過processEventsLocked()對事件進行處理。對于裝置事件,此函數對根據裝置的可用性加載或移除裝置對應的配置資訊。對于原始輸入事件,則在進行轉譯、封裝與加工後将結果存儲到mQueuedListener中。
  3. 所有事件處理完畢後,調用mQueuedListener.flush()将所有暫存的輸入事件一次性地傳遞給InputDispatcher。

接下來從這3個關鍵點出發,詳細分析Inputreader的流程。

2 讀取原始輸入事件

2.1 EventHub的構造器

這裡首先從EventHub的構造器代碼開始分析,看初始化了些什麼?代碼如下:

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);
	//這裡直接參考IMS分析的第一章節:inofify和epoll機制即可。
    mEpollFd = epoll_create(EPOLL_SIZE_HINT);
    mINotifyFd = inotify_init();
    int result = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);

    struct epoll_event eventItem;
    memset(&eventItem, 0, sizeof(eventItem));
    eventItem.events = EPOLLIN;
    eventItem.data.u32 = EPOLL_ID_INOTIFY;
    result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem);
    //建立了名為wakeFds的匿名管道,并将管道讀取端的描述符的可讀事件注冊到epoll對象中。
    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);
    eventItem.data.u32 = EPOLL_ID_WAKE;
    result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &eventItem);
    //...
}
           

這裡說明下:為什麼使用管道?

因為InputReader在執行getEvents()時會因無事件而導緻其線程阻塞在epoll_wait()的調用裡,然而有時希望能夠立刻喚醒InputReader線程使其處理一些請求。此時隻需向wakeFds管道的寫入端寫入任意資料,此時讀取端有資料可讀,使得epoll_wait()得以傳回,進而達到喚醒InputReader線程的目的。

2.2 EventHub->getEvents擷取事件

接下來看EventHub->getEvents(),它的代碼實作如下:

size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {
	//...
    struct input_event readBuffer[bufferSize];
    RawEvent* event = buffer;//這是元事件指針,可以指向一系列的事件,這些事件按照數組的方式存放的
    size_t capacity = bufferSize;
    bool awoken = false;
    for (;;) {
        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
        //mNeedToReopenDevices = false; mClosingDevices = 0; 
		//mNeedToSendFinishedDeviceScan = false; mOpeningDevices = 0
        //mNeedToScanDevices = true
        if (mNeedToScanDevices) {
            mNeedToScanDevices = false;
            scanDevicesLocked();
            mNeedToSendFinishedDeviceScan = true;
        }
     //...
        // Grab the next input event.
        bool deviceChanged = false;
        while (mPendingEventIndex < mPendingEventCount) {
            //...這裡省略對于其他的epoll類型的處理。
            //如果是EPOLLIN類型的事件,意味着epoll監視的檔案描述符中有寫入事件,這類事件是輸入事件
            Device* device = mDevices.valueAt(deviceIndex);
            if (eventItem.events & EPOLLIN) {
                int32_t readSize = read(device->fd, readBuffer,
                        sizeof(struct input_event) * capacity);
                if (readSize == 0 || (readSize < 0 && errno == ENODEV)) {
                    deviceChanged = true;
                    closeDeviceLocked(device);
                } else if //...
                } else {
                    int32_t deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id;
                    //産生事件的個數
                    size_t count = size_t(readSize) / sizeof(struct input_event);
                    for (size_t i = 0; i < count; i++) {
                        struct input_event& iev = readBuffer[i];
                       //這裡主要是對于事件時間戳的設定,考慮的因素挺多,但這不是本次分析的重點。
                        event->deviceId = deviceId;
                        //...其他屬性指派
                        event += 1;
                        capacity -= 1;
                    }
                    if (capacity == 0) {
                        mPendingEventIndex -= 1;
                        break;
                    }
                }
            } else 
            //...
        }
        //...
        int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis);
        //pollResult處理
    }
    return event - buffer;
}
           

總結一下,EventHub負責打開/dev/input/目錄下的所有裝置,然後為每一個裝置建立一個Device,并把這個Device放入EventHub所定義的數組們Device中。之後把這個裝置納入監視範圍。接下來等待事件發生,一旦有事件發生,就從産生input_event事件的裝置中讀取出這些裝置,把這些事件轉化為RawEvent類型放入InputReader提供的事件數組中,之後傳回。同時這裡簡單說明下RawEvent結構體,它的定義如下:

struct RawEvent {
    nsecs_twhen;    /* 發生事件時的時間戳 */
    int32_tdeviceId;/* 産生事件的裝置Id,由EventHub自行配置設定,InputReader根據它從EventHub中擷取此裝置的詳細資訊 */
    int32_ttype;    /* 事件的類型 */
    int32_tcode;    /* 事件代碼 */
    int32_tvalue;   /* 事件值 */
};
           

特殊說明:RawEvent也用來表示裝置增删事件,為此EventHub定義了三個特殊的事件類型DEVICE_ADD、DEVICE_REMOVED以及FINISHED_DEVICE_SCAN,用以與原始輸入事件進行差別。同時這裡列出 Android支援的裝置類型:

Android Framework 輸入子系統(04)InputReader解讀

getEvent函數是通過inotify和epoll機制對/dev/input/下的事件 進行擷取并封裝成一個個RawEvent事件。

接下來重點分析掃描裝置 scanDevicesLocked 和epoll_wait兩個關鍵點。

2.2.1 掃描裝置

關鍵方法scanDevicesLocked實作如下:

void EventHub::scanDevicesLocked() {
    status_t res = scanDirLocked(DEVICE_PATH);
    if(res < 0) {
        ALOGE("scan dir failed for %s\n", DEVICE_PATH);
    }
    if (mDevices.indexOfKey(VIRTUAL_KEYBOARD_ID) < 0) {
        createVirtualKeyboardLocked();
    }
}
           

繼續分析scanDirLocked,代碼實作如下:

status_t EventHub::scanDirLocked(const char *dirname)
{
    char devname[PATH_MAX];
    char *filename;
    DIR *dir;
    struct dirent *de;
    dir = opendir(dirname);
    if(dir == NULL)
        return -1;
    strcpy(devname, dirname);
    filename = devname + strlen(devname);
    *filename++ = '/';
    while((de = readdir(dir))) {
        if(de->d_name[0] == '.' &&
           (de->d_name[1] == '\0' ||
            (de->d_name[1] == '.' && de->d_name[2] == '\0')))
            continue;
        strcpy(filename, de->d_name);
        openDeviceLocked(devname);
    }
    closedir(dir);
    return 0;
}
           

函數調用readdir函數掃描/dev/input下的檔案,然後調用openDeviceLocked打開這些裝置檔案。代碼如下:

status_t EventHub::openDeviceLocked(const char *devicePath) {
     ...
     InputDeviceIdentifier identifier;
 
     // 擷取裝置的名字,如果成功擷取到裝置的名字,把它存入InputDeviceIdentifier中
     if(ioctl(fd, EVIOCGNAME(sizeof(buffer) - 1), &buffer) < 1) {
         //fprintf(stderr, "could not get device name for %s, %s\n", devicePath, strerror(errno));
     } else {
         buffer[sizeof(buffer) - 1] = '\0';
         identifier.name.setTo(buffer);
     }
     ...
 
     //構造EventHub所需要的對象Device,這裡的fd是剛剛打開的裝置的檔案描述符
     int32_t deviceId = mNextDeviceId++;//從這裡可以看出,deviceId是與裝置無關的,和打開順序有關
     Device* device = new Device(fd, deviceId, String8(devicePath), identifier);
 
     // 測試裝置能夠産生的事件的類型,這裡就是Android支援的事件類型,是Kernel的一個子集
     ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(device->keyBitmask)), device->keyBitmask);
     ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(device->absBitmask)), device->absBitmask);
     ioctl(fd, EVIOCGBIT(EV_REL, sizeof(device->relBitmask)), device->relBitmask);
     ioctl(fd, EVIOCGBIT(EV_SW, sizeof(device->swBitmask)), device->swBitmask);
     ioctl(fd, EVIOCGBIT(EV_LED, sizeof(device->ledBitmask)), device->ledBitmask);
     ioctl(fd, EVIOCGBIT(EV_FF, sizeof(device->ffBitmask)), device->ffBitmask);
     ioctl(fd, EVIOCGPROP(sizeof(device->propBitmask)), device->propBitmask);
     ...
     //根據前面擷取到的裝置屬性,檢測裝置是滑鼠,鍵盤,搖桿等,然後把這些資訊繼續存入Device
     if (test_bit(BTN_MOUSE, device->keyBitmask)
             && test_bit(REL_X, device->relBitmask)
             && test_bit(REL_Y, device->relBitmask)) {
         device->classes |= INPUT_DEVICE_CLASS_CURSOR;
     }
     ...
    // Load the key map.
    // We need to do this for joysticks too because the key layout may specify axes.
    status_t keyMapStatus = NAME_NOT_FOUND;
    if (device->classes & (INPUT_DEVICE_CLASS_KEYBOARD | INPUT_DEVICE_CLASS_JOYSTICK)) {
        // Load the keymap for the device.
        //加載kl和kcm檔案
        keyMapStatus = loadKeyMapLocked(device);
    }
    ...
    //将前面打開的裝置的檔案描述符加入到epoll監聽中
    if (epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, &eventItem)) {
        ALOGE("Could not add device fd to epoll instance.  errno=%d", errno);
        delete device;
        return -1;
    }
           

該函數打開掃描到的/dev/input下的裝置檔案,然後擷取該裝置檔案的name,bus,product,vendor,version,location, uniqueId, descriptor以及裝置類型,如是否是鍵盤、虛拟按鍵等。如果是INPUT_DEVICE_CLASS_KEYBOARD 這個類型,則調用loadKeyMapLocked來加載kl檔案。

這裡繼續分析loadKeyMapLocked,代碼如下:

status_t EventHub::loadKeyMapLocked(Device* device) {
    return device->keyMap.load(device->identifier, device->configuration);
}
           

繼續分析load實作,代碼如下:

status_t KeyMap::load(const InputDeviceIdentifier& deviceIdenfifier,
        const PropertyMap* deviceConfiguration) {
    // Use the configured key layout if available.
    if (deviceConfiguration) {
        String8 keyLayoutName;
        if (deviceConfiguration->tryGetProperty(String8("keyboard.layout"),
                keyLayoutName)) {
            status_t status = loadKeyLayout(deviceIdenfifier, keyLayoutName);
            //...
        }

        String8 keyCharacterMapName;
        if (deviceConfiguration->tryGetProperty(String8("keyboard.characterMap"),
                keyCharacterMapName)) {
            status_t status = loadKeyCharacterMap(deviceIdenfifier, keyCharacterMapName);
            //...
        }
		//...
    }
	//...
    // Try searching by device identifier.
    if (probeKeyMap(deviceIdenfifier, String8::empty())) { return OK;}
    if (probeKeyMap(deviceIdenfifier, String8("Generic"))) {return OK;}
    // Try the Virtual key map as a last resort.
    if (probeKeyMap(deviceIdenfifier, String8("Virtual"))) {return OK;}
    return NAME_NOT_FOUND;
}
           

這裡說明下查找邏輯(以上代碼 遞進分析):

  1. configuration不為空,即存在idc檔案,則根據idc檔案中指定的keyboard.layout和keyboard.characterMap來加載指定的kl和kcm檔案 
  2. configuration為空,則根據deviceIdenfifier來加載kl和kcm檔案(這個會根據vendor,product,version三個屬性來決定加載哪個檔案) 
  3. deviceIdenfifier無法找到配置檔案,則根據名字Generic來加載檔案,即Generic.kl和Generic.kcm檔案 
  4. Generic.kl和Generic.kcm無法找到,則根據名字Virtual來加載檔案,即Virtual.kl和Virtual.kcm檔案

這裡對于按鍵類裝置,驅動層有一組 對按鍵的定義,而android在IMS中又有一組對按鍵值的定義,這是為什麼呢?

這是因為Android 系統是建立在Linux核心基礎上的,但如果Linux核心中 輸入事件的定義值發生了變化,android系統也要跟着改,而這樣就很被動,是以android系統采用了配置檔案*.kl 和*.kcm,這樣隻要配置就可以了。而接下來談談kl和kcm檔案的差異:

kl檔案僅僅是值的映射,比如:

# This is an example of a key layout file for basic system controls,
# such as volume and power keys which are typically implemented as GPIO pins
# the device decodes into key presses.

key 114   VOLUME_DOWN
key 115   VOLUME_UP
key 116   POWER
           

像前面114、115這些數字都是在kernel*/include/uapi/linux/input.h中定義的,kernel報上來的鍵值就是114或115,而kl檔案是對這個鍵值的映射,在android系統中代表的是VOLUMEDOWN、VOLUMEUP。

kcm檔案則有規則限制,比如:

key A {
    label:                              'A'
    base:                               'a'
    shift, capslock:                    'A'
    ctrl, alt, meta:                    none
}
           

在上面的例子中,

label

 屬性被配置設定了 

'A'

 行為(驅動中對應行為)。同樣

ctrl

alt

 和 

meta

 屬性同時被配置設定了 

none

 行為。

按鍵聲明包括關鍵字 

key

,後跟 Android 按鍵代碼名稱、左大括号、一組屬性和行為以及一個右大括号。每個按鍵屬性都會建立從按鍵到行為的映射。為了使按鍵字元映射檔案更加緊湊,可以将多個屬性(用逗号分隔)映射到同一個行為。

2.2.2 epoll_wait等待事件簡要說明

從epoll_wait()中得到新的事件後會重新循環,對新事件進行處理,傳回本次getEvents()調用所讀取的事件數量,之後會調用processEventsLocked來處理RawEvent。

2.3 總結

擷取輸入事件的目的:從驅動中将input事件獨取出來轉換成 RawEvent事件,同時對于輸入裝置,抽象成一個EventHub中的Device結構體并存儲。

3 處理事件

processEventsLocked的代碼實作如下:

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;
            }
            //關鍵點1:有輸入裝置産生的事件,在這個方法中處理
            processEventsForDeviceLocked(deviceId, rawEvent, batchSize);
        } else {
            switch (rawEvent->type) {//裝置添加類的事件在這裡處理
            case EventHubInterface::DEVICE_ADDED:
                //這個方法中建立了InputReader所必須的一些資料結構
                //添加裝置
                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;
    }
}
           

3.1 processEventsForDeviceLocked處理裝置的輸入事件,代碼如下:

void InputReader::processEventsForDeviceLocked(int32_t deviceId,
        const RawEvent* rawEvents, size_t count) {
    ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
    //...
    InputDevice* device = mDevices.valueAt(deviceIndex);
    //...
    device->process(rawEvents, count);
}
           

這裡根據deviceId擷取到InputDevice,然後調用InputDevice的process函數

void InputDevice::process(const RawEvent* rawEvents, size_t count) {
    size_t numMappers = mMappers.size();
    for (const RawEvent* rawEvent = rawEvents; count--; rawEvent++) {
        if (mDropUntilNextSync) {
            if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
                mDropUntilNextSync = false;
            } 
        } else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_DROPPED) {
            ALOGI("Detected input event buffer overrun for device %s.", getName().string());
            mDropUntilNextSync = true;
            reset(rawEvent->when);
        } else {
            for (size_t i = 0; i < numMappers; i++) {
                InputMapper* mapper = mMappers[i];
                mapper->process(rawEvent);
            }
        }
    }
}
           

這裡的mMappers成員變量儲存了一系列輸入裝置事件處理對象,是在InputReader類的成員函數createDevice中建立的。這裡查詢每一個InputMapper對象是否要對目前發生的事件進行處理。這裡後面以按鍵處理為例,發生鍵盤事件,真正會對該事件進行處理的隻有KeyboardKeyMapper對象。接下來對KeyboardKeyMapper的process進行分析:

void KeyboardInputMapper::process(const RawEvent* rawEvent) {
    switch (rawEvent->type) {
    case EV_KEY: {
        int32_t scanCode = rawEvent->code;
        int32_t usageCode = mCurrentHidUsage;
        mCurrentHidUsage = 0;

        if (isKeyboardOrGamepadKey(scanCode)) {
            int32_t keyCode;
            uint32_t flags;
            if (getEventHub()->mapKey(getDeviceId(), scanCode, usageCode, &keyCode, &flags)) {
                keyCode = AKEYCODE_UNKNOWN;
                flags = 0;
            }
            processKey(rawEvent->when, rawEvent->value != 0, keyCode, scanCode, flags);
        }
        break;
    }
	//...
    }
}
           

繼續分析processKey,代碼實作如下:

void KeyboardInputMapper::processKey(nsecs_t when, bool down, int32_t keyCode, int32_t scanCode, uint32_t policyFlags) {
    if (down) {
        // 根據螢幕方向的不同調整鍵盤碼
        // Add key down.
        ssize_t keyDownIndex = findKeyDown(scanCode);
        if (keyDownIndex >= 0) {
            // key repeat, be sure to use same keycode as before in case of rotation
        } else {
            // key down
        }
        mDownTime = when;
    } else {
        // Remove key down.
        if (keyDownIndex >= 0) {
            // key up, be sure to use same keycode as before in case of rotation
        } else {
            return;
        }
    }
	//...
    NotifyKeyArgs args(when, getDeviceId(), mSource, policyFlags,
            down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP,
            AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, newMetaState, downTime);
	//說明:這裡getListener是InputReader初始化時傳入的對象,即InputDispatcher
    getListener()->notifyKey(&args);
}
           

通過對getListener()的分析了解到,這裡傳回的是mQueuedListener對象 。(在構造器中定義 mQueuedListener = new QueuedInputListener(listener); 這句話中定義,同時這裡傳遞的listerner便是InputDispatcher)。notifyKey代碼實作如下所示:

void QueuedInputListener::notifyKey(const NotifyKeyArgs* args) {
    mArgsQueue.push(new NotifyKeyArgs(*args));
}
           

3.2 添加/删除裝置

3.2.1 addDeviceLocked添加裝置

void InputReader::addDeviceLocked(nsecs_t when, int32_t deviceId) {
    ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
    //...
    //擷取廠商資訊和裝置類型
    InputDeviceIdentifier identifier = mEventHub->getDeviceIdentifier(deviceId);
    uint32_t classes = mEventHub->getDeviceClasses(deviceId);
    int32_t controllerNumber = mEventHub->getDeviceControllerNumber(deviceId);

    //建立InputDevice
    InputDevice* device = createDeviceLocked(deviceId, controllerNumber, identifier, classes);
    //使用inputReader中儲存的配置資訊對新Inputdevice進行政策配置,執行reset進行裝置重制
    device->configure(when, &mConfig, 0);
    device->reset(when);
    //...
    //添加到清單mDevices中
    mDevices.add(deviceId, device);//
    bumpGenerationLocked();
}
           

這裡繼續分析createDeviceLocked,代碼實作如下:

InputDevice* InputReader::createDeviceLocked(int32_t deviceId, int32_t controllerNumber,
        const InputDeviceIdentifier& identifier, uint32_t classes) {
    InputDevice* device = new InputDevice(&mContext, deviceId, bumpGenerationLocked(),
            controllerNumber, identifier, classes);
    // External devices.
    if (classes & INPUT_DEVICE_CLASS_EXTERNAL) {
        device->setExternal(true);
    }
	//...
    // Keyboard-like devices.
    uint32_t keyboardSource = 0;
    int32_t keyboardType = AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC;
    if (classes & INPUT_DEVICE_CLASS_KEYBOARD) {
        keyboardSource |= AINPUT_SOURCE_KEYBOARD;
    }
    //...
    // Joystick-like devices.
    if (classes & INPUT_DEVICE_CLASS_JOYSTICK) {
        device->addMapper(new JoystickInputMapper(device));
    }
    return device;
}
           

這裡主要建立InputDevice裝置,并根據class給device添加了各種能夠支援的Mapper。最後添加到mDevices的Vector容器中。

3.2.2 removeDeviceLocked删除裝置

void InputReader::removeDeviceLocked(nsecs_t when, int32_t deviceId) {
    InputDevice* device = NULL;
    ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
    //...
    device = mDevices.valueAt(deviceIndex);
    mDevices.removeItemsAt(deviceIndex, 1);
    bumpGenerationLocked();
    //...
    device->reset(when);
    delete device;
}
           

根據傳遞進來的deviceId找到對應的deviceIndex,然後從mDevices的Vector容器中删除該device。(說明:這裡的mDevices是InputDevice類型的,和之前在EventHub中的Device結構體并不相同,EventHub中的Device結構體主要是記錄。這裡的InputDevice主要是封裝了處理事件各種Mapper,便于對輸入事件的處理)。

4 将事件傳遞給InputDispatcher

mQueuedListener.flush的代碼實作如下:

void QueuedInputListener::flush() {
    size_t count = mArgsQueue.size();
    for (size_t i = 0; i < count; i++) {
        NotifyArgs* args = mArgsQueue[i];
        args->notify(mInnerListener);
        delete args;
    }
    mArgsQueue.clear();
}
           

之前處理事件中,執行了mArgsQueue.push操作将NotifyKeyArgs放進Vector中。在flush時直接取出來執行NotifyKeyArgs的notify方法,這裡代碼如下:

void NotifyKeyArgs::notify(const sp<InputListenerInterface>& listener) const {
    listener->notifyKey(this);
}
           

這裡調用傳遞進來的listener執行notifyKey,實際上就是調用了InputDispatcher的notifyKey函數。

代碼實作如下:

void InputDispatcher::notifyKey(const NotifyKeyArgs* args) {
    //validateKeyEvent來判斷是否是有效按鍵事件,按鍵則為Up/Down
    //構造KeyEvent對象
    KeyEvent event;
    event.initialize(args->deviceId, args->source, args->action,
            flags, keyCode, args->scanCode, metaState, 0,
            args->downTime, args->eventTime);
    //最後會調用到java層的PhoneWindowManagerService函數
    mPolicy->interceptKeyBeforeQueueing(&event, /*byref*/ policyFlags);
    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對象
        KeyEntry* newEntry = new KeyEntry(args->eventTime,
                args->deviceId, args->source, policyFlags,
                args->action, flags, keyCode, args->scanCode,
                metaState, repeatCount, args->downTime);
		将輸入事件加入隊列,如果傳回true,則調用後面mLooper.wake函數
        needWake = enqueueInboundEventLocked(newEntry);
        mLock.unlock();
    } // release lock
    if (needWake) {
        //喚醒等待的InputDispatcher,進行輸入事件分發。
        mLooper->wake();
    }
}
           

這裡有2個關鍵點:一個是interceptKeyBeforeQueueing,這裡最終會調用到java層的PhoneWindowManager,而另一個是enqueueInboundEventLocked,将時間隊列加入到mInboundQueue中。

4.1 分析 interceptKeyBeforeQueueing

這裡看實作接口,最終找到NativeInputManager::interceptKeyBeforeQueueing,代碼實作如下:

void NativeInputManager::interceptKeyBeforeQueueing(const KeyEvent* keyEvent,
        uint32_t& policyFlags) {
    if (mInteractive) {
        policyFlags |= POLICY_FLAG_INTERACTIVE;
    }
    if ((policyFlags & POLICY_FLAG_TRUSTED)) {
        nsecs_t when = keyEvent->getEventTime();
        JNIEnv* env = jniEnv();
        jobject keyEventObj = android_view_KeyEvent_fromNative(env, keyEvent);
        jint wmActions;
        if (keyEventObj) {
            wmActions = env->CallIntMethod(mServiceObj,
                    gServiceClassInfo.interceptKeyBeforeQueueing,
                    keyEventObj, policyFlags);
            if (checkAndClearExceptionFromCallback(env, "interceptKeyBeforeQueueing")) {
                wmActions = 0;
            }
            android_view_KeyEvent_recycle(env, keyEventObj);
            env->DeleteLocalRef(keyEventObj);
        } else {
            ALOGE("Failed to obtain key event object for interceptKeyBeforeQueueing.");
            wmActions = 0;
        }

        handleInterceptActions(wmActions, when, /*byref*/ policyFlags);
    } else {
        if (mInteractive) {
            policyFlags |= POLICY_FLAG_PASS_TO_USER;
        }
    }
}
           

這裡會通過JNI機制(對應com_android_server_input_InputManagerService)檔案中回調 java層的PhoneWindowManager裡的同名方法interceptKeyBeforeQueueing,實作如下:

public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
    //...
    int result;
    boolean isWakeKey = (policyFlags & WindowManagerPolicy.FLAG_WAKE) != 0
            || event.isWakeKey();
	//...
    // Handle special keys.
    switch (keyCode) {
        case KeyEvent.KEYCODE_VOLUME_DOWN:
        case KeyEvent.KEYCODE_VOLUME_UP:
        case KeyEvent.KEYCODE_VOLUME_MUTE: {
            //...
            if (down) {
                TelecomManager telecomManager = getTelecommService();
                if (telecomManager != null) {
                    if (telecomManager.isRinging()) {
                        telecomManager.silenceRinger();
                        result &= ~ACTION_PASS_TO_USER;
                        break;
                    }
                    //...
                }
            }
            break;
        }
		//...
    }
	//...
    return result;
}
           

這裡interceptKeyBeforeQueueing中以打電話 按power鍵為例,這裡直接截斷并将result标志位置為非,即不發給使用者層。實際上這裡把三類按鍵(system Key | Global Key | User Key)中前兩項都進行了相應的處理。

4.2 分析enqueueInboundEventLocked

enqueueInboundEventLocked的代碼實作如下:

bool InputDispatcher::enqueueInboundEventLocked(EventEntry* entry) {
    bool needWake = mInboundQueue.isEmpty();
    mInboundQueue.enqueueAtTail(entry);

    switch (entry->type) {
    case EventEntry::TYPE_KEY: {
        KeyEntry* keyEntry = static_cast<KeyEntry*>(entry);
        if (isAppSwitchKeyEventLocked(keyEntry)) {
            if (keyEntry->action == AKEY_EVENT_ACTION_DOWN) {
                mAppSwitchSawKeyDown = true;
            } else if (keyEntry->action == AKEY_EVENT_ACTION_UP) {
                if (mAppSwitchSawKeyDown) {
                    mAppSwitchDueTime = keyEntry->eventTime + APP_SWITCH_TIMEOUT;
                    mAppSwitchSawKeyDown = false;
                    needWake = true;
                }
            }
        }
        break;
    }
	//...這裡忽略Motion,因為情況類似
    }
    return needWake;
}
           

這裡依然以按鍵事件為例,将EventEntry加入到mInboundQueue中,該函數兩種情況下會傳回true:

  1. 加入該鍵盤事件到mInboundQueue隊列之前,mInboundQueue為空,這說明InputDispatcherThread線程在睡眠,等待InputReaderThread線程的喚醒,是以,它傳回true表示要喚醒InputDispatcherThread線程;
  2. 加入該鍵盤事件到mInboundQueue隊列之前,mInboundQueue不為空,但此時使用者按下Home鍵等需要切換APP的按鍵,在切換App時,新的App會把它的鍵盤消息接收通道注冊到InputDispatcher中去,并且會等待InputReader的喚醒,是以,在這種情況下,也需要傳回true,表示要喚醒InputDispatcherThread線程。

如果不是這兩種情況,那麼就說明InputDispatcherThread線程現在正在處理前面的鍵盤事件,不需要被喚醒。