文档章节

Android 中input event的分析

kingsOSZT
 kingsOSZT
发布于 2015/10/22 16:42
字数 1782
阅读 110
收藏 3
点赞 0
评论 0

文章将分析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 <ui/EventHub.h>
./base/services/jni/com_android_server_KeyInputQueue.cpp:static sp<EventHub> gHub;
./base/services/jni/com_android_server_KeyInputQueue.cpp:    sp<EventHub> hub = gHub;
./base/services/jni/com_android_server_KeyInputQueue.cpp:        hub = new EventHub;
./base/services/jni/com_android_server_KeyInputQueue.cpp:    sp<EventHub> 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<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;
}

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)

 

后续的文章将介绍/dev/input在Kernel中的实现。


© 著作权归作者所有

共有 人打赏支持
kingsOSZT
粉丝 2
博文 26
码字总数 8456
作品 0
松江
Android应用程序窗口(Activity)的窗口对象(Window)的创建过程分析

每一个Activity组件都有一个关联的ContextImpl对象,同时,它还关联有一个Window对象,用来描述一个具体的应用程序窗口。与Activity组件所关联的窗口对象的实际类型为PhoneWindow。 那么创建...

lonamessi ⋅ 05/31 ⋅ 0

Android官方架构组件之LiveData + ViewModel + Room 源码分析一

简单使用案例: MainActivity: MessageViewModel: 其中viewModel.getMessageObserver().observe(this, new Observer() ) 中的this即SupportActivity :LifecycleOwner SupportActivity im......

编程之乐 ⋅ 05/23 ⋅ 0

native_app_glue 分析

natvie_activity The native activity interface provided by is based on a set of application-provided callbacks that will be called by the Activity's main thread when certain even......

lightUp ⋅ 2016/06/28 ⋅ 0

探索Activity启动流程-实现打开插件中的Activity

通过分析Activity的启动流程,探索Android的插件化,下面通过源码分析实现一个简单的插件化 打开一个 未安装apk中的Activity 开始分析 Activity的启动流程从 startActivity开始 然后通过 In...

liwg ⋅ 05/11 ⋅ 0

Android 计步器应用 - healthgo

healthgo a android pedometer app (安卓计步器) Support Android 4.1- 7.0 Thanks Project/People desc other Realm a mobile database 使用了这个数据库 EventBus a publish/subscribe eve......

五十风 ⋅ 04/13 ⋅ 0

使用Python制作微信跳一跳半自动外挂+详细注释

思路 1.截取安卓手机当前屏幕图片并复制到Mac。 知识点:ADB工具--截屏命令 2.测量截图中两方块之间的距离。知识点:Matplotlib制图工具 3.根据距离判断手机所需按下的时间。 4.通过鼠标控制...

earth9 ⋅ 04/16 ⋅ 0

Android SurfaceFlinger 学习之路(五)----VSync 工作原理

原址 VSync信号的科普我们上一篇已经介绍过了,这篇我们要分析在SurfaceFlinger中的作用。(愈发觉得做笔记对自己记忆模块巩固有很多帮助,整理文章不一定是用来给别人看的,但一定是为加强自...

u010164190 ⋅ 04/27 ⋅ 0

android沉浸状态栏实现、地图多线路规划、Retrofit+RxJava+Jsoup+Mvp模式资讯类App等源码

Android精选源码 Android各种沉浸式状态栏实现源码(http://www.apkbus.com/thread-598927-1-1.html) Android自定义View实现炫酷的星期日期选择控件源码(http://www.apkbus.com/thread-59893...

逆鳞龙 ⋅ 05/08 ⋅ 0

Android性能分析工具简介

在Android项目开发工程中,功能开发只是其中的一部分,更多的时候是优化,优化除了个人的良好习惯,往往还需要借助第三方工具。本文罗列Android优化过程中的一些常用工具借助这些工具,可以很...

code_xzh ⋅ 05/02 ⋅ 0

Android 渗透测试学习手册 第一章 Android 安全入门

第一章 Android 安全入门 作者:Aditya Gupta 译者:飞龙 协议:CC BY-NC-SA 4.0 Android 是当今最流行的智能手机操作系统之一。 随着人气的增加,它存在很多安全风险,这些风险不可避免地被...

apachecn_飞龙 ⋅ 2016/12/06 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

Mahout推荐算法之SlopOne

一、 算法原理 有别于基于用户的协同过滤和基于item的协同过滤,SlopeOne采用简单的线性模型估计用户对item的评分。如下图,估计UserB对ItemJ的偏好 图(1) 在真实情况下,该方法有如下几个...

xiaomin0322 ⋅ 27分钟前 ⋅ 0

LVM讲解

LVM是什么 LVM是 Logical Volume Manager(逻辑卷管理)的简写,它是Linux环境下对磁盘分区进行管理的一种机制,Linux用户安装Linux操作系统时遇到的一个常见的难以决定的问题就是如何正确地...

李超小牛子 ⋅ 37分钟前 ⋅ 0

mysql更改密码、连接mysql、mysql常用命令

1. 更改mysql的root账户密码: mysql中root账户和系统root不是一个账户 1.1 更改环境变量PATH,增加mysql绝对路径 由于mysql安装目录为/usr/local/mysql/,所以系统不能直接使用mysql,需把/...

laoba ⋅ 38分钟前 ⋅ 0

阿里云发布企业数字化及上云外包平台服务:阿里云众包平台

摘要: 阿里云正式发布旗下众包平台业务(网址:https://zhongbao.aliyun.com/),支持包括:网站定制开发,APP、电商系统等软件开发,商标、商品LOGO、VI、产品包装设计、营销推广、大数据人...

猫耳m ⋅ 38分钟前 ⋅ 0

阿里云发布企业数字化及上云外包平台服务:阿里云众包平台

摘要: 阿里云正式发布旗下众包平台业务(网址:https://zhongbao.aliyun.com/),支持包括:网站定制开发,APP、电商系统等软件开发,商标、商品LOGO、VI、产品包装设计、营销推广、大数据人...

阿里云云栖社区 ⋅ 41分钟前 ⋅ 0

1.03-Maven中使用ueditor富文本编辑器

起因:在maven仓库未找到百度的ueditor的jar包 操作: 1.下载百度的ueditor的jar包 2.打开命令行,切换到ueditor的下载位置,运行一下命令: mvn install:install-file -Dfile=ueditor-1.1....

静以修身2025 ⋅ 47分钟前 ⋅ 0

几道Spring 面试题

1、BeanFactory 接口和 ApplicationContext 接口有什么区别? ApplicationContext 接口继承BeanFactory接口 Spring核心工厂是BeanFactory BeanFactory采取延迟加载,第一次getBean时才会初始...

职业搬砖20年 ⋅ 56分钟前 ⋅ 0

包饺子

http://storage.slide.news.sina.com.cn/slidenews/77_ori/2018_24/74766_826131_625489.gif

霜叶情 ⋅ 58分钟前 ⋅ 0

xml解析

方法一: String s_xml1 = "<xml>" + "<head>lalalalal</head>" + "<body>1234</body>" + "</xml>"; try { DocumentBuilderFactory documentBuilderFactory......

GithubXD ⋅ 今天 ⋅ 0

reuse stream

Although Java streams were designed to be operated only once, programmers still ask how to reuse a stream. From a simple web search, we can find many posts with this same issue ......

idoz ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部