自动化测试或爬虫脚本场景经常需要模拟用户向手机设备发送各种交互事件,最常见的就是点击事件,这里面就有一个大前提就是你需要获取点击位置的 x、y 屏幕坐标。

如何获取以及如何发送事件,这里涉及到 Android 系统提供的 getevent 和 sendevent 操作知识点。

通过 getevent 和 sendevent 命令可以查看和触发系统的操作事件,包括触摸屏事件和虚拟物理按键事件。除此之外,input 命令也可以做到发送指令。

getevent

我们使用 USB 连接一台安卓手机设备后,通过 adb shell 进入 shell 交互式环境,输入命令 getevent -help 可以查看 getevent 命令的参数介绍:

yifeng:/ $ getevent -help
Usage: getevent [-t] [-n] [-s switchmask] [-S] [-v [mask]] [-d] [-p] [-i] [-l] [-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, props=64)
    -d: show HID descriptor, if available
    -p: show possible events (errs, dev, name, pos. events)
    -i: show all device info and possible events
    -l: label event types and names in plain text
    -q: quiet (clear verbosity mask)
    -c: print given number of events then exit
    -r: print rate events are received

参数很多,都有对应的英文解释,比如 -t 表示显示事件发生的事件,-l 表示显示事件类型和名称。我们先不加任何参数,了解一下 getevent 命令的用法。

执行 getevent 命令,开始查看接收到的事件信息。开头这一系列 add device 输入表示当前设备支持的事件类型。

yifeng:/ $ getevent
add device 1: /dev/input/event7
  name:     "uinput_nav"
add device 2: /dev/input/event6
  name:     "sdm660-snd-card Button Jack"
add device 3: /dev/input/event5
  name:     "sdm660-snd-card Headset Jack"
add device 4: /dev/input/event3
  name:     "uinput-goodix"
could not get driver version for /dev/input/mice, Not a typewriter
add device 5: /dev/input/event1
  name:     "ant_check-input"
add device 6: /dev/input/event4
  name:     "gpio-keys"
add device 7: /dev/input/event0
  name:     "qpnp_pon"
add device 8: /dev/input/event2
  name:     "NVTCapacitiveTouchScreen"

备注:上面打印的这段设备信息,通过执行 getevent -p 命令行也能拿到,需要注意的是,里面有个屏幕尺寸信息:

......
add device 8: /dev/input/event2
  name:     "NVTCapacitiveTouchScreen"
  events:
    KEY (0001): 008f  014a
    ABS (0003): 002f  : value 0, min 0, max 9, fuzz 0, flat 0, resolution 0
                0035  : value 0, min 0, max 1079, fuzz 0, flat 0, resolution 0
                0036  : value 0, min 0, max 2339, fuzz 0, flat 0, resolution 0
                0039  : value 0, min 0, max 65535, fuzz 0, flat 0, resolution 0
                003a  : value 0, min 0, max 1000, fuzz 0, flat 0, resolution 0
  input props:
    INPUT_PROP_DIRECT

其实是不准确的,不能直接拿来用的,获取屏幕分辨率等信息还是要通过这个命令获取:

adb shell dumpsys window displays

或者更简洁一点的:

adb shell wm size

说回正题,开始监听事件后,我们按一下手机音量上键,就会得到这么一段输入信息:

/dev/input/event4: 0001 0073 00000001
/dev/input/event4: 0000 0000 00000000
/dev/input/event4: 0001 0073 00000000
/dev/input/event4: 0000 0000 00000000

每一行代表一条指令,每条指令从左到右,依次表示:设备名称、事件类型、事件编码和事件携带的数据(十六进制表示)。

设备名称可以从 getevent 命令开头的一段输出中找到,事件类型和事件编码可以在 android 系统内核源码中对照查看:include/linux/input.h,源码仓库 git 地址为:

https://android.googlesource.com/kernel/msm.git/+/android-msm-hammerhead-3.4-kk-r1/include/linux/input.h

比如,input.h 文件中定义的事件类型有这些(十六进制):

/*
 * Event types
 */
