好了 本篇作为Android 系统调试系列第一篇内容 , 我一直以为和认为Android系统工程师水平,基本可以用 调试水平 调试问题技巧来判断高低。
调试工具手法技巧作为Android程序员的瑞士军刀 必须先磨好刀 。
一、概述
adb shell input
是一个命令,用于在Android设备上模拟用户输入。它可以用于自动化测试、调试和其他需要模拟用户输入的场景。通过adb shell input
命令,可以模拟按键、滑动、触摸等操作。例如,adb shell input tap x y
可以模拟在屏幕上点击坐标为(x,y)的位置。
adb shell input
命令的格式如下:
adb shell input [<source>] [-d DISPLAY_ID] <command> [<arg>...]
其中,<source>
是要模拟的事件的来源,可以是以下之一:
keyboard
: 键盘touchscreen
: 触摸屏trackball
: 轨迹球
如果不指定来源,默认使用以下规则:
text
: 触摸屏keyevent
: 键盘tap
: 触摸屏swipe
: 触摸屏press
: 轨迹球roll
: 轨迹球
-d DISPLAY_ID
是指定要模拟事件的显示器ID,如果不指定,默认使用以下规则:
keyevent
: 无效显示器(INVALID_DISPLAY)- 其他事件: 默认显示器(DEFAULT_DISPLAY)
<command>
是要模拟的事件类型,可以是以下之一:
text
: 输入文本keyevent
: 模拟按键tap
: 模拟点击swipe
: 模拟滑动press
: 模拟长按roll
: 模拟滚动
<arg>
是根据不同的事件类型指定的参数,例如坐标、按键码、文本内容等。
二、常用命令
2.1 输入文本
要在设备上输入文本,可以使用以下命令:
adb shell input text <string>
其中,<string>
是要输入的文本内容。注意,文本中不能包含空格或特殊字符,否则会导致错误或不完整的输入。如果需要输入空格或特殊字符,可以使用转义符\
或者使用按键事件代替。
例如,要在设备上输入"Hello World!",可以使用以下命令:
adb shell input text Hello\ World\!
或者
adb shell input keyevent 36 # H adb shell input keyevent 33 # e adb shell input keyevent 46 # l adb shell input keyevent 46 # l adb shell input keyevent 48 # o adb shell input keyevent 62 # SPACE adb shell input keyevent 55 # W adb shell input keyevent 48 # o adb shell input keyevent 46 # r adb shell input keyevent 37 # l adb shell input keyevent 32 # d adb shell input keyevent 56 # !
2.2 模拟按键
要在设备上模拟按键,可以使用以下命令:
adb shell input keyevent [--longpress|--doubletap] <keycode>
其中,--longpress
和--doubletap
是可选的修饰符,分别表示长按和双击。<keycode>
是要模拟的按键的代码,可以是数字或字符串。例如,要在设备上模拟返回键,可以使用以下命令:
adb shell input keyevent KEYCODE_BACK
或者
adb shell input keyevent 4
要查看所有可用的按键代码,请参考 KeyEvent 文档
(仅限于Google官方的镜像 如果自己系统源码中自定义keycode 查不到的 要看自己的系统代码)。
2.3 模拟点击
要在设备上模拟点击某个坐标点,可以使用以下命令:
adb shell input tap <x> <y>
其中,<x>
和<y>
是要点击的屏幕坐标。例如,要在设备上点击(100,200)这个点,可以使用以下命令:
adb shell input tap 100 200
2.4 模拟滑动
要在设备上模拟从一个坐标点滑动到另一个坐标点,可以使用以下命令:
adb shell input swipe <x1> <y1> <x2> <y2> [<duration>]
其中,<x1>
和<y1>
是滑动的起始坐标,<x2>
和<y2>
是滑动的结束坐标,<duration>
是滑动的持续时间,单位是毫秒,可选参数。例如,要在设备上从(100,200)滑动到(300,400),并持续2秒,可以使用以下命令:
adb shell input swipe 100 200 300 400 2000
2.5 模拟长按
要在设备上模拟长按某个坐标点,可以使用以下命令:
adb shell input press <x> <y>
其中,<x>
和<y>
是要长按的屏幕坐标。例如,要在设备上长按(100,200)这个点,可以使用以下命令:
adb shell input press 100 200
2.6 模拟滚动
要在设备上模拟滚动屏幕,可以使用以下命令:
adb shell input roll <dx> <dy>
其中,<dx>
和<dy>
是水平和垂直方向的滚动距离。例如,要在设备上向右滚动50个像素,向下滚动100个像素,可以使用以下命令:
adb shell input roll 50 100
三、进阶用法
3.1 组合按键的用法
要在设备上模拟组合按键,例如Ctrl+C、Alt+Tab等,可以使用以下命令:
adb shell input keyevent --longpress <keycode1> <keycode2> [<keycode3> ...]
或者
adb shell input keycombination [-t duration(ms)] <keycode1> <keycode2> [<keycode3> ...]
其中,--longpress
和-t duration(ms)
是可选的修饰符,分别表示长按和指定按键间隔时间。<keycode1>
是要模拟的第一个按键的代码,<keycode2>
是要模拟的第二个按键的代码,以此类推。注意,在使用keycombination
命令时,按键顺序很重要。例如,要在设备上模拟Ctrl+C,可以使用以下命令:
adb shell input keyevent --longpress KEYCODE_CTRL_LEFT KEYCODE_C
或者
adb shell input keycombination -t 100 KEYCODE_CTRL_LEFT KEYCODE_C
要查看所有可用的按键代码,请参考 KeyEvent 文档。
3.2 长按power键
要在设备上模拟长按power键,可以使用以下命令:
adb shell input keyevent --longpress KEYCODE_POWER
或者
adb shell input keyevent --longpress 26
这样可以弹出关机菜单或者其他功能,根据不同设备的设置而定。
如果想要直接关机或者重启,可以使用以下命令:
adb shell reboot -p # 关机 adb shell reboot # 重启
3.3 输入中文
要在设备上输入中文,不能直接使用以下命令:
adb shell input text <string>
因为这个命令只支持ASCII字符集,不支持中文或其他非ASCII字符。如果强行输入中文,会导致乱码或者错误。
要输入中文,有两种方法:
- 方法一:使用
am broadcast
命令发送一个广播,携带要输入的中文内容作为extra数据,然后在设备上安装一个接收该广播的应用,并在接收到广播后调用InputMethodManager
的showSoftInput()
方法来弹出软键盘,并使用Instrumentation
的sendStringSync()
方法来输入中文。这种方法需要编写一个应用,并且需要root权限。 - 方法二:使用
adb push
命令将要输入的中文内容保存到一个文件中,并推送到设备上的某个目录下,然后使用cat
命令读取该文件的内容,并通过管道符|
将其传递给input text
命令。这种方法不需要编写应用,也不需要root权限。
例如,要在设备上输入"你好",可以使用以下命令:
echo "你好" > hello.txt # 在电脑上创建一个文件并写入你好(windows直接写进去就完事) adb push hello.txt /sdcard/ # 将文件推送到设备上的sdcard目录下 adb shell cat /sdcard/hello.txt | tr -d '\n' | xargs -0 adb shell input text # 读取文件内容并传递给input text命令
3.4 代码模拟发送按键
1. 方法1:
使用Instrumentation类的sendKeySync()方法,这个方法会创建一个KeyEvent对象,并通过Instrumentation类的injectInputEvent()方法将其注入到系统中。这个方法需要具有INJECT_EVENTS权限,否则会抛出安全异常。这个方法适用于普通的按键事件,例如:
Instrumentation inst = new Instrumentation(); inst.sendKeySync(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_A)); inst.sendKeySync(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_A));
2. 方法2:
使用InputManager类的injectInputEvent()方法,这个方法会直接将一个KeyEvent对象注入到系统中,不经过Instrumentation类的处理。这个方法也需要具有INJECT_EVENTS权限,否则会抛出安全异常。这个方法适用于更复杂的按键事件,例如组合键、长按、双击等,需要自己创建多个KeyEvent对象,并设置相应的属性和时间间隔,并调用相应的方法进行注入。例如:
InputManager im = InputManager.getInstance(); // 模拟发送Ctrl+C组合键 final long now = SystemClock.uptimeMillis(); // 按下Ctrl im.injectInputEvent(new KeyEvent(now, now, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_CTRL_LEFT, 0, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_KEYBOARD), InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); // 按下C im.injectInputEvent(new KeyEvent(now, now, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_C, 0, KeyEvent.META_CTRL_ON | KeyEvent.META_CTRL_LEFT_ON, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_KEYBOARD), InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); // 抬起C im.injectInputEvent(new KeyEvent(now, now, KeyEvent.ACTION_UP, KeyEvent.KEYCODE_C, 0, KeyEvent.META_CTRL_ON | KeyEvent.META_CTRL_LEFT_ON, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_KEYBOARD), InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); // 抬起Ctrl im.injectInputEvent(new KeyEvent(now, now, KeyEvent.ACTION_UP, KeyEvent.KEYCODE_CTRL_LEFT, 0, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_KEYBOARD), InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
3. 方法3:
随便搞个ShellUtils工具类 , 网上代码非常多 这种方法要系统root的。
ShellUtils.execCommand("input keyevent 224",true);
系统应用或具有系统签名的应用(app)以及app的UID和GID组对于能否模拟发送输入事件具有影响。通常,只有属于system或shell组的app才能将输入事件注入到系统中。可以使用Process类的myUid()和myGid()方法获取app的UID和GID,并使用Process类的getGidForName()方法获取system或shell组的GID。如果app的UID是0(即root用户)或者GID是system或shell组的GID,那么就可以进行输入事件的注入;否则不可以。
四、注意事项
在使用adb shell input
命令时,有一些注意事项需要了解:
adb shell input
命令只能模拟基本的输入事件,不能模拟复杂的手势或多点触控等操作。adb shell input
命令只能模拟用户在设备上的操作,不能模拟设备的物理状态,例如旋转、摇晃、倾斜等。adb shell input
命令只能模拟用户在当前屏幕上的操作,不能模拟用户在其他应用或界面上的操作。adb shell input
命令只能模拟用户在设备上的操作,不能模拟用户在外接设备上的操作,例如键盘、鼠标、手柄等。adb shell input
命令只能模拟用户在设备上的操作,不能模拟用户对设备的语音或人脸等输入。
如果需要模拟更高级或更复杂的输入事件,可以考虑使用其他工具或方法,例如:
- 使用
uiautomator
或monkeyrunner
等自动化测试框架来编写和执行测试脚本。 - 使用
“Android投屏工具”
或ARDC
等工具来远程控制设备,并在电脑上进行操作。
五、源码解析
adb shell input
命令在Android 13中已经不再是一个单独的可执行文件,而是作为一个ShellCommand的子类,被集成到了InputManagerService中。这样做的好处是可以减少进程间通信的开销,提高输入事件的处理效率。源码改到了frameworks/base/services/core/java/com/android/server/input/InputShellCommand.java
中,在这个文件中定义了一个InputShellCommand类,继承了ShellCommand类,并实现了各种事件类型的处理方法。例如:
- onCommand(String cmd):这个函数是ShellCommand类的抽象方法,用于根据输入的命令和参数来执行相应的操作。这个函数首先解析输入源和显示ID(如果有的话),然后根据命令的类型,调用不同的runXXX方法来执行具体的操作。
- onHelp():这个函数是ShellCommand类的抽象方法,用于打印帮助信息。这个函数向输出流输出adb shell input命令的用法和参数说明。
--------只关注重点函数 下面的 - runXXX(int inputSource, int displayId):这些函数用于执行不同类型的命令,例如text、keyevent、tap、swipe等。它们会获取相应的参数,并调用sendXXX方法发送相应的输入事件。
- sendXXX(int inputSource, ...):这些函数用于发送不同类型的输入事件,例如文本、按键、点击、滑动等。它们会根据参数创建KeyEvent或MotionEvent对象,并设置其属性,并调用injectKeyEvent或injectMotionEvent方法将其注入到系统中。
- injectKeyEvent(KeyEvent event):这个函数用于同步注入一个键盘事件到系统中,它会使用InputManager类的getInstance()和injectInputEvent()方法来实现。
- injectKeyEventAsync(KeyEvent event):这个函数用于异步注入一个键盘事件到系统中,它也会使用InputManager类的getInstance()和injectInputEvent()方法来实现,但是使用了INJECT_INPUT_EVENT_MODE_ASYNC模式,以便让系统的拦截器能够处理该事件。
- injectMotionEvent(int inputSource, int action, long downTime, long when,
float x, float y, float pressure, int displayId):这个函数用于注入一个动作事件到系统中,它会使用MotionEvent类的obtain()方法来创建一个MotionEvent对象,并设置其输入设备ID、输入源和显示ID等属性,并使用InputManager类的getInstance()和injectInputEvent()方法来实现。
所有的事件处理方法最终都会调用InputManagerService的内部方法来将输入事件注入到系统中。InputManagerService的源码位于frameworks/base/services/core/java/com/android/server/input/InputManagerService.java
中,在这个文件中定义了一个InputManagerService类,实现了各种输入事件的管理和分发逻辑。(太复杂了 有空 再分析这块内容)
通过以上源码分析,可以了解到Android 13系统是如何接收、分发和处理adb shell input命令模拟的输入事件的。这些源码也可以帮助大家更好地理解adb shell input命令的原理和限制。
六、总结
本章重点Android adb input 调试命令的使用和源码分析。可以利用这些命令来模拟用户在设备上的各种操作,从而进行测试、调试或其他目的。同时,也可以通过阅读源码来深入了解Android 13系统的输入事件管理机制。希望这篇文章对大家有所帮助,如果还有其他问题,请随时向我提问 如果有错误请指正哈 !。