Loading [MathJax]/jax/input/TeX/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Android手机上用户操作模拟方法的研究与实现

Android手机上用户操作模拟方法的研究与实现

作者头像
腾讯移动品质中心TMQ
发布于 2018-02-02 08:18:41
发布于 2018-02-02 08:18:41
4.6K0
举报

一、 问题背景

最近研究了一下Android手机上用户操作的模拟方法, 有一些心得与大家分享下。

之所以去研究Android手机上用户操作的模拟方法,是因为最近做毕业设计,想尝试开发Android的UI自动化测试。最开始使用MonkeyRunner来录制脚本,开发过程中发现在MonkeyRunner上录制时,模拟拖拽的操作不方便。

接着我又尝试自己通过Monkey中的同样的方法进行用户操作的模拟,结果运行的时候出了Injecting to another application requires INJECT_EVENTS permission异常,甚至是加了INJECT_EVENTS权限也报同样的错误。在网上查询后才知道Android系统2.0版本以前并没有对跨进程间发送事件进行限制,之后的版本从安全方面考虑加上了限制。Monkey之所以不会出这个异常,是因为它与系统一起发布,拥有和系统一样的签名。

因此笔者想是否有其他的方法可以跨进程模拟用户操作,且还能在不同的手机上适用。

二、 目前现状

经过一段时间学习,笔者发现目前大部分的Android自动化测试工具使用两种方法来模拟用户操作,一是通过Monkey或者Monkey同样的方法来完成,另外一种是通过读写Android系统的输入输入设备节点文件来完成。

首先看下Monkey是如何实现用户模拟的。

Android2.3.7中的Monkey实现了三种途径向系统插入Input事件:

1. 随机生成Input事件,对应类为MonkeySourceRandom;

2. 从脚本读取Input事件,对应类为MonkeySourceScript ;

3. 从socket读取Input事件,对应类为MonkeySourceNetwork ;

这三个类均继承自MonkeyEventSource类,它们将从不同源获取到的event放进内部queue,在Monkey的runMonkeyCycles()循环中取出这些event插入系统。如下面的代码所示:

代码语言:js
AI代码解释
复制
MonkeyEvent ev = mEventSource.getNextEvent();            
if (ev != null) {
int injectCode = ev.injectEvent(mWm, mAm, mVerbose);  
}

MonkeyEvent模拟系统里面的Input Event有MonkeyKeyEvent、MonkeyMotionEvent、MonkeyPowerEvent、MonkeyNoopEvent、MonkeyFlipEvent、MonkeyCommandEvent、MonkeyActivityEvent。每个Event都实现了 injectEvent()函数,每种事件根据类型实现了具体的事件插入。 例如 MonkeyCommandEvent是执行一个命令,代码如下:

代码语言:js
AI代码解释
复制
    public int injectEvent(IWindowManager iwm, IActivityManager iam, int verbose)
{        
if (mCmd != null)
{
//Execute the shell command            
try {                
java.lang.Process p = Runtime.getRuntime().exec(mCmd);
int status = p.waitFor();
System.err.println("// Shell command " + mCmd + " status was " + status);
} catch (Exception e) {
System.err.println("// Exception from " + mCmd + ":");
System.err.println(e.toString());
}        
}      
return MonkeyEvent.INJECT_SUCCESS;    
       }[1]

第二方法是通过读写Android系统的输入设备节点文件来完成。从网上可以找到较多的文档描述如何获取和模拟用户的操作,但是没有很好的普遍适用的代码实现。该方法主要是基于Android的输入输出子系统。先下Android的Input子系统是如何工作的。

