IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android input 原理分析(三) _ scanCode与keyCode映射 -> 正文阅读

[移动开发]Android input 原理分析(三) _ scanCode与keyCode映射

《input 原理分析(二)》中得知,在scan device 会对/dev/input 下每个input device 通过openDeviceLocked 进行初始化等操作,其中针对keyboard 或joystick 设备需要进行scan code 与 keycode 映射。这一篇详细解析映射过程,后面InputReader 进行分发按键会在InputDevice 的KeyBoardMapper 中需要将scan code 转换为keycode 发送给Android 系统。

0. 加载key map

frameworks\native\services\inputflinger\reader\EventHub.cpp

status_t EventHub::openDeviceLocked(const char* devicePath) {
    ...
    // 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.
        keyMapStatus = loadKeyMapLocked(device);
    }
    ...
}

1. loadKeyMapLocked

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

device 在openDeviceLocked 中创建,而keyMap 为Device 的内部成员变量。

    struct Device {
        ...
        KeyMap keyMap;
        ...
    }

2. KeyMap::load

/frameworks/native/libs/input/Keyboard.cpp

status_t KeyMap::load(const InputDeviceIdentifier& deviceIdentifier,
        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(deviceIdentifier, keyLayoutName.c_str());
            if (status == NAME_NOT_FOUND) {
                ALOGE("Configuration for keyboard device '%s' requested keyboard layout '%s' but "
                        "it was not found.",
                        deviceIdentifier.name.c_str(), keyLayoutName.string());
            }
        }

        String8 keyCharacterMapName;
        if (deviceConfiguration->tryGetProperty(String8("keyboard.characterMap"),
                keyCharacterMapName)) {
            status_t status = loadKeyCharacterMap(deviceIdentifier, keyCharacterMapName.c_str());
            if (status == NAME_NOT_FOUND) {
                ALOGE("Configuration for keyboard device '%s' requested keyboard character "
                        "map '%s' but it was not found.",
                        deviceIdentifier.name.c_str(), keyCharacterMapName.string());
            }
        }

        if (isComplete()) {
            return OK;
        }
    }

    // Try searching by device identifier. 通过设备信息查找对应的映射表
    if (probeKeyMap(deviceIdentifier, "")) {
        return OK;
    }

    // Fall back on the Generic key map.
    // TODO Apply some additional heuristics here to figure out what kind of
    //      generic key map to use (US English, etc.) for typical external keyboards. 查找通用的映射表
    if (probeKeyMap(deviceIdentifier, "Generic")) {
        return OK;
    }

    // Try the Virtual key map as a last resort.  查找虚拟映射表
    if (probeKeyMap(deviceIdentifier, "Virtual")) {
        return OK;
    }

    // Give up!
    ALOGE("Could not determine key map for device '%s' and no default key maps were found!",
            deviceIdentifier.name.c_str());
    return NAME_NOT_FOUND;
}

可能有三次解析Key map:

  • 根据设备信息解析,例如vendor、product 等;
  • 解析Generic 映射表;
  • 解析Virtual 映射表;

至于解析一次,需要根据probeKeyMap() 的返回值确定,如果keylayout 和keyCharacterMap 都解析完,标志probe 完成,详细看probeKeyMap 函数。

2.1 probeKeyMap()

bool KeyMap::probeKeyMap(const InputDeviceIdentifier& deviceIdentifier,
        const std::string& keyMapName) {
    if (!haveKeyLayout()) {
        loadKeyLayout(deviceIdentifier, keyMapName);
    }
    if (!haveKeyCharacterMap()) {
        loadKeyCharacterMap(deviceIdentifier, keyMapName);
    }
    return isComplete();
}

2.1.1 loadKeyLayout()

status_t KeyMap::loadKeyLayout(const InputDeviceIdentifier& deviceIdentifier,
        const std::string& name) {
    std::string path(getPath(deviceIdentifier, name,
            INPUT_DEVICE_CONFIGURATION_FILE_TYPE_KEY_LAYOUT));
    if (path.empty()) {
        return NAME_NOT_FOUND;
    }

    status_t status = KeyLayoutMap::load(path, &keyLayoutMap);
    if (status) {
        return status;
    }

    keyLayoutFile = path;
    return OK;
}

getPath() 这里不做详细解析,主要功能:

  • 如果name 为空,则通过设备的vendor、product、version 组成一个kl 文件名,如果没有version,则只需要vendor、product 组成一个kl文件名;
  • 如果name 不为空,则通过name 组成一个kl 文件名;
  • kl 的路径于/odm/usr/keylayout、/vendor/usr/keylayout、/system/usr/keylayout、/data/system/devices/ 中其中一个;

最终的kl 完整名称可能为:/system/usr/keylayout/Vendor_0079_Product_0011.kl 或 /system/usr/keylayout/Generic.kl

-->

重点来分析KeyLayoutMap::load()

frameworks\native\libs\input\KeyLayoutMap.cpp