#define EV_SYN          0x00
#define EV_KEY          0x01
#define EV_REL          0x02
#define EV_ABS          0x03
#define EV_MSC          0x04
#define EV_SW               0x05
#define EV_LED          0x11
#define EV_SND          0x12
#define EV_REP          0x14
#define EV_FF               0x15
#define EV_PWR          0x16
#define EV_FF_STATUS    0x17
#define EV_MAX          0x1f
#define EV_CNT          (EV_MAX+1)

可以看到,前面我们操作的音量上键使用到了 EV_SYN 和 EV_KEY 两个类型,对应 input.h 定义的事件编码(十进制)为:

#define KEY_VOLUMEDOWN      114
#define KEY_VOLUMEUP        115

注意,对比查看事件编码时,需要进行十六进制与十进制的转换,可以找个在线工具,比如:

https://tool.oschina.net/hexconvert

这样对比下来,我们就能理解 getevent 不带任何参数时输出信息的含义。不过还是有点繁琐,不如直接给到事件名字更加清晰一些。

比如,使用 -l 参数直接打印事件名称,再来看下同样的音量上键对应的输出信息,这样看起来是不是舒服多了:

yifeng:/ $ getevent -l
......
/dev/input/event4: EV_KEY       KEY_VOLUMEUP         DOWN
/dev/input/event4: EV_SYN       SYN_REPORT           00000000
/dev/input/event4: EV_KEY       KEY_VOLUMEUP         UP
/dev/input/event4: EV_SYN       SYN_REPORT           00000000

同样的,我们看下添加 -l 参数前后,手指触摸屏幕的事件输出情况。这是 getevent命令下触摸屏幕的输出信息:

/dev/input/event2: 0003 0039 0000008b
/dev/input/event2: 0003 0035 000003c2
/dev/input/event2: 0003 0036 00000134
/dev/input/event2: 0003 003a 000003e8
/dev/input/event2: 0001 014a 00000001
/dev/input/event2: 0000 0000 00000000
/dev/input/event2: 0003 003a 00000000
/dev/input/event2: 0003 0039 ffffffff
/dev/input/event2: 0001 014a 00000000
/dev/input/event2: 0000 0000 00000000

翻译一下,十六进制 0003 对应的事件类型为 EV_ABS,表示触摸屏幕对应的坐标相关数据,input.h 文件中的定义有:

#define ABS_MT_SLOT     0x2f    /* MT slot being modified */
#define ABS_MT_TOUCH_MAJOR  0x30    /* Major axis of touching ellipse */
#define ABS_MT_TOUCH_MINOR  0x31    /* Minor axis (omit if circular) */
#define ABS_MT_WIDTH_MAJOR  0x32    /* Major axis of approaching ellipse */
#define ABS_MT_WIDTH_MINOR  0x33    /* Minor axis (omit if circular) */
#define ABS_MT_ORIENTATION  0x34    /* Ellipse orientation */
#define ABS_MT_POSITION_X   0x35    /* Center X ellipse position */
#define ABS_MT_POSITION_Y   0x36    /* Center Y ellipse position */
#define ABS_MT_TOOL_TYPE    0x37    /* Type of touching device */
#define ABS_MT_BLOB_ID      0x38    /* Group a set of packets as a blob */
#define ABS_MT_TRACKING_ID  0x39    /* Unique ID of initiated contact */
#define ABS_MT_PRESSURE     0x3a    /* Pressure on contact area */
#define ABS_MT_DISTANCE     0x3b    /* Contact hover distance */

其实我们比较常用的就是触摸中心点的屏幕绝对坐标 x、y 值。添加 -l 参数,再来操作一遍,看下 getevent -l 命令的输出信息:

/dev/input/event2: EV_ABS       ABS_MT_TRACKING_ID   0000008a
/dev/input/event2: EV_ABS       ABS_MT_POSITION_X    000003b3
/dev/input/event2: EV_ABS       ABS_MT_POSITION_Y    0000014c
/dev/input/event2: EV_ABS       ABS_MT_PRESSURE      000003e8
/dev/input/event2: EV_KEY       BTN_TOUCH            DOWN
/dev/input/event2: EV_SYN       SYN_REPORT           00000000
/dev/input/event2: EV_ABS       ABS_MT_PRESSURE      00000000
/dev/input/event2: EV_ABS       ABS_MT_TRACKING_ID   ffffffff
/dev/input/event2: EV_KEY       BTN_TOUCH            UP
/dev/input/event2: EV_SYN       SYN_REPORT           00000000