Android系统本质上是Linux系统,在Linux中输入子系统是由输入子系统设备驱动层、输入子系统核心层(Input Core)和输入子系统事件处理层(Event Handler)组成。其中设备驱动层提供对硬件各寄存器的读写访问和将底层硬件对用户输入访问的响应转换为标准的输入事件,再通过核心层提交给事件处理层;而核心层对下提供了设备驱动层的编程接口,对上又提供了事件处理层的编程接口;而事件处理层就为我们用户空间的应用程序提供了统一访问设备的接口和驱动层提交来的事件处理[2]。如图1所示, 图中的/dev/input/eventX就是所谓输入设备节点文件,它是硬件设备提供给系统的设备接口文件。

图1 Linxu系统的Input子系统示意图

再来看看Linux的系统是如何从设备节点文件获取事件的。系统进程的EventHub会读取输入设备节点文件的事件,而InputReaderThread从EventHub中不断读取事件,并通过pipe传递给InputDispatcherThread,由InputDispatcherThread分发到各应用程序,由它们去处理。如图2所示。

图2 底层按键事件获取的简单流程[3]

如何通过读写设备节点文件来模拟用户操作呢? 以Touch事件为例,在读写之前需要知道触摸屏对应的设备节点文件是哪一个。在Android上我们可以通过命令adb shell cat /proc/bus/input/devices来查看,在笔者三星GT-i9300上的运行结果如图3所示。在这个手机上/dev/input/event2就是触摸屏的设备节点文件。找到了这个设备文件后,可以通过命令adb shell getevent /dev/input/event2读这个设备的事件信息,轻点一下手机屏幕,再查看getevent接收到的数据,结果如图4所示。 Android系统地单击操作其实是由一系列的事件组成的,图中的0003 0035 00000190中0003是EV_ABS的代码,0035是多点触摸的ABS_MT_POSITION_X事件代码,00000190代表坐标的值。 详细的代码定义可以参考Android的input.h的源代码或者访问链接。现在得到了这些事件序列,只要我们以同样的序列通过Android系统的sendevent写到系统中就会触发单击操作了。但是这样还不能达到方便的模拟用户操作,并且上述方法还不具有普适性。

图3 查看Input设备节点文件

图4 三星GT-i9300手机上单击屏幕时触发的事件

三、 研究内容与结果

为了让程序可以在不同的手机上都可以运行,代码需要解决以下两个问题:

1. 由于不同厂家的设备都不一样,所以event的文件也各不相同,程序首先需要找到这个文件。

2. 由于Android使用的touch的协议也不一样,代码必须要很好的兼容不同的手机。

首先需要做的是找到手机中的touch screen对应的event文件。示例代码如下:

代码语言:js
AI代码解释
复制
for(int i=0; i<EV_MAX; i++)
{
       memset(devicePath, 0, sizeof(devicePath));
       memset(buf, 0, sizeof(buf));
       sprintf(devicePath, "/dev/input/event%d", i);
       //LOGI(LOG_TAG, devicePath);
       if ((fd = open(devicePath, O_RDONLY)) < 0)
       {
              LOGI(LOG_TAG, "Open device failed");
              //打开该文件失败,继续打开下一个
              continue;
       }
       // EVIOCGNAME参考input.h
       ioctl(fd, EVIOCGNAME(sizeof(buf)), buf); 
       //根据设备的名称来查找
       if( strstr(buf, “touchscreen”))
       {
              memcpy(device->deviceName, buf, sizeof(buf));
              //找到了,把event文件的路径保留下来
              memcpy(device->devicePath, devicePath, sizeof(devicePath));}
}

其次就是解决对touch协议的支持问题。Android系统2.0以前不支持multi-touch事件,2.0以后都支持了。当multi-touch协议和single-touch协议在手机上被使用时,单击屏幕触发的将是multi-touch协议的坐标,而single-touch协议事件的坐标将被忽略[4];另外multi-touch事件还分为Type A[5]和Type B,所以代码需要对以上情况进行支持。

在我的代码中首先是读取了设备的设置,判断出手机是multi-touch还是single-touch,以及设备支持的事件等。示例代码片段如下,该代码是模仿Android系统的EventHub.cpp写的,大家如果有兴趣可以去看看这个文件。

