ADROID 2.1 架构解析之键盘

来源:互联网 发布:天下谁人不通共 知乎 编辑:程序博客网 时间:2024/05/01 13:32

参考原文:http://blog.csdn.net/skdev/archive/2010/03/08/5355542.aspx

1、WindowManager服务
文件:frameworks/base/services/java/com/android/server/WindowManagerService.java
WindowManagerService的构造函数中会创建内部类KeyQ对象。
private WindowManagerService(Context context, PowerManagerService pm,
            boolean haveInputMethods) {
    if (MEASURE_LATENCY) {
        lt = new LatencyTimer(100, 1000);
        }
    ...
    mQueue = new KeyQ();
    ...
}

2、KeyInputQueue类
文件:frameworks/base/services/java/com/android/server/KeyInputQueue.java
KeyQ继承于KeyInputQueue类,在KeyInputQueue类的构造函数中会启动一个线程mThread,在这个线程里不断读取输入事件,然后对这个事件进行处理:
KeyInputQueue(Context context, HapticFeedbackCallback  hapticFeedbackCallback) {
       ......
        mThread.start();
}
mThread的定义如下:
Thread mThread = new Thread("InputDeviceReader") {
        public void run() {
                ......
            RawInputEvent ev = new RawInputEvent();
            while (true) {
                    try {
                                   ...
                        // block, doesn't release the monitor
                        readEvent(ev);
                                   ...
                          }
            ...
              synchronized (mFirst) {
                          ...
                        final int classes = di.classes;
                        final int type = ev.type;
                        final int scancode = ev.scancode;
                 ...
                        // Is it a key event?
                        if (type == RawInputEvent.EV_KEY &&
                                (classes&RawInputEvent.CLASS_KEYBOARD) != 0 &&
                                (scancode < RawInputEvent.BTN_FIRST ||
                                        scancode > RawInputEvent.BTN_LAST)) {
                            boolean down;
                            if (ev.value != 0) {
                                down = true;
                                di.mKeyDownTime = curTime;
                            } else {
                                down = false;
                                     }
                            int keycode = rotateKeyCodeLocked(ev.keycode);
                            addLocked(di, curTimeNano, ev.flags,
                                    RawInputEvent.CLASS_KEYBOARD,
                                    newKeyEvent(di, di.mKeyDownTime, curTime, down,
                                            keycode, 0, scancode,
                                            ((ev.flags & WindowManagerPolicy.FLAG_WOKE_HERE) != 0)
                                             ? KeyEvent.FLAG_WOKE_HERE : 0));

                        } else if (ev.type == RawInputEvent.EV_KEY) {
                    ...
                }
            }
                 }
}
mThread不断调用readEvent,将输入事件读取到ev类,即RawInputEvent的变量里,然后会判断输入事件的类型。比如如果如下条件成立,
type == RawInputEvent.EV_KEY &&
    (classes&RawInputEvent.CLASS_KEYBOARD) != 0 &&
    (scancode < RawInputEvent.BTN_FIRST || scancode > RawInputEvent.BTN_LAST)
则会判断成键盘事件。
判断会相应事件后,会调用addLocked函数把相应的事件加入到事件队列中去:
    private void addLocked(InputDevice device, long whenNano, int flags,
            int classType, Object event) {
        boolean poke = mFirst.next == mLast;

        QueuedEvent ev = obtainLocked(device, whenNano, flags, classType, event);
        QueuedEvent p = mLast.prev;
        while (p != mFirst && ev.whenNano < p.whenNano) {
            p = p.prev;
        }

        ev.next = p.next;
        ev.prev = p;
        p.next = ev;
        ev.next.prev = ev;
        ev.inQueue = true;

        if (poke) {
            long time;
            if (MEASURE_LATENCY) {
                time = System.nanoTime();
            }
            mFirst.notify();
            mWakeLock.acquire();
            if (MEASURE_LATENCY) {
                lt.sample("1 addLocked-queued event ", System.nanoTime() - time);
            }
        }
    }
这个事件队列的类型是QueuedEvent

3、readEvent的JNI实现:android_server_KeyInputQueue_readEvent
文件:frameworks/base/services/jni/com_android_server_KeyInputQueue.cpp
static jboolean
android_server_KeyInputQueue_readEvent(JNIEnv* env, jobject clazz,
                                          jobject event)
{
    gLock.lock();
    sp<EventHub> hub = gHub;
    if (hub == NULL) {
        hub = new EventHub;
        gHub = hub;
    }
    gLock.unlock();

    int32_t deviceId;
    int32_t type;
    int32_t scancode, keycode;
    uint32_t flags;
    int32_t value;
    nsecs_t when;
    bool res = hub->getEvent(&deviceId, &type, &scancode, &keycode,
            &flags, &value, &when);

    env->SetIntField(event, gInputOffsets.mDeviceId, (jint)deviceId);
    env->SetIntField(event, gInputOffsets.mType, (jint)type);
    env->SetIntField(event, gInputOffsets.mScancode, (jint)scancode);
    env->SetIntField(event, gInputOffsets.mKeycode, (jint)keycode);
    env->SetIntField(event, gInputOffsets.mFlags, (jint)flags);
    env->SetIntField(event, gInputOffsets.mValue, value);
    env->SetLongField(event, gInputOffsets.mWhen,
                        (jlong)(nanoseconds_to_milliseconds(when)));

    return res;
}
此函数将从eventhub读到的变量存到android/view/RawInputEvent类对应的变量里。为什么呢?这里要简单说明一下。
3.1 register_android_server_KeyInputQueue的定义
文件:frameworks/base/services/jni/com_android_server_KeyInputQueue.cpp
int register_android_server_KeyInputQueue(JNIEnv* env)
{
      ...
    jclass inputEvent = env->FindClass("android/view/RawInputEvent");
    LOG_FATAL_IF(inputEvent == NULL, "Unable to find class android/view/RawInputEvent");

    gInputOffsets.mDeviceId
        = env->GetFieldID(inputEvent, "deviceId", "I");
    LOG_FATAL_IF(gInputOffsets.mDeviceId == NULL, "Unable to find RawInputEvent.deviceId");

    gInputOffsets.mType
        = env->GetFieldID(inputEvent, "type", "I");
    LOG_FATAL_IF(gInputOffsets.mType == NULL, "Unable to find RawInputEvent.type");

    gInputOffsets.mScancode
        = env->GetFieldID(inputEvent, "scancode", "I");
    LOG_FATAL_IF(gInputOffsets.mScancode == NULL, "Unable to find RawInputEvent.scancode");

    gInputOffsets.mKeycode
        = env->GetFieldID(inputEvent, "keycode", "I");
    LOG_FATAL_IF(gInputOffsets.mKeycode == NULL, "Unable to find RawInputEvent.keycode");

    gInputOffsets.mFlags
        = env->GetFieldID(inputEvent, "flags", "I");
    LOG_FATAL_IF(gInputOffsets.mFlags == NULL, "Unable to find RawInputEvent.flags");

    gInputOffsets.mValue
        = env->GetFieldID(inputEvent, "value", "I");
    LOG_FATAL_IF(gInputOffsets.mValue == NULL, "Unable to find RawInputEvent.value");

    gInputOffsets.mWhen
        = env->GetFieldID(inputEvent, "when", "J");
    LOG_FATAL_IF(gInputOffsets.mWhen == NULL, "Unable to find RawInputEvent.when");

    return res;
}
此函数获取android/view/RawInputEvent类的变量。

3.2 register_android_server_KeyInputQueue的被调用
文件:frameworks/base/services/jni/onload.cpp
extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
    JNIEnv* env = NULL;
    jint result = -1;

    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
        LOGE("GetEnv failed!");
        return result;
    }
    LOG_ASSERT(env, "Could not retrieve the env!");

    register_android_server_KeyInputQueue(env);
    register_android_server_HardwareService(env);
    register_android_server_AlarmManagerService(env);
    register_android_server_BatteryService(env);
    register_android_server_SensorService(env);
    register_android_server_FallbackCheckinService(env);
    register_android_server_SystemServer(env);

    return JNI_VERSION_1_4;
}
当Android的VM(Virtual Machine)执行到System.loadLibrary()函数时,首先会去执行C组件里的JNI_OnLoad()函数。
从上可以看出,JNI_OnLoad函数还会调用其他相应的服务注册函数。
从android_server_KeyInputQueue_readEvent的定义可以看出,它会去调用hub->getEvent函数,hub的类型是EventHub,下面我们来看看EventHub类中的
getEvent函数。

