安卓adb模拟输入太慢的几个解决方法

前言
起初通过了adb shell input 来控制安卓的输入的时候,发现 adb shell input 非常慢,网上说 adb input 输入时要对本地进行 I/O 处理,通过 getevent 发现 adb 并没有对 event 进行操作。在这里我使用我之前写的 python自动玩连连看 来举个栗子,下面这个栗子使用的是夜神模拟器,Android 5。

注意
这篇讲如何使用 adb ,必须要对手机进行 root ,推荐使用安卓模拟器来实验。

问题
如果单纯的用 Python 进行控制 adb 的话,只用 adb 速度几乎一秒钟点击一次。

Python 伪代码:[Github]
def run_list(setp_list):
    ...
    for setps in setp_list:
        for y,x in setps:
            tx = 131 + (x-1) * 74
            ty = 146 + (y-1) * 91
            subprocess.call("adb shell input tap " + str(tx) + " " + str(ty) , shell=True)
在这个情况下,对一些游戏场景还是不友好,比如玩连连看,到底要点到什么时候呢?

改进一:sendevent
在 Linux 中,有一个 /dev/input/ 的目录,里面的有各种 event ,这些 event 所触发的事件都是来自外部的各个输入设备,在 /proc/bus/input/devices 可以看到当前系统的各个输入设备。[Github]
root@shamu:/ # cat /proc/bus/input/devices                        
...
I: Bus=0000 Vendor=1234 Product=0001 Version=0001
N: Name="Android_Input"  // 安卓输入事件
P: Phys=
S: Sysfs=/devices/virtual/input/input4
U: Uniq=
H: Handlers=sysrq rfkill kbd mouse0 event4 // 可以看到安卓对应输入的是event4事件
B: PROP=0
B: EV=f
B: KEY=420 0 10000 ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff fffffffe
B: REL=140
B: ABS=4600000 0

在 android 中可以使用 getevent 来截取各个 event 输入的事件:getevent /dev/input/eventX,如果不指定事件入口,就会截取所有事件。

由于我们要截取按下屏幕时的输入事件,也就是要截取 Android_Input 对应的 event4 事件,通过大量点击观察,除了 XXX YYY 之外,其他地方完全不变。通过多次观察得知 XXX 为按下的X坐标,YYY为按下时的Y坐标。也就是说一个点击回合产生 12 个 event。[Github]
root@shamu:/ # getevent /dev/input/event4                                  
0001 014a 00000001
0003 0035 00000XXX
0003 0036 00000YYY
0000 0002 00000000
0000 0000 00000000
0000 0002 00000000
0000 0000 00000000
0001 014a 00000000
0003 0035 00000000
0003 0036 00000000
0000 0002 00000000
0000 0000 00000000
这个时候我们可以用 sendevent 来进行模拟。

值得注意的是,getevent 输出的都是16进制,而 sendevent 使用的则是10进制 。这样就可以写一个 sh 脚本用 adb 上传上去然后使用 adb shell 执行。
                                0            300                  1
例如:第一行是 0001 014a 00000001 那么则是 sendevent /dev/input/event4  0 330 1

Python 生成 sh 的伪代码:[Github]
def mane_make_sh_sendevent(x,y):
    mane_sh = open('mane_sendevent.sh','a+')
    mane_sh.writelines("sendevent /dev/input/event4 1 330 1"+'\n')
    mane_sh.writelines("sendevent /dev/input/event4 3 53 %s" % (x)+'\n')
    mane_sh.writelines("sendevent /dev/input/event4 3 54 %s" %(y) +'\n')
    mane_sh.writelines("sendevent /dev/input/event4 0 2 0"+'\n')
    mane_sh.writelines("sendevent /dev/input/event4 0 0 0"+'\n')
    mane_sh.writelines("sendevent /dev/input/event4 0 2 0"+'\n')
    mane_sh.writelines("sendevent /dev/input/event4 0 0 0"+'\n')
    mane_sh.writelines("sendevent /dev/input/event4 1 330 0"+'\n')
    mane_sh.writelines("sendevent /dev/input/event4 3 53 0"+'\n')
    mane_sh.writelines("sendevent /dev/input/event4 3 54 0"+'\n')
    mane_sh.writelines("sendevent /dev/input/event4 0 2 0"+'\n')
    mane_sh.writelines("sendevent /dev/input/event4 0 0 0"+'\n')
    mane_sh.close()
使用 python 生成了sh 用adb pull 上去 然后执行看看,比原来的 adb shell input 快了一倍,大概是0.5 秒点击一次,但是效果还是不太理想。

改进二:dd 大法
上面提到用 getevent 来截取 /dev/input/eventX ,按照这种逻辑下使用 sendevent 也只是模拟16进制输入到 /dev/input/eventX,那我使用更加底层的方法来用dd模拟输入呢?

在输入了命令之后,随便在 Android 里点几下,然后中断,得到一个 mane.bin
root@shamu:/ # dd if=/dev/input/event4 of=/sdcard/mane.bin                 
^C0+116 records in
15+0 records out
7680 bytes transferred in 4.859 secs (1580 bytes/sec)
然后输入 dd of=/dev/input/event4 if=/sdcard/mane.bin,在按下 [ENTER] 的那一刻发现屏幕上出现了指针,也就是说dd大法是可行的。

