自動化測試或爬蟲腳本場景經常需要模拟使用者向手機裝置發送各種互動事件,最常見的就是點選事件,這裡面就有一個大前提就是你需要擷取點選位置的 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調試(安全設定)
按照提示,一步步操作,打開即可。
長按識别二維碼,即可關注我
原創推薦
高精度高仿「開眼」,這個開源項目值得學習
禁用 testOnly 屬性,解決 debug 包安裝失敗
解決 Adb Unavailable,嘗試了一千種方案後