代码语言:js
AI代码解释
复制
//*******************
//在这之前用ioctl打开我们找到的 /dev/input/event*/ 文件
//获取ABS的设置信息,并根据ABS信息判断哪些Event是支持的
ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(absBitmask)), absBitmask);
ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(keyBitmask)), keyBitmask);
if( test_bit(ABS_MT_POSITION_X, absBitmask)
&& test_bit(ABS_MT_POSITION_Y, absBitmask))
{
       LOGI(LOG_TAG, "This input device is a multi-touch device");
       device->isMultiTouch = true;
       device->supportMTEvent->bPositionX = true;
       device->supportMTEvent->bPositionY = true;
       //以下开始判断哪些事件是被该设备支持的                  
       if( test_bit(ABS_MT_SLOT, absBitmask))
       {
              device->supportMTEvent->bSlot = true;
              LOGI(LOG_TAG, "ABS_MT_SLOT is supported");
       }
       ……
}else if (test_bit(BTN_TOUCH, keyBitmask)
              && test_bit(ABS_X, absBitmask)
              && test_bit(ABS_Y, absBitmask)){
       LOGI(LOG_TAG, "This input device is a single-touch device");
       device->isSingleTouch = true;
}

目前已经获得了设备支持的协议了,那我们就可以将touch操作封装成函数给各种操作调用了,在我的代码实现了一个函数,它负责根据系统支持的事件来发送对应的事件。示例代码如下:

代码语言:js
AI代码解释
复制
/***
*     该函数是用于判断某个触摸事件是否支持,如果支持我们就发送它
*     将判断放到这个函数是因为在touch和drag等函数就不用再去判断了
**/
int TouchUtil::sendTouchEvent(const int fd, const int eventId, const int value)
{
       if (fd <= 0)
       {
              return RET_ERROR;
       }
       switch(eventId)
       {
              case ABS_MT_SLOT:
                     if( touchDevice->supportMTEvent->bSlot)
                     {
                            writeEvent(fd, EV_ABS, ABS_MT_SLOT, value);
                     }
                     break;
              case ABS_MT_TOUCH_MAJOR:
                     if( touchDevice->supportMTEvent->bTouchMajor)
                     {
                            writeEvent(fd, EV_ABS, ABS_MT_TOUCH_MAJOR, value);
                     }
           ……
              default:
                     break;
       }
       return RET_OK;
}

此外由于普通的single touch事件基本上可以分成按下手指、移动手指、释放手指的操作。由于在前面的sendTouchEvent函数里面已经对支持的事件做了判断,所以在下面的函数中直接给了touch协议事件的全集。示例代码如下:

代码语言:js
AI代码解释
复制
       int TouchUtil::down(const int x, const int y, const int slot, const int trackintId)
{
       if(!isDeviceReady)
       {
              return RET_ERROR;
       }
       if( touchDevice->isMultiTouch)
       {
              int value = (rand() % 16) + 1;
              //按下一个点
              sendTouchEvent(fd, ABS_MT_SLOT, slot);
              sendTouchEvent(fd, ABS_MT_TRACKING_ID, trackintId);
              sendTouchEvent(fd, ABS_MT_POSITION_X, x);
              sendTouchEvent(fd, ABS_MT_POSITION_Y, y);
              sendTouchEvent(fd, ABS_MT_TOUCH_MAJOR, value);
              sendTouchEvent(fd, ABS_MT_TOUCH_MINOR, value);
              sendTouchEvent(fd, ABS_MT_WIDTH_MAJOR, value);
              sendTouchEvent(fd, ABS_MT_WIDTH_MINOR, value);
              sendTouchEvent(fd, ABS_MT_ORIENTATION, value);
              sendTouchEvent(fd, ABS_MT_TOOL_TYPE, BTN_TOOL_FINGER);
              sendTouchEvent(fd, ABS_MT_BLOB_ID, value);
              sendTouchEvent(fd, ABS_MT_PRESSURE, value);
              sendTouchEvent(fd, ABS_MT_DISTANCE, value);
              sendTouchEvent(fd, ABS_MT_TOOL_X, value);
              sendTouchEvent(fd, ABS_MT_TOOL_Y, value);
              sendTouchEvent(fd, SYN_MT_REPORT, 0);
              sendTouchEvent(fd, SYN_REPORT, 0);
       }else
       {
              writeEvent(fd, EV_ABS, ABS_X, x);
              writeEvent(fd, EV_ABS, ABS_Y, y);
              writeEvent(fd, EV_ABS, ABS_PRESSURE, 1);
              writeEvent(fd, EV_KEY, BTN_TOUCH, 1);
              writeEvent(fd, EV_SYN, SYN_REPORT, 0);
       }
       return RET_OK;
}