这个时候我就可以用Python 生成dd的文件,放上去运行,由于代码太长就不列出来了。[Github]

继续上面,使用刚刚录制好的 mane.bin 模拟输入到原来的 event4 上:
root@shamu:/ # dd of=/dev/input/event4 if=/sdcard/mane.bin                 
15+0 records in
15+0 records out
7680 bytes transferred in 0.090 secs (85333 bytes/sec)
发现了一个很严重的问题,刚我使用了 5 秒的时间点击,却用了 0.09 点击完了,如此快的速度?

原来在记录 event4 所发生的事件上,没有记录事件的事件,只记录了事件,当你不按任何按钮的时候,也就不会触发 event4 的事件,也就不会记录到 mane.bin 这个文件,所以 mane.bin 只有记录的事件,没有记录时间。

这就导致了dd大法点击如此快的问题,软件还没有反应过来,所以这个方法不能用

改进三:使用echo模拟16进制输出
既然一样也是 16 进制输入到事件里面,那么也就可以使用 echo 模拟 16 进制输入到 /dev/input/eventX,先用 dd大法 得到一个十六进制的文件,就如上面的 mane.bin 使用16进制打开看看:


通过 echo 输入16进制到 /dev/input/eventX 命令如下:
echo -e -n '\x01\x02\x03\x04\x05\x06\x83\x00\x01\x00\x4A\x01\x01\x00\x00\x00' > /dev/input/event4
Python 生成 echo 模拟16进制输出的伪代码:[Github]
def mane_make_echo(x,y):
    mane_sh = open('mane_echo.sh','a')
    mane_sh.writelines(r"echo -e -n '\x01\x02\x03\x04\x05\x06\x83\x00\x01\x00\x4A\x01\x01\x00\x00\x00' > /dev/input/event4"+'\n')
    sx = format(x,'x')
    sx = '0'*(4-len(sx)) + sx
    sx2 = sx[0:2]
    sx1 = sx[2:4]
    mane_sh.writelines(r"echo -e -n '\x01\x02\x03\x04\x05\x06\x83\x00\x03\x00\x35\x00\x%s\x%s\x00\x00' > /dev/input/event4" % (sx1,sx2)+'\n')
    sx = format(y,'x')
    sx = '0'*(4-len(sx)) + sx
    sx2 = sx[0:2]
    sx1 = sx[2:4]
    mane_sh.writelines(r"echo -e -n '\x01\x02\x03\x04\x05\x06\x83\x00\x03\x00\x36\x00\x%s\x%s\x00\x00' > /dev/input/event4" % (sx1,sx2)+'\n')
    mane_sh.writelines(r"echo -e -n '\x01\x02\x03\x04\x05\x06\x83\x00\x00\x00\x02\x00\x00\x00\x00\x00' > /dev/input/event4"+'\n')
    mane_sh.writelines(r"echo -e -n '\x01\x02\x03\x04\x05\x06\x83\x00\x00\x00\x00\x00\x00\x00\x00\x00' > /dev/input/event4"+'\n')
    mane_sh.writelines(r"echo -e -n '\x01\x02\x03\x04\x05\x06\x2D\x00\x00\x00\x02\x00\x00\x00\x00\x00' > /dev/input/event4"+'\n')
    mane_sh.writelines(r"echo -e -n '\x01\x02\x03\x04\x05\x06\x2D\x00\x00\x00\x00\x00\x00\x00\x00\x00' > /dev/input/event4"+'\n')
    mane_sh.writelines(r"echo -e -n '\x01\x02\x03\x04\x05\x06\xCD\x00\x01\x00\x4A\x01\x00\x00\x00\x00' > /dev/input/event4"+'\n')
    mane_sh.writelines(r"echo -e -n '\x01\x02\x03\x04\x05\x06\xCD\x00\x03\x00\x35\x00\x00\x00\x00\x00' > /dev/input/event4"+'\n')
    mane_sh.writelines(r"echo -e -n '\x01\x02\x03\x04\x05\x06\xCD\x00\x03\x00\x36\x00\x00\x00\x00\x00' > /dev/input/event4"+'\n')
    mane_sh.writelines(r"echo -e -n '\x01\x02\x03\x04\x05\x06\xCD\x00\x00\x00\x02\x00\x00\x00\x00\x00' > /dev/input/event4"+'\n')
    mane_sh.writelines(r"echo -e -n '\x01\x02\x03\x04\x05\x06\xCD\x00\x00\x00\x00\x00\x00\x00\x00\x00' > /dev/input/event4"+'\n')
    mane_sh.close()

由于echo执行完在0.3秒左右,这效果比 改进一 快多了,原来 0.5 秒钟点击一次,现在 1 秒钟大概点 3 次,也就是 0.3 秒左右点一次。

其他方法:写程序
要是写程序的话就不瓜这篇文章的事了,下次在讨论。

总结:
使用 echo 模拟16进制输出也许是最好的方法。

Comments