4、EventHub库
文件:frameworks/base/libs/ui/EventHub.cpp
4.1 读取输入设备状态
static const char *device_path = "/dev/input";
bool EventHub::getEvent(int32_t* outDeviceId, int32_t* outType,
        int32_t* outScancode, int32_t* outKeycode, uint32_t *outFlags,
        int32_t* outValue, nsecs_t* outWhen)
{
    ...
    if (!mOpened) {
        mError = openPlatformInput() ? NO_ERROR : UNKNOWN_ERROR;
        mOpened = true;
    }
    ...
    while(1) {
    ...
       pollres = poll(mFDs, mFDCount, -1);
    ...
        // mFDs[0] is used for inotify, so process regular events starting at mFDs[1]
        for(i = 1; i < mFDCount; i++) {
            if(mFDs[i].revents) {
                LOGV("revents for %d = 0x%08x", i, mFDs[i].revents);
                if(mFDs[i].revents & POLLIN) {
                    res = read(mFDs[i].fd, &iev, sizeof(iev));
                    if (res == sizeof(iev)) {
                        LOGV("%s got: t0=%d, t1=%d, type=%d, code=%d, v=%d",
                             mDevices[i]->path.string(),
                             (int) iev.time.tv_sec, (int) iev.time.tv_usec,
                             iev.type, iev.code, iev.value);
                        *outDeviceId = mDevices[i]->id;
                        if (*outDeviceId == mFirstKeyboardId) *outDeviceId = 0;
                        *outType = iev.type;
                        *outScancode = iev.code;
                        if (iev.type == EV_KEY) {
                            err = mDevices[i]->layoutMap->map(iev.code, outKeycode, outFlags);
                            LOGV("iev.code=%d outKeycode=%d outFlags=0x%08x err=%d/n",
                                iev.code, *outKeycode, *outFlags, err);
                            if (err != 0) {
                                *outKeycode = 0;
                                *outFlags = 0;
                            }
                        } else {
                            *outKeycode = iev.code;
                        }
                        *outValue = iev.value;
                        *outWhen = s2ns(iev.time.tv_sec) + us2ns(iev.time.tv_usec);
                        return true;
                    } else {
                        if (res<0) {
                            LOGW("could not get event (errno=%d)", errno);
                        } else {
                            LOGE("could not get event (wrong size: %d)", res);
                        }
                        continue;
                    }
                }
            }
        }

        // read_notify() will modify mFDs and mFDCount, so this must be done after
        // processing all other events.
        if(mFDs[0].revents & POLLIN) {
            read_notify(mFDs[0].fd);
        }
    }
}
openPlatformInput() 打开/dev/input/ 目录下的所有输入设备文件。
打开设备后,不断轮循所有设备,直到读取到有POLLIN事件产生的设备的状态。

