欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Input系統(tǒng)之InputReader處理按鍵事件詳解

 更新時(shí)間:2022年11月23日 09:20:55   作者:大胃粥  
這篇文章主要為大家介紹了Input系統(tǒng)之InputReader處理按鍵事件詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

前言

前面幾篇文章已經(jīng)為 Input 系統(tǒng)的分析打好了基礎(chǔ),現(xiàn)在是時(shí)候進(jìn)行更深入的分析了。

通常,手機(jī)是不帶鍵盤的,但是手機(jī)上仍然有按鍵,就是我們經(jīng)常使用的電源鍵以及音量鍵。因此還是有必要分析按鍵事件的處理流程。

那么,掌握按鍵事件的處理流程,對我們有什么用處呢?例如,手機(jī)上添加了一個(gè)功能按鍵,你知道如何把這個(gè)物理按鍵映射到上層,然后處理這個(gè)按鍵嗎?又例如,如果設(shè)備是不需要電源鍵,但是系統(tǒng)默認(rèn)把某一個(gè)按鍵映射為電源鍵,那么我們?nèi)绾问惯@個(gè)按鍵不成為電源鍵呢?所有這一切,都與按鍵事件的處理流程相關(guān)。

認(rèn)識按鍵事件

很多讀者可能還不知道按鍵事件到底長什么樣,通過 adb shell getevent 可以獲取輸入設(shè)備產(chǎn)生的元輸入事件,按鍵事件當(dāng)然也包括在內(nèi)。

當(dāng)按下電源鍵,可以產(chǎn)生如下的按鍵事件

/dev/input/event0: 0001 0074 00000001
/dev/input/event0: 0000 0000 00000000
/dev/input/event0: 0001 0074 00000000
/dev/input/event0: 0000 0000 00000000

/dev/input/event0 是內(nèi)核為輸入設(shè)備生成的設(shè)備文件,它代表一個(gè)輸入設(shè)備,它后面的數(shù)據(jù)格式為 type code value。

以第一行為例,幾個(gè)數(shù)據(jù)的含義如下

  • 0001 代表輸入設(shè)備產(chǎn)生事件的類型。此時(shí)電源鍵產(chǎn)生的是一個(gè)按鍵類型事件,而如果手指在觸摸設(shè)備上滑動(dòng),產(chǎn)生是一個(gè)坐標(biāo)類型事件。
  • 0074 表示按鍵的掃描碼(code),這個(gè)掃描碼是與輸入設(shè)備相關(guān),因此不同的設(shè)備上的電源鍵,產(chǎn)生的掃描碼可能不同。
  • 00000001 表示按鍵值(value)。 00000001 表示按鍵被按下,00000000 表示按鍵抬起。

因此,一個(gè)輸入設(shè)備產(chǎn)生的事件,可以通過 type + code + value 的形式表示。

注意,以上這些數(shù)據(jù)都是元輸入事件的數(shù)據(jù),是由內(nèi)核直接為輸入設(shè)備生成的數(shù)據(jù),因此讀起來沒那么直觀。我們可以通過 adb shell getevent -l 顯示輸入系統(tǒng)對這些數(shù)據(jù)的分析結(jié)果,當(dāng)按下電源鍵,會出現(xiàn)如下結(jié)果

/dev/input/event0: EV_KEY       KEY_POWER            DOWN     
/dev/input/event0: EV_SYN       SYN_REPORT           00000000 
/dev/input/event0: EV_KEY       KEY_POWER            UP
/dev/input/event0: EV_SYN       SYN_REPORT           00000000

第一行數(shù)據(jù),EV_KEY + KEY_POWER + DOWN 表示電源鍵按下。

第三行數(shù)據(jù),EV_KEY + KEY_POWER + UP 表示電源鍵抬起。

第二行和第四行的數(shù)據(jù),EV_SYN + SYN_REPORT 表示之前的一個(gè)事件的數(shù)據(jù)已經(jīng)發(fā)送完畢,需要系統(tǒng)同步之前一個(gè)事件的所有數(shù)據(jù)。對于一個(gè)按鍵,一個(gè)事件只有一條數(shù)據(jù),然而對于觸摸板上的滑動(dòng)事件,例如手指按下事件,它的數(shù)據(jù)可不止一條,我們將在后面的文章中看到。

好,既然已經(jīng)以按鍵事件的數(shù)據(jù)有了基本的認(rèn)識,那么接下來開始著手分析按鍵事件的處理流程。

處理按鍵事件