我再以同样的方式实现了移动手指和松开手指的函数后,就只需要将这些函数封装成各种操作的函数就可以供调用了。如下面代码所以之的单击和拖动的函数, 也可以根据自己的需要实现长按,双击等操作。

代码语言:js
AI代码解释
复制
int TouchUtil::touch(const int x, const int y, const int interval)
{
       if(!isDeviceReady)
       {
              return RET_ERROR;
       }
       g_trackingID ++;
       down(x, y, 0, g_trackingID);
       usleep(interval);
       up(x, y, 0);
}
int TouchUtil::pan(const int fromX, const int fromY, const int toX, const int toY)
{
       if(!isDeviceReady)
       {
              return RET_ERROR;
       }
       char msg[128];
       memset(msg, 0, sizeof(msg));
       sprintf(msg, "pan(%d, %d, %d, %d)", fromX, fromY, toX, toY);
       LOGI(LOG_TAG, msg);
       g_trackingID ++;
       down(fromX, fromY, 0, g_trackingID);
       usleep(5);
       int deltaX = (toX - fromX) / 10;
       int deltaY = (toY - fromY) / 10;
       int tempX = fromX;
       int tempY = fromY;
       for(int i=0; i<9; i++)
       {
              tempX += deltaX;
              tempY += deltaY;
              pressAndMove(tempX, tempY, 0, g_trackingID);
              usleep(5);
       }
       up(toX, toY, 0);
       return      RET_OK;
}

为了在其他的测试程序中也能使用这种方法,我将以上实现的代码放到了一个jni工程中,编译成了一个.so文件。

四、 研究结果及需要继续解决的问题

笔者按以上的方法实现的用户touch模拟操作,在三星GT-i9300、MOTO Droid、小米等手机上进行了测试,并以SOSO地图作为实验对象,确认是可以较好的模拟用户操作。

在以上的研究中也还有一些待解决的问题,主要包括:

1. 不是所有的手机设备都完全遵循multi-touch的协议。例如multi-touch协议要求touch结束必须以SYN_MT_REPORT以及SYN_REPORT结束,大部分的手机都遵循该协议,可是示例中的三星GT-i9300手机结束的时候并没有发送SYN_MT_REPORT。目前我暂时的解决对这类手机放到一个适配表里,需要继续研究更好的方法。

2. 由于研究的时间较短,目前只是对少量的手机做到了zoom in和zoom out的模拟,还没有完全研究出针对大部分手机都适用的双指以及多指操作的模拟方法,这也是需要继续研究解决的问题。

以上就是我的一点心得,有什么不对或者可以改进的地方请大家不吝赐教,共同进步。

五、 参考文献

[1] Android_Input分析. http://wenku.baidu.com/view/1f6650906bec0975f465e22f.html

[2] S3C2440上touch screen触摸屏驱动.http://www.linuxidc.com/Linux/2011-10/45460.htm

[3] Android View动画与按键处理的分析. http://km.oa.com/group/533/articles/show/126065?kmref=search

[4] Android Touch Devices. http://source.android.com/tech/input/touch-devices.html