除了两次触摸屏幕引发的事件携带数据上非常小的 x、y 值差异外,其他都是一样的。

sendevent

了解完 getevent 获取事件,通过 sendevent 就能够发送事件。还是使用 help 参数先看下介绍和用法:

yifeng:/ $ sendevent --help
usage: sendevent DEVICE TYPE CODE VALUE

Sends a Linux input event.

可以看到,sendevent 命令需要的四个参数(设备、类型、编码和数据)刚好就是 getevent 命令获取到的。那么模拟前面获取到的触摸屏幕事件,就是执行这样一组指令(需要全部完整执行完一组,不同手机设备参数也不同):

sendevent /dev/input/event2 0003 0039 00000095
sendevent /dev/input/event2 0003 0035 0000039b
sendevent /dev/input/event2 0003 0036 0000014a
sendevent /dev/input/event2 0003 003a 000003e8
sendevent /dev/input/event2 0001 014a 00000001
sendevent /dev/input/event2 0000 0000 00000000
sendevent /dev/input/event2 0003 003a 00000000
sendevent /dev/input/event2 0003 0039 ffffffff
sendevent /dev/input/event2 0001 014a 00000000
sendevent /dev/input/event2 0000 0000 00000000

需要注意的是,真机设备测试中,可能会遇到 sendevent 不起作用的情况,可能是没有 input 目录文件写入权限的问题,试着修改一下:

chmod 777 /dev/input/event2

input

sendevent 命令通过一组命令才能完成一个事件,而且参数复杂。相比较而言,input 命令就简单直接多了。

yifeng:/ $ input
Usage: input [<source>] <command> [<arg>...]

The sources are:
      dpad
      keyboard
      mouse
      touchpad
      gamepad
      touchnavigation
      joystick
      touchscreen
      stylus
      trackball

The commands and default sources are:
      text <string> (Default: touchscreen)
      keyevent [--longpress] <key code number or name> ... (Default: keyboard)
      tap <x> <y> (Default: touchscreen)
      swipe <x1> <y1> <x2> <y2> [duration(ms)] (Default: touchscreen)
      draganddrop <x1> <y1> <x2> <y2> [duration(ms)] (Default: touchscreen)
      press (Default: trackball)
      roll <dx> <dy> (Default: trackball)
      tmode <tmode>

比如,向手机上已经获取焦点的输入框输入一段文本内容,就是这样:

input text "hello world"

再比如,发送触摸屏幕事件,使用 tap 命令,提供 x、y 坐标即可:

input tap 100 500

input 命令还有 swipe、press 等其他用法,这里就不一一介绍了。

注意事项

前面提到的 getevent、sendevent 和 input 操作事件的命令,在部分手机上还存在另一个操作权限的问题。

如果你在操作时遇到这样的提示信息,比如 getevent 命令遇到权限拒绝提示:

yifeng:/ $ getevent
could not open /dev/input/event7, Permission denied
could not open /dev/input/event6, Permission denied
could not open /dev/input/event5, Permission denied
could not open /dev/input/event3, Permission denied
could not open /dev/input/mice, Permission denied
could not open /dev/input/event1, Permission denied
could not open /dev/input/event4, Permission denied
could not open /dev/input/event0, Permission denied
could not open /dev/input/event2, Permission denied

input 命令遇到 killed 提示信息:

yifeng:/ $ input text "hello world"
Killed

这个时候,只需要到手机「开发者选项」设置中,找到 USB 调试模块中的这个选项:

USB调试(安全设置)

按照提示,一步步操作,打开即可。

60b0d55af010d27a4805e83961b310d3.jpeg

长按识别二维码,即可关注我


原创推荐

高精度高仿「开眼」,这个开源项目值得学习

禁用 testOnly 属性,解决 debug 包安装失败

解决 Adb Unavailable,尝试了一千种方案后

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