status_t KeyLayoutMap::load(const std::string& filename, sp<KeyLayoutMap>* outMap) {
    outMap->clear();

    Tokenizer* tokenizer;
    status_t status = Tokenizer::open(String8(filename.c_str()), &tokenizer);
    if (status) {
        ...
    } else {
        sp<KeyLayoutMap> map = new KeyLayoutMap();
        if (!map.get()) {
            ...
        } else {
            Parser parser(map.get(), tokenizer);
            status = parser.parse();
            ...
        }
        delete tokenizer;
    }
    return status;
}

-->

继续分析parse()

status_t KeyLayoutMap::Parser::parse() {
    while (!mTokenizer->isEof()) {
        ...

        if (!mTokenizer->isEol() && mTokenizer->peekChar() != '#') {
            String8 keywordToken = mTokenizer->nextToken(WHITESPACE);
            if (keywordToken == "key") {
                mTokenizer->skipDelimiters(WHITESPACE);
                status_t status = parseKey();
                if (status) return status;
            } else if (keywordToken == "axis") {
                ...
            } else if (keywordToken == "led") {
                ...
            } else {
                ...
            }

            ...

        mTokenizer->nextLine();
    }
    return NO_ERROR;
}

-->

继续分析parseKey()

status_t KeyLayoutMap::Parser::parseKey() {
    ...
    char* end;
    int32_t code = int32_t(strtol(codeToken.string(), &end, 0));
    ...
    int32_t keyCode = getKeyCodeByLabel(keyCodeToken.string());
    if (!keyCode) {
        ALOGE("%s: Expected key code label, got '%s'.", mTokenizer->getLocation().string(),
                keyCodeToken.string());
        return BAD_VALUE;
    }
    ...

    Key key;
    key.keyCode = keyCode;
    key.flags = flags;
    map.add(code, key);
    return NO_ERROR;
}

这里以Generic.kl 为例,kl 文件内容大概为:(详细的kl 讲解可以参考:https://source.android.google.cn/devices/input/key-layout-files?hl=zh-cn

key 1     ESCAPE
key 2     1
key 3     2
...
key 113   VOLUME_MUTE
key 114   VOLUME_DOWN
...
key 172   HOME
...

解析tokenstring 为key ,进入parseKey,紧接着解析scancode,再是keycodeToken,这里通过函数getKeyCodeByLabel 解析keyCodeToken 对应的keycode,最终将scancode 和keycode以映射的关系存入map 中。

-->

继续分析getKeyCodeByLabel()

frameworks\native\include\input\InputEventLabels.h

static inline int32_t getKeyCodeByLabel(const char* label) {
    return int32_t(lookupValueByLabel(label, KEYCODES));
}

这里KEYCODES 为InputEventLable 数组,通过label 转换为AKEYCODE_开头的key:

#define DEFINE_KEYCODE(key) { #key, AKEYCODE_##key }

struct InputEventLabel {
    const char *literal;
    int value;
};
static const InputEventLabel KEYCODES[] = {

    DEFINE_KEYCODE(UNKNOWN),
    DEFINE_KEYCODE(SOFT_LEFT),
    DEFINE_KEYCODE(SOFT_RIGHT),
    DEFINE_KEYCODE(HOME),
    DEFINE_KEYCODE(BACK),
    DEFINE_KEYCODE(CALL),
    DEFINE_KEYCODE(ENDCALL),
    ...

也就是最后通过label 获得将是AKEYCODE_*

例如,kl 文件中home 键描述为:

key 172   HOME

scan code 172 标记为HOME,keycodeToken 解析为HOME,根据这个label 带入到KEYCODES 数组中得到的keycode 为AKEYCODE_HOME

所有AKEYCODE_HOME 定义在 frameworks/native/include/android/keycodes.h:

enum {
    AKEYCODE_UNKNOWN         = 0,
    AKEYCODE_SOFT_LEFT       = 1,
    AKEYCODE_SOFT_RIGHT      = 2,
    AKEYCODE_HOME            = 3,
    AKEYCODE_BACK            = 4,
    AKEYCODE_CALL            = 5,
    ...

而这里的值只是在native 中传送,可以dispatch 到JAVA,其实JAVA 层KeyEvent.java 中做了对应,名称为KEYCODE_*:

public class KeyEvent extends InputEvent implements Parcelable {
    public static final int KEYCODE_UNKNOWN         = 0;
    public static final int KEYCODE_SOFT_LEFT       = 1;
    public static final int KEYCODE_SOFT_RIGHT      = 2;
    public static final int KEYCODE_HOME            = 3;
    public static final int KEYCODE_BACK            = 4;
    public static final int KEYCODE_CALL            = 5;
    ...

至此scan code 与keycode 的映射部分基本完成,下面通过时序图描述调用过程:

?

2.1.2 loadKeyCharacterMap

关于input 的kcm 暂时不做分析,详细可以参考:https://source.android.google.cn/devices/input/key-character-map-files?hl=zh-cn

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-07-24 11:36:36  更:2021-07-24 11:37:35 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/1 21:25:05-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码