從前面文章可知,InputReader 從 EventHub 中獲取了輸入事件的數(shù)據(jù),然后調(diào)用如下函數(shù)進(jìn)行處理

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;
            // 獲取同一個(gè)設(shè)備的元輸入事件的數(shù)量
            while (batchSize < count) {
                if (rawEvent[batchSize].type >= EventHubInterface::FIRST_SYNTHETIC_EVENT ||
                    rawEvent[batchSize].deviceId != deviceId) {
                    break;
                }
                batchSize += 1;
            }
            // 批量處理同一個(gè)設(shè)備的元輸入事件
            processEventsForDeviceLocked(deviceId, rawEvent, batchSize);
        } else {
            // ...
        }
        count -= batchSize;
        rawEvent += batchSize;
    }
}
void InputReader::processEventsForDeviceLocked(int32_t eventHubId, const RawEvent* rawEvents,
                                               size_t count) {
    auto deviceIt = mDevices.find(eventHubId);
    if (deviceIt == mDevices.end()) {
        return;
    }
    std::shared_ptr<InputDevice>& device = deviceIt->second;
    // 如果 InputDevice 沒有 InputMapper,那么它不能處理事件
    if (device->isIgnored()) {
        return;
    }
    // InputDevice 批量處理元輸入事件
    device->process(rawEvents, count);
}

InputReader 首先找到屬于同一個(gè)設(shè)備的多個(gè)事件,然后交給 InputDevice 進(jìn)行批量處理

// frameworks/native/services/inputflinger/reader/InputDevice.cpp
void InputDevice::process(const RawEvent* rawEvents, size_t count) {
    // 雖然 InputReader 把批量的事件交給 InputDevice,但是 InputDevice 還是逐個(gè)處理事件
    for (const RawEvent* rawEvent = rawEvents; count != 0; rawEvent++) {
        if (mDropUntilNextSync) {
            if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
                mDropUntilNextSync = false;
            } 
        } else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_DROPPED) {
            // EV_SYN + SYN_DROPPED 表明要丟棄后面的事件,直到 EV_SYN + SYN_REPORT
            mDropUntilNextSync = true;
            reset(rawEvent->when);
        } else {
            // 每一個(gè)事件交給 InputMapper 處理
            for_each_mapper_in_subdevice(rawEvent->deviceId, [rawEvent](InputMapper& mapper) {
                mapper.process(rawEvent);
            });
        }
        --count;
    }
}

雖然 InputReader 把事件交給 InputDevice 進(jìn)行批量處理,但是 InputDevice 是逐個(gè)把事件交給它的 InputMapper 處理。

對于鍵盤類型的輸入設(shè)備,它的 InputMapper 實(shí)現(xiàn)類為 KeyboardInputMapper,它對按鍵事件處理如下

void KeyboardInputMapper::process(const RawEvent* rawEvent) {
    switch (rawEvent->type) {
        // 處理 EV_KEY
        case EV_KEY: {
            int32_t scanCode = rawEvent->code;
            int32_t usageCode = mCurrentHidUsage;
            mCurrentHidUsage = 0;
            if (isKeyboardOrGamepadKey(scanCode)) {
                // 注意第三個(gè)參數(shù),value等于0,才表示按鍵down, 也就是說,value 為1, 表示按鍵被按下
                processKey(rawEvent->when, rawEvent->readTime, rawEvent->value != 0, scanCode,
                           usageCode);
            }
            break;
        }
        case EV_MSC: {
            // ...
        }
        // 處理 EV_SYN + SYN_REPORT
        case EV_SYN: {
            if (rawEvent->code == SYN_REPORT) {
                mCurrentHidUsage = 0;
            }
        }
    }
}

其中,我們只需要關(guān)心類型為 EV_KEY 類型的事件,它表示一個(gè)按鍵事件,它的處理如下

