我们在日常的测试中,经常需要模拟用户点击等操作来实现模拟用户各种输入功能,在这里归纳总结一下几种点击方式,以及它们各自的优缺点,目前实现跨进程点击方式大致会有一下四种方式
- 一 adb shell 命令下输入 input命令,我们会看它支持的事件类型如下图:
private BufferedInputStream stdin = null;private BufferedOutputStream stdout = null;private BufferedInputStream erroeOut = null;private static Process mProcess = null;public Shell(String su) throws IOException, InterruptedException { mProcess = Runtime.getRuntime().exec(su); this.stdin = new BufferedInputStream(mProcess.getInputStream()); this.stdout = new BufferedOutputStream(mProcess.getOutputStream()); this.erroeOut = new BufferedInputStream(mProcess.getErrorStream()); this.read();}public void write(String value) throws IOException { this.stdout.write((value + "\n").getBytes()); this.stdout.flush();}复制代码
上面是对执行shell命令的一个简单分装,下面我们只要传入对应的命令即可,比如我们点击屏幕上(100,200)这个点
Shell shell = new Shell("su");shell.write( "input tap 100 200" );复制代码
执行这个命令需要手机要有Root权限,adb shell input 相对来说使用比较简单,但是它执行效率比较慢。
- 二:monkey 大家知道Monkey使用来做压力测试的,其实当我们运行monkey的时候会启动“com.android.commands.monkey”这样一个进程,monkey的各种点击滑动操作都是在此进程中实现的,所以如果我们能够将事件发送到该进程中,那么我们就可以实现跨进程点击的能力了,我们来看看如何实现。 启动“com.android.commands.monkey”进程,可以是使用monkey -port 3131,monkey的进程启动后,怎么能够通信能,其实我们可以通过socket的方式来建立连接,通过连接127.0.0.1地址和端口3131就能与monkey进程建立通信,下面我们就来实现它
touch down x ytouch up x y复制代码
它不仅可以发送点击事件,也可以发送系统keycode等事件,monkey执行的效率上来说快了很多,但是有时后他的稳定性不够好,而且它和UiAutomator不能同时使用,两者相互冲突 - 三:Instrumentation方式: 如果我们在自己的应用可以通过下面的这种方式来发送一个点击事件,
long downTime = SystemClock.uptimeMillis();MotionEvent tapDownEvent = MotionEvent.obtain(downTime, downTime,MotionEvent.ACTION_DOWN, x, y, 0);MotionEvent tapUpEvent = MotionEvent.obtain(downTime+100,downTime+100, MotionEvent.ACTION_UP, x, y, 0);mInstrumentation.sendPointerSync(tapDownEvent);mInstrumentation.sendPointerSync(tapUpEvent);tapDownEvent.recycle();tapUpEvent.recycle();复制代码
可是如果要进程跨进程点击就不行了,它会报这个错误,Permission denied,injecting event from....造成这个错误的原因是因为我们从一个应用向另一个应用发送点击事件,而这两个应用的UID不同导致的。所以要想实现跨进程的能力,可以有两种方式,一是hook被测应用的进程,通过进程通讯的方式在被测应用中实现点击;另一种方式就是通过hook的方式修改测试应用的UID修改被测应用的UID,通过hook方式通过权限检查就是:hook中native InjectInputEvent,将uid的值改为0,这样就能够通过里面hasInjectPermission的权限验证了。findAndHookMethod("com.android.server.input.InputManagerService", lpparam.classLoader, "nativeInjectInputEvent", int.class, lpparam.classLoader.loadClass("android.view.InputEvent"), int.class, int.class, int.class, int.class, int.class, new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { XposedBridge.log("lzf called nativeInjectInputEvent:" + param.args[3]); XposedBridge.log("uid is :" + TargetUid + " " + param.args[3]); if ((Integer) param.args[3] == TargetUid) { XposedBridge.log("here:" + param.args[3]); param.args[3] = 0; } } });复制代码
TargetUid是我们要注入的应用的UID,更加详细的原因可以参考 - 四:sendevent方式:Android提供了这两个方便的工具来处理输入事件getevent:可以查看设备的输入信息sendevent:注入输入事件使用getevent获取设备的输入信息:
/dev/input/event1: EV_ABS ABS_MT_TRACKING_ID 00000005/dev/input/event1: EV_ABS ABS_MT_POSITION_X 0000018b/dev/input/event1: EV_ABS ABS_MT_POSITION_Y 00000529/dev/input/event1: EV_ABS ABS_MT_PRESSURE 00000030/dev/input/event1: EV_SYN SYN_REPORT 00000000/dev/input/event1: EV_ABS ABS_MT_POSITION_X 0000018a/dev/input/event1: EV_ABS ABS_MT_POSITION_Y 00000528/dev/input/event1: EV_SYN SYN_REPORT 00000000/dev/input/event1: EV_ABS ABS_MT_POSITION_X 00000189/dev/input/event1: EV_ABS ABS_MT_POSITION_Y 00000527/dev/input/event1: EV_ABS ABS_MT_PRESSURE 0000002e/dev/input/event1: EV_ABS ABS_MT_TOUCH_MAJOR 00000003/dev/input/event1: EV_SYN SYN_REPORT 00000000/dev/input/event1: EV_ABS ABS_MT_TRACKING_ID ffffffff/dev/input/event1: EV_SYN SYN_REPORT 00000000复制代码
看一下上面每一项的说明:ABS_MT_TRACKING_ID:报告触碰跟踪的ID,是一个非负的任意整数,用来分辨多个同时的操作。例如,当多个手指触碰设备,在手指还在屏幕上时每个手指绑定一个独立的跟踪ID,当手指离开屏幕后,跟踪ID可能被重新使用(可选项)ABS_MT_POSITION_X:触摸事件的X轴坐标(必选项)ABS_MT_POSITION_Y:触摸事件的y轴坐标(必选项)ABS_MT_PRESSURE:触摸事件压力大小或者信号强度(可选项)SYN_REPORT:当以触摸事件up的时候发送的信号量更多的参考:每个sendevent命令都需要4个参数device_name (string)event_type (decimal int)event_code (decimal int)value (decimal int)上面的坐标坐标是十六进制转化为十进制后,发送点击事件sendevent /dev/input/event1 3 53 395sendevent /dev/input/event1 3 54 1321sendevent /dev/input/event1 0 0 0复制代码
这种方式,每个设备的事件类型不一致,需要匹配不同的机型.