android 跨进程点击方式总结

4,483 阅读5分钟

我们在日常的测试中,经常需要模拟用户点击等操作来实现模拟用户各种输入功能,在这里归纳总结一下几种点击方式,以及它们各自的优缺点,目前实现跨进程点击方式大致会有一下四种方式

  • 一 adb shell 命令下输入 input命令,我们会看它支持的事件类型如下图:

我们重点来看一下input tap 命令,它是模拟用户触屏事件,具体的方式为:
input tap x,y;x,y是要点击的坐标点,我们再来看看在我们的程序代码中怎么调用

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进程建立通信,下面我们就来实现它
    首选判断一下monkey进程是否已经启,已经运行的话将它kill后,再次启用。
    接下来我们就可以创建一个socket,连接到3131端口,也就是们刚才启动的monkey进程。
    连上Monkey进程之后,我们就可以发送点击事件了,monkey的点击事件时有down和up两部分组成的,格式如下:
    touch down x y
    touch 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,
    更加详细的原因可以参考www.hizher.com/pageContent…
  • 四:sendevent方式:
    Android提供了这两个方便的工具来处理输入事件
    getevent:可以查看设备的输入信息
    sendevent:注入输入事件
    使用getevent获取设备的输入信息:
    这是N5设备上的信息(不同设备的信息可能不同),可以看到N5手机中6个event设备, /dev/input/event1是发送触摸的设备,我们点击屏幕的某一点,输出下列信息
    /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的时候发送的信号量
    更多的参考:多点触控设备使用以下Linux输入事件
    每个sendevent命令都需要4个参数
    device_name (string)
    event_type (decimal int)
    event_code (decimal int)
    value (decimal int)
    上面的坐标坐标是十六进制转化为十进制后,发送点击事件
    sendevent /dev/input/event1 3 53 395
    sendevent /dev/input/event1 3 54 1321
    sendevent /dev/input/event1 0 0 0
    这种方式,每个设备的事件类型不一致,需要匹配不同的机型.