getevent工具和Android中inputevent的分析

2018-12-17  桥栈  转自 liuwei20081...

修改

 

分析Android 的Input Event 子系统的来龙去脉。

 

Android 系统里面有很多小工具,运行这些工具,我们对它们有一个感性的认识,进而阅读和分析这些小工具源代码,再顺藤摸瓜,就可以把整个子系统的来龙去脉弄清楚。

 

1.运行toolbox的getevent 工具。

 

# getevent -help
getevent -help
Usage: getevent [-t] [-n] [-s switchmask] [-S] [-v [mask]] [-p] [-q] [-c count] [-r] [device]
    -t: show time stamps
    -n: don't print newlines
    -s: print switch states for given bits
    -S: print all switch states
    -v: verbosity mask (errs=1, dev=2, name=4, info=8, vers=16, pos. events=32)
    -p: show possible events (errs, dev, name, pos. events)
    -q: quiet (clear verbosity mask)
    -c: print given number of events then exit
    -r: print rate events are received
# getevent -c 20
getevent -c 20
add device 1: /dev/input/event4
  name:     "sensor-input"
add device 2: /dev/input/event3
  name:     "88pm860x_hook"
add device 3: /dev/input/event2
  name:     "88pm860x_on"
add device 4: /dev/input/event1
  name:     "88pm860x-touch"
add device 5: /dev/input/event0
  name:     "pxa27x-keypad"
/dev/input/event0: 0001 0066 00000001
/dev/input/event0: 0000 0000 00000000
/dev/input/event0: 0001 0066 00000000
/dev/input/event0: 0000 0000 00000000
/dev/input/event1: 0003 0000 00000c48
/dev/input/event1: 0003 0001 00000751
/dev/input/event1: 0001 014a 00000001
/dev/input/event1: 0000 0000 00000000
/dev/input/event1: 0003 0000 00000c67
/dev/input/event1: 0003 0001 000006f9
/dev/input/event1: 0000 0000 00000000
/dev/input/event1: 0003 0000 00000c9e
/dev/input/event1: 0003 0001 0000069e
/dev/input/event1: 0000 0000 00000000
/dev/input/event1: 0003 0000 00000cc4
/dev/input/event1: 0003 0001 00000620
/dev/input/event1: 0000 0000 00000000
/dev/input/event1: 0003 0000 00000ce8
/dev/input/event1: 0003 0001 000005ba
/dev/input/event1: 0000 0000 00000000

运行这个工具,然后按键或者滑动触摸屏,会看到程序会实时打印event。从上面的输出来看,系统有5个input 子系统。它们分别是

 

add device 1: /dev/input/event4
  name:     "sensor-input"

#Sensor input 子系统

 

add device 2: /dev/input/event3
  name:     "88pm860x_hook"

#耳机Hook键子系统。可支持接电话挂电话的耳机上面有一个按键,对应的就是这个input 子系统。


add device 3: /dev/input/event2
  name:     "88pm860x_on"

 

#开机键 input 子系统
add device 4: /dev/input/event1
  name:     "88pm860x-touch"

#Touch Screen input 子系统


add device 5: /dev/input/event0
  name:     "pxa27x-keypad"

#按键子系统,包括Home/Menu/Back等按键。

 

可以尝试多种event,实际感觉一下出来的log。

 

 

2.阅读getevent的代码。代码为./core/toolbox/getevent.c

 