[5] Linux的Multi-touch协议. https://www.kernel.org/doc/Documentation/input/multi-touch-protocol.txt

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2016-02-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 腾讯移动品质中心TMQ 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
如何跨app对其他应用进行虚拟点击
可能很多人在Android开发中会有这样的想法,如何模拟屏幕点击,向另外的app发送点击事件,来达到某种目的。 就像我们平时用 adb shell sendevent命令一样,模拟用户的一组输入操作,来实现自动化测试。
PhoenixZheng
2018/08/07
2.2K0
Android getevent,sendevent,input keyevent
getevent和sendevent是Android系统自带的获取设备的收发事件和模拟设备事件进行自动话测试。而input keyevent也在自动话测试中有很大的作用,用于模拟常用按键等。接下来就一一是实践角度分析此三个工具的使用方法。
DragonKingZhu
2020/03/24
2.1K0
Android  getevent,sendevent,input keyevent
Android逆向工程
在Root前提下,我们可以使用Hooker方式绑定so库,通过逆向方式篡改数值,从而达到所谓破解目的。然而,目前无论是软件加固方式,或是数据处理能力后台化,还是客户端数据真实性验证,都有了一定积累和发展,让此“懒技术”不再是破解修改的万金油。再者,阅读汇编指令,函数指针替换,压栈出栈等技术需要一定技术沉淀,不利于开发同学上手。 两年前,也是因为懒,很懒,非常懒,堆积了足够的动力,写了一个基于人工模拟方式,对一个特定规则的游戏进行暴力破解。我们都知道,人工模拟方式,绕过了大量防破解技术,只要还是人机交互模式,
xiangzhihong
2018/02/05
1.2K0
Android逆向工程
嵌入式输入系统应用编程
先来了解什么是输入设备? 常见的输入设备有键盘、鼠标、遥控杆、书写板、触摸屏等等,用户通过这些输入设备与 Linux 系统进行数据交换。
韦东山
2020/09/30
1.5K0
黑盒测试中关键截图如何打点
Android黑盒测试过程中如何进行有效的打点是我们经常遇到的问题,我们一般会在脚本内部进行数据打点,也可以使用其他进程录屏或截图。那我们如何选取合适的方式进行打点记录呢?下图是对常用打点方式的统计!对于测试开发人员来说有效的关键截图信息是最直观的数据,可以很快定位问题场景!本文重点介绍如何在Shell进程内统计屏幕截图变化。
霍格沃兹测试开发Muller老师
2022/12/04
5640
034android初级篇之android的getevent/sendevent命令
在android中可以使用getevent/sendevent命令获取和模拟系统的输入事件。
上善若水.夏
2018/09/28
1.4K0
Linux 下Input系统应用编程实战
什么是input子系统?不管是什么操作系统,都有一个程序是用于管理各种输入设备的,打个比方,生活中使用的电脑键盘、鼠标就是输入设备,小到日常生活中无可避免的智能手机,智能手机上的触摸屏,按键也是输入社备。那么操作系统是怎么管理这些输入设备的呢?这里还是以最常用的操作系统Linux来进行讲解
杨源鑫
2019/07/04
2.9K0
Linux 下Input系统应用编程实战
Linux应用开发【第三章】输入系统应用开发
​ 在了解输入系统之前,先来了解什么是输入设备?常见的输入设备有键盘、鼠标、遥控杆、书写板、触摸屏等等,用户通过这些输入设备与Linux系统进行数据交换,Linux系统为了统一管控和处理这些设备,于是就实现了一套固定的与硬件无关的输入系统框架,供用户空间程序使用,这就是输入系统。
韦东山
2021/12/15
1.7K0
Linux应用开发【第三章】输入系统应用开发
android 电容屏(四):驱动调试之驱动程序分析篇 -- FocalTech
本人用的触摸屏IC是FocalTech公司的ft5306,是一款i2c的电容屏多点触控芯片。对于它的整体驱动官方已经给了,我们就触摸屏和按键部分的代码做相关说明。说明其中应该注意的地方。
233333
2022/05/10
2.3K0
ft6236 触摸屏驱动
在目录下amp\a53_linux\drv\extdrv\touchpad\ft6236下可以看到ft6236.c的文件
233333
2019/06/20
2K0
【i.MX6ULL】驱动开发13——电容触摸驱动实践(下)
上篇文章介绍了电容触摸驱动的编写,包括设备树的修改和驱动程序(IIC驱动+中断+input子系统),并通过将触摸坐标值实时打印出来的方式,对触摸功能进行测试。
xxpcb
2022/02/11
1.5K0
【i.MX6ULL】驱动开发13——电容触摸驱动实践(下)
[017]Input子系统-上篇
还得当年我刚接触触摸屏手机的时候,我就得非常好奇,为什么我触摸屏幕会产生屏幕上UI的变化,感觉非常神奇。在进入这个行业之后,我才发现原来屏幕分触控层和显示层,我们触摸屏幕的事件会通过"驱动-系统-应用-应用的某个UI控件"这一个完整流程。
王小二
2020/06/08
1.3K1
[017]Input子系统-上篇
Android中Input事件初始化、接收以及分发
至此 , InputManager完成初始化. 接下来就等待/dev/input中添加设备文件.
None_Ling
2020/09/24
2.1K0
input事件的获取
loop线程已经运行起来了,如果不出意外,它是不会终止的;不妨以此为起点,再开始一段新的旅程,我要去探索input事件的获取。
全栈程序员站长
2022/06/28
3.5K0
原创 Paper | USB设备开发:从入门到实践指南(三)
经过上一篇文章的学习,对USB HID驱动有了更多的了解,但是也产生了许多疑问,在后续的学习中解决了一些疑问,本篇文章先对已经解决的问题进行讲解。
Seebug漏洞平台
2024/03/04
2470
原创 Paper | USB设备开发:从入门到实践指南(三)
day28-开发板触屏操作(2022.2.25)
============= 1.触摸屏原始数据解析 ===================
天天Lotay
2022/12/02
9680
day28-开发板触屏操作(2022.2.25)
HiGV ui代码流程
HiGV 是一个轻量级的 GUI 系统,主要是为芯片平台提供统一的轻量级、高效、易用的 GUI 解决方案。该系统采用分层机制实现,其中底层图形库依赖 SDK 中 HiGO 库,而 HiGO 建立在基本的图形驱动(FrameBuffer、芯片 2D 加速驱动、图片编解码等)之上,如图 1-1 所示。
233333
2019/07/27
1.5K0
【i.MX6ULL】驱动开发12——电容触摸驱动实践(上)
上篇文章介绍了LCD屏幕的使用,这个屏幕还有触摸功能,本篇就来介绍LCD的触摸功能的使用。
xxpcb
2022/01/05
1.2K0
【i.MX6ULL】驱动开发12——电容触摸驱动实践(上)
18.Llinux-触摸屏驱动(详解)
本节的触摸屏驱动也是使用之前的输入子系统 1.先来回忆之前第12节分析的输入子系统 其中输入子系统层次如下图所示, 其中事件处理层的函数都是通过input_register_handler()函数注册
诺谦
2018/01/03
3.6K0
18.Llinux-触摸屏驱动(详解)
一次触摸,Android到底干了啥
当我们在写带有UI的程序的时候,如果想获取输入事件,仅仅是写一个回调函数,比如(onKeyEvent,onTouchEvent….),输入事件有可能来自按键的,来自触摸的,也有来自键盘的,其实软键盘也是一种独立的输入事件。那么为什么我能通过回调函数获取这些输入事件呢?系统是如何精确的让程序获得输入事件并去响应的呢?为什么系统只能同一时间有一个界面去获得触摸事件呢? 下面我们通过Android系统输入子系统的分析来回答这些问题。
WeTest质量开放平台团队
2018/10/29
8890
相关推荐
如何跨app对其他应用进行虚拟点击
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档