上一篇文章看了Input系统首次启动时会扫描/dev/input目录,并且会通过函数loadKeyLayout
和loadKeyCharacterMap
加载和解析".kl",".kcm"文件,这两个文件的解析规则对应熟悉Input系统还是比较重要的,上一篇文章限于篇幅只看了加载过程,本篇我们就来分析一下其解析过程,还是先将loadKeyLayout
函数贴出来看看:
KeyMap::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
会得到".kl"的路径,KeyLayoutMap::load
函数拿到路径之后进行解析,并且会为每一个解析的".kl"文件构造一个KeyLayoutMap
(描述scancdoe到Android keycode的映射):
KeyLayoutMap::load
status_t KeyLayoutMap::load(const std::string& filename, sp<KeyLayoutMap>* outMap) {
outMap->clear();
Tokenizer* tokenizer;
//打开".kl"文件
status_t status = Tokenizer::open(String8(filename.c_str()), &tokenizer);
if (status) {
ALOGE("Error %d opening key layout map file %s.", status, filename.c_str());
} else {
sp<KeyLayoutMap> map = new KeyLayoutMap();
if (!map.get()) {
ALOGE("Error allocating key layout map.");
status = NO_MEMORY;
} else {
//解析".kl"
Parser parser(map.get(), tokenizer);
status = parser.parse();
if (!status) {
*outMap = map;
}
}
delete tokenizer;
}
return status;
}
此函数中首先通过Tokenizer
打开".kl"文件,得到一个Tokenizer
,这个类主要作用就是逐行解析文本文件,接着如果成功打开了".kl"文件会创建一个KeyLayoutMap
对象,接着使用KeyLayoutMap
和Tokenizer
又构造了一个Parser
,一看名字就知道这个对象就是解析".kl"文件的核心,其构造函数如下:
// --- KeyLayoutMap::Parser ---
KeyLayoutMap::Parser::Parser(KeyLayoutMap* map, Tokenizer* tokenizer) :
mMap(map), mTokenizer(tokenizer) {
}
接下来我们重点进入Parser
的parse()
函数,在看parse()
函数之前我们这里先来看看parse()
函数用到的Tokenizer
的几个处理文本文件的函数作用:
isEof()函数:
/**
* 返回当前读取到的字符是否是文本结尾
*/
inline bool isEof() const { return mCurrent == getEnd(); }
skipDelimiters(const char* delimiters))函数:
/**
* 跳过该行中指定的字符和空格.
*/
void skipDelimiters(const char* delimiters);
isEol()函数:
/**
* 返回当前读取到的字符是否是文本结尾或者是否是行尾换行
*/
inline bool isEol() const { return isEof() || *mCurrent == '\n'; }
peekChar()函数:
/**
*获取当前位置的字符,文末返回空
*/
inline char peekChar() const { return isEof() ? '\0' : *mCurrent; }
nextToken()函数:
/**
* 在该行中查找指定字符的位置,并返回其位置之前的所有字符
*/
String8 nextToken(const char* delimiters);
了解了这几个函数的作用之后再来看Parser::parse
就很简单了:
Parser::parse
status_t KeyLayoutMap::Parser::parse() {
//如果没有解析到".kl"文件末尾,则while循环不结束
while (!mTokenizer->isEof()) {
//跳过指定字符WHITESPACE,WHITESPACE = " \t\r";
//即跳过空格,tab键和回车,
mTokenizer->skipDelimiters(WHITESPACE);
//isEol和isEof的区别是,换行isEol也会返回true,peekChar作用是
//如果没到文本结尾,则返回当前读取到的字符,这里即跳过"#"开头的行(注释)
if (!mTokenizer->isEol() && mTokenizer->peekChar() != '#') {
//找到该行中,空格,tab键或者回车之前的所有字符,从".kl"文件的格式来看,
//这里的作用就是查找该行行首的字符,有三种,"key","axis","led"
String8 keywordToken = mTokenizer->nextToken(WHITESPACE);
//三种情况三个分支分别处理
if (keywordToken == "key") {
mTokenizer->skipDelimiters(WHITESPACE);
status_t status = parseKey();
if (status) return status;
} else if (keywordToken == "axis") {
mTokenizer->skipDelimiters(WHITESPACE);
status_t status = parseAxis();
if (status) return status;
} else if (keywordToken == "led") {
mTokenizer->skipDelimiters(WHITESPACE);
status_t status = parseLed();
if (status) return status;
} else {
//不符合规则的字符
ALOGE("%s: Expected keyword, got '%s'.", mTokenizer->getLocation().string(),
keywordToken.string());
return BAD_VALUE;
}
mTokenizer->skipDelimiters(WHITESPACE);
//一行解析完成之后还不是结尾或者"#"
if (!mTokenizer->isEol() && mTokenizer->peekChar() != '#') {
ALOGE("%s: Expected end of line or trailing comment, got '%s'.",
mTokenizer->getLocation().string(),
mTokenizer->peekRemainderOfLine().string());
//不符合规则
return BAD_VALUE;
}
}
//开始下一行解析
mTokenizer->nextLine();
}
return NO_ERROR;
}
此函数在一个while循环中不断的解析".kl"文件,并且是一行一行的解析,一行为一个循环,直到解析到末尾,在解析每一行时首先会跳过空格,tab键和回车,然后判断当前读取到的字符不是文本末尾或者该行的末尾,并且不是注释"#"开头,则此行是可以有效解析的行,接着通过函数nextToken
获取这一行中第一个空格,tab键或者回车之前的所有字符,用个例子来说明:
Generic.kl文件中随便找一行,key之后有一个空格,所以调用nextToken
函数获取到的字符串就是"key"
key 116 POWER
上面函数中就有三种解析情况:
keyword是"key",调用parseKey
解析,如果解析出错则返回错误。
keyword是"axis",调用parseAxis解析
,如果解析出错则返回错误。
keyword是"led",调用parseLed解析
,如果解析出错则返回错误。
其他的就是不按规则的格式则返回BAD_VALUE。
先来看parseKey()
函数:
Parser::parseKey
status_t KeyLayoutMap::Parser::parseKey() {
//获取该行中第二个空格,tab键或者回车和第一个空格,tab键或者回车之间的所有字符,
//例:key 116 POWER中,实际作用就是获取scan code为116
String8 codeToken = mTokenizer->nextToken(WHITESPACE);
bool mapUsage = false;
//scan code为usage的情况
if (codeToken == "usage") {
mapUsage = true;
mTokenizer->skipDelimiters(WHITESPACE);
codeToken = mTokenizer->nextToken(WHITESPACE);
}
char* end;
//使用函数strtol来进行进制转换,返回值code为转换后的结果strtol最后一个参数为0,
//意思是:
//采用10进制转换,但如果遇到 '0x'/'0X' 开头则会使用16进制转换,遇到'0'
//开头则会使用8进制转换,通常情况按键的scan code都是非0开头。
int32_t code = int32_t(strtol(codeToken.string(), &end, 0));
if (*end) {
ALOGE("%s: Expected key %s number, got '%s'.", mTokenizer->getLocation().string(),
mapUsage ? "usage" : "scan code", codeToken.string());
return BAD_VALUE;
}
KeyedVector<int32_t, Key>& map = mapUsage ? mMap->mKeysByUsageCode : mMap->mKeysByScanCode;
if (map.indexOfKey(code) >= 0) {
//添加重复值,不合法
return BAD_VALUE;
}
//继续跳过空格,tab键或者回车
mTokenizer->skipDelimiters(WHITESPACE);
//继续使用nextToken,实际作用就是获取key 116 POWER中的POWER
String8 keyCodeToken = mTokenizer->nextToken(WHITESPACE);
//scancdoe转keycode
int32_t keyCode = getKeyCodeByLabel(keyCodeToken.string());
if (!keyCode) {
ALOGE("%s: Expected key code label, got '%s'.", mTokenizer->getLocation().string(),
keyCodeToken.string());
return BAD_VALUE;
}
uint32_t flags = 0;
for (;;) {
mTokenizer->skipDelimiters(WHITESPACE);
if (mTokenizer->isEol() || mTokenizer->peekChar() == '#') break;
String8 flagToken = mTokenizer->nextToken(WHITESPACE);
//对于特殊功能按键,获取flag
uint32_t flag = getKeyFlagByLabel(flagToken.string());
if (!flag) {
...
return BAD_VALUE;
}
if (flags & flag) {
...
return BAD_VALUE;
}
flags |= flag;
}
//构造Key
Key key;
key.keyCode = keyCode;
key.flags = flags;
//map
map.add(code, key);
return NO_ERROR;
}
要理解parseKey
很重要的是先理解其中用到的C/C++库函数strtol
的作用,简单来说这个函数作用就是进制转换,函数原型如下:
strtol简介
long int strtol(const char *str, char **endptr, int base)
strtol会将str指向的字符串,根据参数base,按进制转化为long int, 然后返回转换后的值,而endptr是用来保存str指向的字符串中根据base规则不合法的字符串,base的取值是2~36,和特殊值0,因为上面函数用到的base为0,所以我们不去看其他值,来讲讲0的作用,base 为0是表示会自动根据字符串的格式来判断进制,非0开头的会按str为10进制转换为10进制,0x/0X开头的会按str为16进制转换为10进制,0开头的会按str为8进制转换为10进制。
举个例子:
如下str为117,则会按str10进制转换为10进制,ret就等于117,并且117中不包含10进制格式不合法的字符,所以ptr为null。*
如下修改str为117a,依然会按str10进制转换为10进制,ret就等于117,但是117a中包含了10进制格式不合法的字符a,所以ptr为a。*
如下修改str为0x117a,则会按str16进制转换为10进制,ret就等于4474,并且0x117a中不包含16进制格式不合法的字符,所以ptr为null。*
如下修改str为0117a,则会按str8进制转换为10进制,ret就等于79,并且0117a中包含了8进制格式不合法的字符,所以ptr为a。*
看了上面的例子应该对strtol
函数base为0的情况有了一定理解了,这下再来看parseKey
函数就会比较轻松,继续parseKey
函数函数:
parseKey
函数首先会继续通过nextToken
函数获取按键scan code,对于scan code为"usage"的情况修改mapUsage
为true,跳过空格,tab键或者回车,拿到第三个空格,tab键或者回车与第二个空格,tab键或者回车之间的字符串,如下例子,codeToken
就等于0x0c006F
key usage 0x0c006F BRIGHTNESS_UP
key usage 0x0c0070 BRIGHTNESS_DOWN
接着对于非"usage"的情况,就会直接拿到scan code通过strtol
函数进行转换,从Generic.kl表来看,对于按键类型"key"调用strtol
函数转换时只有两种情况,一种非0开头,一种0x开头,所以这句函数的作用就是对于非0开头的scan code就返回自身,对于0x开头的就按16进制转换为10进制。
int32_t code = int32_t(strtol(codeToken.string(), &end, 0));
继续根据mapUsage
选择KeyedVector<int32_t, Key>为mKeysByScanCode
还是mKeysByUsageCode
,这是一个初始化的作用,接着再调用skipDelimiters
和nextToken
函数,我们到现在已经发现了很多次这两个函数成对使用,第一次获取"key",第二次获取"116",这里第三次就是获取"POWER"了。
key 116 POWER
继续重点函数getKeyCodeByLabel
,返回值为keyCode
,可以想象,这个函数结束后我们按键的扫描码就成功映射到了Android键盘码。
//keyCodeToken就是"key"的按键名称,例如POWER
int32_t keyCode = getKeyCodeByLabel(keyCodeToken.string());
getKeyCodeByLabel
//InputEventLabels.h
static inline int32_t getKeyCodeByLabel(const char* label) {
return int32_t(lookupValueByLabel(label, KEYCODES));
}
继续调用lookupValueByLabel
函数,传了一个重要参数KEYCODES
,KEYCODES
是一个InputEventLabel
结构体数组,
struct InputEventLabel {
const char *literal; //按键名称
int value; //按键的值
};
static const InputEventLabel KEYCODES[] = {
// NOTE: If you add a new keycode here you must also add it to several other files.
// Refer to frameworks/base/core/java/android/view/KeyEvent.java for the full list.
DEFINE_KEYCODE(UNKNOWN),
...
DEFINE_KEYCODE(HOME),
DEFINE_KEYCODE(BACK),
DEFINE_KEYCODE(CALL),
...
DEFINE_KEYCODE(STAR),
DEFINE_KEYCODE(POUND),
DEFINE_KEYCODE(DPAD_UP),
DEFINE_KEYCODE(DPAD_DOWN),
DEFINE_KEYCODE(DPAD_LEFT),
DEFINE_KEYCODE(DPAD_RIGHT),
DEFINE_KEYCODE(DPAD_CENTER),
DEFINE_KEYCODE(VOLUME_UP),
DEFINE_KEYCODE(VOLUME_DOWN),
DEFINE_KEYCODE(POWER),
DEFINE_KEYCODE(CAMERA),
DEFINE_KEYCODE(CLEAR),
....
...
}
再把宏DEFINE_KEYCODE
展开看看:
#define DEFINE_KEYCODE(key) { #key, AKEYCODE_##key }
这个宏里面构造了一个InputEventLabel
结构体,还是拿POWER来举例,通过宏DEFINE_KEYCODE
就可以得到{“POWER”,AKEYCODE_POWER}的对应关系,"POWER"
时按键名称,其按键值为AKEYCODE_POWER
。
继续函数lookupValueByLabel
:
lookupValueByLabel
//InputEventLabels.h
static int lookupValueByLabel(const char* literal, const InputEventLabel *list) {
while (list->literal) {
if (strcmp(literal, list->literal) == 0) {
return list->value;
}
list++;
}
return list->value;
}
lookupValueByLabel
函数就会遍历KEYCODES
数组,通过C库函数strcmp
查找literal
是否包含在KEYCODES
数组,返回值是InputEventLabel
结构体的value,即根据"POWER"
则返回AKEYCODE_POWER
,AKEYCODE_POWER
的具体数值定义在哪里呢?
来看keycodes.h:
/**
* Key codes.
*/
enum {
/** Unknown key code. */
AKEYCODE_UNKNOWN = 0,
/** Soft Left key.
* Usually situated below the display on phones and used as a multi-function
* feature key for selecting a software defined function shown on the bottom left
* of the display. */
AKEYCODE_SOFT_LEFT = 1,
/** Soft Right key.
* Usually situated below the display on phones and used as a multi-function
* feature key for selecting a software defined function shown on the bottom right
* of the display. */
AKEYCODE_SOFT_RIGHT = 2,
/** Home key.
* This key is handled by the framework and is never delivered to applications. */
AKEYCODE_HOME = 3,
/** Back key. */
AKEYCODE_BACK = 4,
/** Call key. */
AKEYCODE_CALL = 5,
...
AKEYCODE_VOLUME_UP = 24,
/** Volume Down key.
* Adjusts the speaker volume down. */
AKEYCODE_VOLUME_DOWN = 25,
/** Power key. */
AKEYCODE_POWER = 26,
...
}
我们看到这里面定义了很多AKEYCODE_XX
的枚举值,AKEYCODE_POWER
对应的数值就是26。
最终getKeyCodeByLabel
函数就会根据按键名称,返回对应的数值,这即是scan code和keycode的一一映射关系,如果自己需要添加物理按键,就需要按照此流程进行定义。
接着再回到parseKey()
函数:
status_t KeyLayoutMap::Parser::parseKey() {
...
...
int32_t keyCode = getKeyCodeByLabel(keyCodeToken.string());
if (!keyCode) {
return BAD_VALUE;
}
uint32_t flags = 0;
for (;;) {
mTokenizer->skipDelimiters(WHITESPACE);
if (mTokenizer->isEol() || mTokenizer->peekChar() == '#') break;
String8 flagToken = mTokenizer->nextToken(WHITESPACE);
uint32_t flag = getKeyFlagByLabel(flagToken.string());
if (!flag) {
ALOGE("%s: Expected key flag label, got '%s'.", mTokenizer->getLocation().string(),
flagToken.string());
return BAD_VALUE;
}
if (flags & flag) {
ALOGE("%s: Duplicate key flag '%s'.", mTokenizer->getLocation().string(),
flagToken.string());
return BAD_VALUE;
}
flags |= flag;
}
Key key;
key.keyCode = keyCode;
key.flags = flags;
map.add(code, key);
return NO_ERROR;
}
获取到合法keycode之后来到一个死循环,又见skipDelimiters
函数,继续该行跳过空格,tab键或者回车,如果时常规定义的按键此时就到行尾了,就会直接break跳出循环,最后构造一个结构体Key
,将keyCode
和flags
保存进去,再将这个结构体保存在mKeysByScanCode
(非Usage)这个map中,以scan code
为键,keyCode
为值。
struct Key {
int32_t keyCode;
uint32_t flags;
};
另外还有一些按键是这种格式:
key 476 F11 FUNCTION
key 477 F12 FUNCTION
key 478 1 FUNCTION
就会继续通过nextToken
获取flagToken
等于FUNCTION
,再调用getKeyFlagByLabel
函数获取flag,这个函数和前面getKeyCodeByLabel
一样的作用,它会从FLAGS
数组中寻找:
static const InputEventLabel FLAGS[] = {DEFINE_FLAG(VIRTUAL),
DEFINE_FLAG(FUNCTION),
DEFINE_FLAG(GESTURE),
DEFINE_FLAG(WAKE),
{nullptr, 0}};
#define DEFINE_FLAG(flag) { #flag, POLICY_FLAG_##flag }
拿到FUNCTION
展开宏DEFINE_FLAG
得到POLICY_FLAG_FUNCTION
,POLICY_FLAG_FUNCTION
的值定义在Input.h,指示这是一个特殊功能的按键。
enum {
...
// Indicates that the key is the special function modifier.
POLICY_FLAG_FUNCTION = 0x00000004,
...
}
到此Parser::parseKey()
这个函数我们就已经完全分析清楚了,这个函数最大的作用就是通过InputEventLabel.h的getKeyCodeByLabel
函数完成scan code
到keycode
的映射关系。
另外两个解析函数parseAxis
和parseLed
的规则和parseKey
类似,仅仅是其函数内部调用InputEventLabel.h的函数不一样,如parseAxis
内部调用getAxisByLabel
,parseLed
内部调用getLedByLabel
,这两个函数和getKeyCodeByLabel
实现也基本一致,也只有用到的宏不同,我们也没必要再浪费时间去看了。
#define DEFINE_KEYCODE(key) { #key, AKEYCODE_##key }
#define DEFINE_AXIS(axis) { #axis, AMOTION_EVENT_AXIS_##axis }
#define DEFINE_LED(led) { #led, ALED_##led }
#define DEFINE_FLAG(flag) { #flag, POLICY_FLAG_##flag }
到此解析“.kl“文件就已经分析完了,个人认为还是比较清晰的,其实解析“.kl“文件就是简单的解析文本,一行一行的循环解析,从文本中拿到什么字符串,作对应的操作就行了,解析过程中通过三个函数parseKey
,parseAxis
,parseLed
对三种类型的的按键分别处理,对于"key",最终会构造Key结构体,存入mKeysByScanCode
或者mKeysByUsageCode
中,对于"axis",最终会构造AxisInfo
结构体,存入mAxes
中,对于Led
结构体,存入mLedsByUsageCode
或者mLedsByScanCode
中。