4.2 openPlatformInput函数
bool EventHub::openPlatformInput(void)
{
    ...
    res = scan_dir(device_path);
    if(res < 0) {
        LOGE("scan dir failed for %s/n", device_path);
        //open_device("/dev/input/event0");
    }

    return true;
}
openPlatformInput函数会调用scan_dir函数:
int EventHub::scan_dir(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);
        open_device(devname);
    }
    closedir(dir);
    return 0;
}
scan_dir会递归地调用open_device函数,打开/dev/input/ 目录下的所有输入设备文件。

4.3 导入键盘配置文件
int EventHub::open_device(const char *deviceName)
{
               ...
       // find the .kl file we need for this device
        const char* root = getenv("ANDROID_ROOT");
        snprintf(keylayoutFilename, sizeof(keylayoutFilename),
                 "%s/usr/keylayout/%s.kl", root, tmpfn);
        bool defaultKeymap = false;
        if (access(keylayoutFilename, R_OK)) {
            snprintf(keylayoutFilename, sizeof(keylayoutFilename),
                     "%s/usr/keylayout/%s", root, "qwerty.kl");
            defaultKeymap = true;
        }
        device->layoutMap->load(keylayoutFilename);
        ...
        char propName[100];
        sprintf(propName, "hw.keyboards.%u.devname", publicID);
        property_set(propName, name);
        ...
}
由以上代码可知,会优先加载/system/usr/keylayout/输入设备名称.kl
如: /sys/class/input/input1/name = keypad,则会加载/system/usr/keylayout/keypad.kl,如果该文件不存在,则加载默认文件/system/usr/keylayout/qwerty.kl,该文件的原型在:sdk/emulator/keymaps/qwerty.kl ,eclair以前的版本都是放在development/ emulator/keymaps/qwerty.kl