void KeyboardInputMapper::processKey(nsecs_t when, nsecs_t readTime, bool down, int32_t scanCode,
                                     int32_t usageCode) {
    int32_t keyCode;
    int32_t keyMetaState;
    uint32_t policyFlags;
    // 1. 根據(jù)鍵盤配置文件,把 scanCode 轉(zhuǎn)化為 keycode,并獲取 flags
    if (getDeviceContext().mapKey(scanCode, usageCode, mMetaState, &keyCode, &keyMetaState,
                                  &policyFlags)) {
        keyCode = AKEYCODE_UNKNOWN;
        keyMetaState = mMetaState;
        policyFlags = 0;
    }
    // 按下
    if (down) {
        // 根據(jù)屏幕方向,再次轉(zhuǎn)換 keyCode
        // Rotate key codes according to orientation if needed.
        if (mParameters.orientationAware) {
            keyCode = rotateKeyCode(keyCode, getOrientation());
        }
        // 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
            keyCode = mKeyDowns[keyDownIndex].keyCode;
        } else {
            // key down
            if ((policyFlags & POLICY_FLAG_VIRTUAL) &&
                getContext()->shouldDropVirtualKey(when, keyCode, scanCode)) {
                return;
            }
            if (policyFlags & POLICY_FLAG_GESTURE) {
                // 如果設(shè)備通知支持觸摸,那么發(fā)送一個(gè) ACTION_CANCEL 事件
                getDeviceContext().cancelTouch(when, readTime);
            }
            KeyDown keyDown;
            keyDown.keyCode = keyCode;
            keyDown.scanCode = scanCode;
            // 保存按下的按鍵
            mKeyDowns.push_back(keyDown);
        }
        mDownTime = when;
    } else { // 抬起按鍵
        // Remove key down.
        ssize_t keyDownIndex = findKeyDown(scanCode);
        if (keyDownIndex >= 0) {
            // key up, be sure to use same keycode as before in case of rotation
            keyCode = mKeyDowns[keyDownIndex].keyCode;
            // 移除
            mKeyDowns.erase(mKeyDowns.begin() + (size_t)keyDownIndex);
        } else {
            // key was not actually down
            ALOGI("Dropping key up from device %s because the key was not down.  "
                  "keyCode=%d, scanCode=%d",
                  getDeviceName().c_str(), keyCode, scanCode);
            return;
        }
    }
    // 更新meta狀態(tài)
    if (updateMetaStateIfNeeded(keyCode, down)) {
        // If global meta state changed send it along with the key.
        // If it has not changed then we'll use what keymap gave us,
        // since key replacement logic might temporarily reset a few
        // meta bits for given key.
        keyMetaState = mMetaState;
    }
    nsecs_t downTime = mDownTime;
    // 外部設(shè)備的按鍵按下時(shí),添加喚醒標(biāo)志位
    if (down && getDeviceContext().isExternal() && !mParameters.doNotWakeByDefault &&
        !isMediaKey(keyCode)) {
        policyFlags |= POLICY_FLAG_WAKE;
    }
    // 設(shè)備是否能生成重復(fù)按鍵事件,一般設(shè)備都不支持這個(gè)功能
    // 而是由系統(tǒng)模擬生成重復(fù)按鍵事件
    if (mParameters.handlesKeyRepeat) {
        policyFlags |= POLICY_FLAG_DISABLE_KEY_REPEAT;
    }
    // 2. 生成 NotifyKeyArgs, 并加到 QueuedInputListener 隊(duì)列中
    NotifyKeyArgs args(getContext()->getNextId(), when, readTime, getDeviceId(), mSource,
                       getDisplayId(), policyFlags,
                       down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP/*action*/,
                       AKEY_EVENT_FLAG_FROM_SYSTEM/*flags*/, keyCode, scanCode, keyMetaState, downTime);
    getListener()->notifyKey(&args);
}

我現(xiàn)在只關(guān)心手機(jī)上電源鍵以及音量鍵的處理流程,因此這里的處理過程主要分為兩步

  • 根據(jù)按鍵配置文件,把掃描碼(scan code)轉(zhuǎn)換為按鍵碼(key code),并從配置文件中獲取策略標(biāo)志位(policy flag)。不同的輸入設(shè)備的同一種功能的按鍵,例如電源鍵,產(chǎn)生的掃描碼不一定都相同,Android 系統(tǒng)需要把掃描碼映射為同一個(gè)按鍵碼進(jìn)行處理。
  • 創(chuàng)建一個(gè)事件 NotifyKeyArgs,并加入到 QueuedInputListener 隊(duì)列中。從前面的文章可知,當(dāng) InputReader 處理完從 EventHub 讀到的事件后,會刷新這個(gè)隊(duì)列,從而把事件發(fā)送給 InputClassifier。而對于按鍵事件,InputClassifier 不做任何加工,直接把事件傳遞給 InputDispatcher。

現(xiàn)在只要知道如何把一個(gè)按鍵的掃描碼映射為按鍵碼,InputReader 處理按鍵事件的整個(gè)流程都一清二楚了。

掃描碼映射按鍵碼