从代码中,我们知道,程序在while(1)的一个死循环里,不断地在读取 (select 操作)/dev/input 下面的文件,检查是否Kernel往里面更新内容,如果有内容更新,就把它打印出来。并且从代码中,我们还知道,任何一个event都有三种属性,type,code,value.

 

 

    while(1) {
        pollres = poll(ufds, nfds, -1); 
        //printf("poll %d, returned %d/n", nfds, pollres);
        if(ufds[0].revents & POLLIN) {
            read_notify(device_path, ufds[0].fd, print_flags);
        }
        for(i = 1; i < nfds; i++) {
            if(ufds[i].revents) {
                if(ufds[i].revents & POLLIN) {
                    res = read(ufds[i].fd, &event, sizeof(event)); 
                    if(res < (int)sizeof(event)) {
                        fprintf(stderr, "could not get event/n");
                        return 1;
                    }
                    if(get_time) {
                        printf("%ld-%ld: ", event.time.tv_sec, event.time.tv_usec);
                    }
                    if(print_device)
                        printf("%s: ", device_names[i]);
                    printf("%04x %04x %08x", event.type, event.code, event.value);

                    if(sync_rate && event.type == 0 && event.code == 0) {
                        int64_t now = event.time.tv_sec * 1000000LL + event.time.tv_usec;
                        if(last_sync_time)
                            printf(" rate %lld", 1000000LL / (now - last_sync_time));
                        last_sync_time = now;
                    }
                    printf("%s", newline);
                    if(event_count && --event_count == 0)
                        return 0;
                }
            }
        }

 

 

3.问题来了,Android Framework是否也是一样的原理呢??猜测应该是一样的才对,不然这个工具就没有调试的价值了。

 

我们来阅读和分析framework中input event的相关代码。

 

我们从Kernel层往上看,先看看Framework中,直接操纵/dev/input设备的代码。

 

在.frameworks/base/libs/ui/EventHub.cpp 中,我们看到跟getevent工具类似的代码。

 

bool EventHub::getEvent(int32_t* outDeviceId, int32_t* outType,
        int32_t* outScancode, int32_t* outKeycode, uint32_t *outFlags,
        int32_t* outValue, nsecs_t* outWhen)
{

....

 

    while(1) {
....
        release_wake_lock(WAKE_LOCK_ID);

        pollres = poll(mFDs, mFDCount, -1); 

        acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID);

        if (pollres <= 0) {
            if (errno != EINTR) {
                LOGW("select failed (errno=%d)/n", errno);
                usleep(100000);
            }
            continue;
        }

 

....


        // 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(),

             ....

        }

 

4.那么framework中那个模块再调用EventHub呢,接着往下查。

 

在framework目录中,输入下面的命令查找

 

# find . -name "*.cpp" |grep -v EventHub | xargs grep EventHub 
./base/services/jni/com_android_server_KeyInputQueue.cpp:#include
./base/services/jni/com_android_server_KeyInputQueue.cpp:static sp gHub;
./base/services/jni/com_android_server_KeyInputQueue.cpp:    sp hub = gHub;
./base/services/jni/com_android_server_KeyInputQueue.cpp:        hub = new EventHub;
./base/services/jni/com_android_server_KeyInputQueue.cpp:    sp hub = gHub;
./base/services/jni/com_android_server_KeyInputQueue.cpp:        hub = new EventHub;

 

5.从查找结果中得知,在jni文件com_android_server_KeyInputQueue.cpp文件中有对EventHub进行调用。

 

打开并阅读com_android_server_KeyInputQueue.cpp文件得知,在下面的函数中调用了EventHub的getEvent函数

 

static jboolean
android_server_KeyInputQueue_readEvent(JNIEnv* env, jobject clazz,
                                          jobject event) 
{
    gLock.lock();
    sp 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;
}

6.根据jni的调用规则,在本文件中查找对于的java函数。

 

static JNINativeMethod gInputMethods[] = {

    /* name, signature, funcPtr */
    { "readEvent",       "(Landroid/view/RawInputEvent;)Z",
            (void*) android_server_KeyInputQueue_readEvent },

    .... 

7. 接着顺藤摸瓜,找到对应的java文件,base/services/java/com/android/server/KeyInputQueue.java

 

    private static native boolean readEvent(RawInputEvent outEvent);

在一个线程中会调用readEvent函数。

 

    Thread mThread = new Thread("InputDeviceReader") {
        public void run() {
            if (DEBUG) Slog.v(TAG, "InputDeviceReader.run()");
            android.os.Process.setThreadPriority(
                    android.os.Process.THREAD_PRIORITY_URGENT_DISPLAY);

            RawInputEvent ev = new RawInputEvent();
            while (true) {
                try {
                    InputDevice di;

                    // block, doesn't release the monitor
                    readEvent(ev);
 

                    boolean send = false;
                    boolean configChanged = false;

                    if (false) {
                        Slog.i(TAG, "Input event: dev=0x"
                                + Integer.toHexString(ev.deviceId)
                                + " type=0x" + Integer.toHexString(ev.type)
                                + " scancode=" + ev.scancode
                                + " keycode=" + ev.keycode
                                + " value=" + ev.value);
                    }

 

8.那是谁启动这个线程呢???查找mThread变量,得知在KeyInputQueue的构造函数中会启动这个线程。

 

    KeyInputQueue(Context context, HapticFeedbackCallback  hapticFeedbackCallback) {
        if (MEASURE_LATENCY) {
            lt = new LatencyTimer(100, 1000);
        }

        Resources r = context.getResources();
        BAD_TOUCH_HACK = r.getBoolean(com.android.internal.R.bool.config_filterTouchEvents);

        JUMPY_TOUCH_HACK = r.getBoolean(com.android.internal.R.bool.config_filterJumpyTouchEvents);

        mHapticFeedbackCallback = hapticFeedbackCallback;

        readExcludedDevices();

        PowerManager pm = (PowerManager)context.getSystemService(
                                                        Context.POWER_SERVICE);
        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
                                                        "KeyInputQueue");
        mWakeLock.setReferenceCounted(false);

        mFirst = new QueuedEvent();
        mLast = new QueuedEvent();
        mFirst.next = mLast;

 

        mThread.start(); 
    }

9.那这个KeyInputQueue是在哪里被实例化呢?

 

而且查看KeyInputQueue类的声明,得知它是一个abstract class.

 

public abstract class KeyInputQueue

{

.....

}

说明它肯定会被某个类继承.接着查找。

 

/frameworks$ find . -name "*.java" |grep -v KeyInputQueue | xargs grep KeyInputQueue 
./policies/base/phone/com/android/internal/policy/impl/KeyguardViewMediator.java: * {@link com.android.server.KeyInputQueue}'s and {@link android.view.WindowManager}'s.
./base/services/java/com/android/server/PowerManagerService.java:                    && !"KeyInputQueue".equals(tag))) {
./base/services/java/com/android/server/WindowManagerService.java:import com.android.server.KeyInputQueue.QueuedEvent;
./base/services/java/com/android/server/WindowManagerService.java:        implements Watchdog.Monitor, KeyInputQueue.HapticFeedbackCallback {
./base/services/java/com/android/server/WindowManagerService.java:        return KeyInputQueue.getSwitchState(sw);
./base/services/java/com/android/server/WindowManagerService.java:        return KeyInputQueue.getSwitchState(devid, sw);
./base/services/java/com/android/server/WindowManagerService.java:        return KeyInputQueue.hasKeys(keycodes, keyExists);
./base/services/java/com/android/server/WindowManagerService.java:    private class KeyQ extends KeyInputQueue
./base/services/java/com/android/server/WindowManagerService.java:            implements KeyInputQueue.FilterCallback {
./base/services/java/com/android/server/InputDevice.java:    // For use by KeyInputQueue for keeping track of the current touch
./base/services/java/com/android/server/InputDevice.java:            if (KeyInputQueue.BAD_TOUCH_HACK) {
./base/services/java/com/android/server/InputDevice.java:                    Slog.i("KeyInputQueue", "Updating: " + currentMove);
./base/services/java/com/android/server/InputDevice.java:                    Slog.i("KeyInputQueue", "Updating: " + currentMove);

 

10.从上面的查找结果得知,会在WindowManagerService.java中有一个KeyQ类继承KeyInputQueue类,再在这个文件中查找KeyQ类在哪里定义并实例化的,找到在其构造函数里实例化的。

 

   private WindowManagerService(Context context, PowerManagerService pm, 
            boolean haveInputMethods) {
        if (MEASURE_LATENCY) {
            lt = new LatencyTimer(100, 1000);
        }

           ....

    mQueue = new KeyQ(); 

        mInputThread = new InputDispatcherThread();

        PolicyThread thr = new PolicyThread(mPolicy, this, context, pm);
         ...

}

 

至此,基本上把Input event的Framework的流程全部走完了。WindowManagerService是属于System server进程里面起的一个Service.一开机就会运行,当然其构造函数一开机就能会运行。

 

 

至此,整个流程如下:

               WindowManagerService

                             |

                             |

                            //

                         KeyQ

                             |

                             |

                            //

                        KeyInputQueue

                             |

                             |

                            //

                        EventHub

                             |

                             |

                            //

                         Kernel device (/dev/input)

更多相关文章

  1. C语言函数的递归(上)
  2. Android实现带动画效果的Tab Menu
  3. Android(安卓)GPS 定位的实现(1)
  4. Android之简易天气预报小应用(xml解析练手)
  5. 初窥图像处理利器RenderScript
  6. 在代码中获取Android(安卓)theme中的attr属性值
  7. 数据解析
  8. Android(安卓)蓝牙开发(四)OPP传输文件
  9. Android(安卓)Studio代码调试技巧篇

随机推荐

  1. Android当中的SeekBar与iOS中的UISlider
  2. Android(安卓)path 贝塞尔曲线 波浪形
  3. 在android jni中使用log
  4. android各个文件分析
  5. 点击LinearLayout使用selector改变TextVi
  6. 创建Android(安卓)SD卡遇到的问题
  7. UI
  8. android:screenOrientation的说明
  9. [Android] Android的音频采集
  10. 使用Android自带Ant构建Apk