4.4 按键映射
如4.3节中open_device的代码,先加载配置文件:
device->layoutMap->load(keylayoutFilename);
如4.1节中getEvent的代码,再将读取到的按键码进行转换:
err = mDevices[i]->layoutMap->map(iev.code, outKeycode, outFlags);
文件:frameworks/base/libs/ui/KeyLayoutMap.cpp

status_t

KeyLayoutMap::load(const char* filename)

{

int fd = open(filename, O_RDONLY);

...

while (true) {

        String8 token = next_token(&p, &line);

        if (*p == '/0') {

            break;

        }

        switch (state)

        {

            case BEGIN:

                if (token == "key") {

                    state = SCANCODE;

                } else {

                    LOGE("%s:%d: expected key, got '%s'/n", filename, line,

                            token.string());

                    err = BAD_VALUE;

                    goto done;

                }

                break;

            case SCANCODE:

                scancode = strtol(token.string(), &end, 0);

                if (*end != '/0') {

                    LOGE("%s:%d: expected scancode (a number), got '%s'/n",

                            filename, line, token.string());

                    goto done;

                }

                //LOGI("%s:%d: got scancode %d/n", filename, line, scancode );

                state = KEYCODE;

                break;

            case KEYCODE:

                keycode = token_to_value(token.string(), KEYCODES);

                //LOGI("%s:%d: got keycode %d for %s/n", filename, line, keycode, token.string() );

                if (keycode == 0) {

                    LOGE("%s:%d: expected keycode, got '%s'/n",

                            filename, line, token.string());

                    goto done;

                }

                state = FLAG;

                break;

            case FLAG:

                if (token == "key") {

                    if (scancode != -1) {

                        //LOGI("got key decl scancode=%d keycode=%d"

                        //       " flags=0x%08x/n", scancode, keycode, flags);

                        Key k = { keycode, flags };

                        m_keys.add(scancode, k);

                        state = SCANCODE;

                        scancode = -1;

                        keycode = -1;

                        flags = 0;

                        break;

                    }

                }

                tmp = token_to_value(token.string(), FLAGS);

                //LOGI("%s:%d: got flags %x for %s/n", filename, line, tmp, token.string() );

                if (tmp == 0) {

                    LOGE("%s:%d: expected flag, got '%s'/n",

                            filename, line, token.string());

                    goto done;

                }

                flags |= tmp;

                break;

        }

    }

...

}

由以上代码可知,是以如下方式对配置文件进行解析的:

BEGIN: 如果第一个关键字是key,则转入SCANCODE,否则退出。

SCANCODE: 将第二个关键字转为数字,即扫描码scancode,转向KEYCODE

KEYCODE: 将第三个关键字与KEYCODES列表配对,找出关键码keycode,转向FLAG

FLAG:如果第四个关键字是key,则保存刚扫描的键码,然后转向SCANCODE,否则将该关键字与FLAGS列表配对,找出flags值,然后转向BEGIN

 

注:KEYCODES列表和FLAGS列表的定义在:

frameworks/base/include/ui/KeycodeLabels.h

 

status_t

KeyLayoutMap::map(int32_t scancode, int32_t *keycode, uint32_t *flags) const

{

    if (m_status != NO_ERROR) {

        return m_status;

    }

 

    ssize_t index = m_keys.indexOfKey(scancode);

    if (index < 0) {

        //LOGW("couldn't map scancode=%d/n", scancode);

        return NAME_NOT_FOUND;

    }

 

    const Key& k = m_keys.valueAt(index);

 

    *keycode = k.keycode;

    *flags = k.flags;

 

    //LOGD("mapped scancode=%d to keycode=%d flags=0x%08x/n", scancode,

    //        keycode, flags);

 

    return NO_ERROR;

}

Map的功能是根据scancode,找到对应的keycode.

 

qwerty.kl 的部分配置:

#  scancode    keycode      flags

key 399   GRAVE

key 2     1

key 3     2

key 4     3

key 5     4

key 6     5

key 7     6

key 8     7

key 9     8

key 10    9

key 11    0

key 158   BACK              WAKE_DROPPED

key 230   SOFT_RIGHT        WAKE

key 60    SOFT_RIGHT        WAKE

5、流程总结

当有按键响应时,内核传给ANDROID的是scancodeANDROIDscancode经配置表(qwerty.kl)找到keycode标识符,然后由内部表KEYCODES列表找到keycode的数字值。

原创粉丝点击