status_t EventHub::mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode, int32_t metaState,
                          int32_t* outKeycode, int32_t* outMetaState, uint32_t* outFlags) const {
    std::scoped_lock _l(mLock);
    Device* device = getDeviceLocked(deviceId);
    status_t status = NAME_NOT_FOUND;
    if (device != nullptr) {
        // Check the key character map first.
        const std::shared_ptr<KeyCharacterMap> kcm = device->getKeyCharacterMap();
        if (kcm) {
            // 1. KeyCharacterMapFile :轉(zhuǎn)換 scanCode 為 keyCode
            if (!kcm->mapKey(scanCode, usageCode, outKeycode)) {
                *outFlags = 0;
                status = NO_ERROR;
            }
        }
        // Check the key layout next.
        if (status != NO_ERROR && device->keyMap.haveKeyLayout()) {
            // 2. KeyLayoutFile: 把 scanCode 轉(zhuǎn)換為 keycode
            if (!device->keyMap.keyLayoutMap->mapKey(scanCode, usageCode, outKeycode, outFlags)) {
                status = NO_ERROR;
            }
        }
        if (status == NO_ERROR) {
            if (kcm) {
                // 3. KeyCharacterMapFile: 根據(jù)meta按鍵狀態(tài),重新映射按鍵字符
                kcm->tryRemapKey(*outKeycode, metaState, outKeycode, outMetaState);
            } else {
                *outMetaState = metaState;
            }
        }
    }
    if (status != NO_ERROR) {
        *outKeycode = 0;
        *outFlags = 0;
        *outMetaState = metaState;
    }
    return status;
}

掃描碼轉(zhuǎn)化為按鍵碼的過程有點(diǎn)小復(fù)雜

  • 首先根據(jù) kcm(key character map) 文件進(jìn)行轉(zhuǎn)換。
  • 如果第一步失敗,那么根據(jù) kl(key layout) 文件進(jìn)行轉(zhuǎn)換。
  • 如果前兩步,有一個(gè)成功,那么再根據(jù)meta按鍵狀態(tài),重新使用 kcm 文件對按鍵碼再次進(jìn)行轉(zhuǎn)換。這個(gè)只對鍵盤起作用,例如按下 shift ,再按字母鍵,那么會產(chǎn)生大寫的字母的按鍵碼。而對于電源鍵和音量鍵,此步驟可以忽略。

可以發(fā)現(xiàn),kcm 和 kl 文件都可以把按鍵的掃描碼進(jìn)行轉(zhuǎn)換為按鍵碼,然而 kcm 文件一般都只是針對鍵盤按鍵,而對于電源鍵和音量鍵,一般都是通過 kl 文件進(jìn)行轉(zhuǎn)換的。

那么如何找到輸入設(shè)備的 kl 文件呢?前面通過 adb shell getevent 可以發(fā)現(xiàn)輸入設(shè)備的文件節(jié)點(diǎn)為 /dev/input/event0,再通過 adb shell dumpsys input 導(dǎo)出所有設(shè)備的信息,就可以找到電源鍵屬于哪個(gè)設(shè)備,以及設(shè)備的的按鍵配置文件

    6: qpnp_pon
      Classes: KEYBOARD
      Path: /dev/input/event0
      Enabled: true
      Descriptor: fb60d4f4370f5dbe8267b63d38dea852987571ab
      Location: qpnp_pon/input0
      ControllerNumber: 0
      UniqueId: 
      Identifier: bus=0x0000, vendor=0x0000, product=0x0000, version=0x0000
      KeyLayoutFile: /system/usr/keylayout/Generic.kl
      KeyCharacterMapFile: /system/usr/keychars/Generic.kcm
      ConfigurationFile: 
      VideoDevice: <none>

從這個(gè)信息就可以看出,輸入設(shè)備的 kl 文件為 /system/usr/keylayout/Generic.kl,它的電源鍵映射如下

key 116   POWER

其中,116是十進(jìn)制,它的十六進(jìn)制為 0x74,正好就是 adb shell getevent 顯示的電源按鍵的掃描碼。

POWER 就是被映射成的按鍵碼,但是它是一個(gè)字符,而實(shí)際使用的是 int 類型,這個(gè)關(guān)系的映射是在下面定義的

// frameworks/native/include/android/keycodes.h
/**
 * Key codes.
 */
enum {
AKEYCODE_POWER = 26,
}

因此,電源按鍵的掃描碼 0x74,被映射為按鍵碼 26,正好就是上層 KeyEvent.java 定義的電源按鍵值

// frameworks/base/core/java/android/view/KeyEvent.java
public static final int KEYCODE_POWER           = 26;

在很早的 Android 版本上,配置文件中,電源鍵還會定義一個(gè)策略標(biāo)志位,如下

key 116   POWER WAKE

其中,WAKE 就是一個(gè)策略標(biāo)志位,在把掃描轉(zhuǎn)換為按鍵碼時(shí),這個(gè)策略標(biāo)志位也會被解析,它表示需要喚醒設(shè)備,上層會根據(jù)這個(gè)標(biāo)志位,讓設(shè)備喚醒。

結(jié)束

InputReader 處理按鍵事件的過程其實(shí)很簡單,就是把按鍵事件交給 KeyboardInputMapper 處理,KeyboardInputMapper 根據(jù)配置文件,把按鍵的掃描碼轉(zhuǎn)換為按鍵碼,并同時(shí)從配置文件中獲取策略標(biāo)志位,然后把這些信息包裝成一個(gè)事件,發(fā)送到下一環(huán)。

現(xiàn)在,如果項(xiàng)目上讓你完成功能按鍵的映射,或者解除某個(gè)按鍵的電源功能,你會了嗎?

以上就是Input系統(tǒng)之InputReader處理按鍵事件詳解的詳細(xì)內(nèi)容,更多關(guān)于InputReader處理按鍵事件的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Android打開WebView黑屏閃爍問題排查

    Android打開WebView黑屏閃爍問題排查

    這篇文章主要介紹了Android打開WebView黑屏閃爍問題排查,文章通過詳細(xì)的代碼示例和圖文介紹WebView黑屏閃爍的問題,感興趣的小伙伴可以跟著小編一起來學(xué)習(xí)
    2023-05-05
  • 去掉activity默認(rèn)動(dòng)畫效果的簡單方法

    去掉activity默認(rèn)動(dòng)畫效果的簡單方法

    下面小編就為大家?guī)硪黄サ鬭ctivity默認(rèn)動(dòng)畫效果的簡單方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2016-12-12
  • Android?搜索框架使用詳解

    Android?搜索框架使用詳解

    這篇文章主要為大家介紹了Android?搜索框架使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-11-11
  • Android繪圖常用方法匯總

    Android繪圖常用方法匯總

    這篇文章主要為大家總結(jié)了Android繪圖的常用方法,具有一定的參考價(jià)值,感興趣的朋友可以參考一下
    2016-05-05
  • Android開發(fā)之搜索框SearchView用法示例

    Android開發(fā)之搜索框SearchView用法示例

    這篇文章主要介紹了Android開發(fā)之搜索框SearchView用法,結(jié)合實(shí)例形式分析了Android搜索框SearchView的基本功能、用法及相關(guān)操作注意事項(xiàng),需要的朋友可以參考下
    2019-03-03
  • Android開發(fā)服務(wù)Service全面講解

    Android開發(fā)服務(wù)Service全面講解

    Android的服務(wù)是開發(fā)Android應(yīng)用程序的重要組成部分。不同于活動(dòng)Activity,服務(wù)是在后臺運(yùn)行,服務(wù)沒有接口,生命周期也與活動(dòng)Activity非常不同。通過使用服務(wù)我們可以實(shí)現(xiàn)一些后臺操作,比如想從遠(yuǎn)程服務(wù)器加載一個(gè)網(wǎng)頁等,下面來看看詳細(xì)內(nèi)容,需要的朋友可以參考下
    2023-02-02
  • Android 開發(fā)實(shí)例簡單涂鴉板

    Android 開發(fā)實(shí)例簡單涂鴉板

    本文主要介紹 Android 簡單涂鴉板,這里提供了代碼示例和實(shí)現(xiàn)效果圖,有興趣的小伙伴可以參考下
    2016-08-08
  • Android PickerView底部選擇框?qū)崿F(xiàn)流程詳解

    Android PickerView底部選擇框?qū)崿F(xiàn)流程詳解

    本次主要介紹Android中底部彈出框的使用,使用兩個(gè)案例來說明,首先是時(shí)間選擇器,然后是自定義底部彈出框的選擇器,以下來一一說明他們的使用方法
    2022-09-09
  • Android動(dòng)態(tài)更新Menu菜單的實(shí)現(xiàn)過程

    Android動(dòng)態(tài)更新Menu菜單的實(shí)現(xiàn)過程

    菜單是用戶界面中最常見的元素之一,使用非常頻繁,下面這篇文章主要給大家介紹了關(guān)于Android動(dòng)態(tài)更新Menu菜單的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-09-09
  • Android?Studio實(shí)現(xiàn)簡易計(jì)算器設(shè)計(jì)

    Android?Studio實(shí)現(xiàn)簡易計(jì)算器設(shè)計(jì)

    這篇文章主要為大家詳細(xì)介紹了Android?Studio實(shí)現(xiàn)簡易計(jì)算器設(shè)計(jì),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-05-05

最新評論