Android之Monkey源码分析(第十三篇:触摸事件流程分析)

2023-10-23 13:04

本文主要是介绍Android之Monkey源码分析(第十三篇:触摸事件流程分析),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

    前面讲了一些monkey作者的设计思想(有的我没写完,还没发布,惨),这篇来点实际的,monkey程序是如何发起一个触摸事件的呢?

本例子中假设使用的命令是:

adb shell monkey -p com.android.camera1000

表示向相机app发出1000个事件,所有事件都是随机的,其中会包括touch事件,那么这些touch事件是怎么构造的呢?

执行流程

一、入口函数

    public static void main(String[] args) {//…………省略……int resultCode = (new Monkey()).run(args); //……………省略……}

main函数是java程序的入口函数,monkey作为一个标准的java程序,当然它也最先调用main函数,在main函数的内部,创建了Monkey对象,并调用了run()方法,参数args表示命令行参数数组,同时也传了进去

二、Monkey的run()方法

    private int run(String[] args) {//………………省略…………mEventSource = new MonkeySourceRandom(mRandom, mMainApps,mThrottle, mRandomizeThrottle, mPermissionTargetSystem); //………………省略…………crashedAtCycle = runMonkeyCycles(); //…………………省略………}

Monkey中的run()方法挺长的,有200多行,该方法完整的分析,可以看我之前的文章,这里我们最需要知道的是两个

1、创建事件来源对象

这里创建的是:MonkeySourceRandom对象

2、调用runMonkeyCycles()方法

调用runMonkeyCycles()方法后,主线程会在该方法内循环执行,当中有我们的touch事件

三、Monkey的runMonkeyCycles()方法

    private int runMonkeyCycles() {//…………省略……while (!systemCrashed && cycleCounter < mCount) {//………………省略……if (shouldAbort) { //………………省略……return eventCounter; }//…………………省略……MonkeyEvent ev = mEventSource.getNextEvent();if (ev != null) {  int injectCode = ev.injectEvent(mWm, mAm, mVerbose);//……………………省略……}//……………………省略……}

runMonkeyCycles()方法也挺长的,截取了今天我们关注的业务逻辑

1、循环获取事件

通过while循环,一直获取事件

2、循环结束条件

第一是:systemCrashed 表示系统发生故障,monkey不再获取新的事件

第二是:cycleCount 超过预设的次数,就是我们在命令行参数传入的事件总数

第三是:shouldAbort 标志位,跟我们命令行传入的命令有关,比如anr、native crash、默认不开启的,这个标志位也会终止事件循环

3、反复获取事件

                MonkeyEvent ev = mEventSource.getNextEvent();

通过之前的文章可知,MonkeyEvent是所有事件类的父类,而这里的mEventSource指向的是MonkeySourceRandom对象,调用它的getNextEvent()方法,这个方法会返回一个Event对象,我们要看到的touch事件,就会在这个方法中返回,我们接下来就去这个方法中看看什么时候会返回touch事件

4、开始注入事件

int injectCode = ev.injectEvent(mWm, mAm, mVerbose);

 每个Event对象会有injectEvent()方法,同时会wms、ams、以及一个debug参数mVerbose传入进去,具体的事件执行,就是在各个事件对象的injectEvent()方法中进行的,稍后我们会看到touch事件的injectEvent是如何发出一个touch事件的!

四、MonkeySourceRandom的getNextEvent()方法

    public MonkeyEvent getNextEvent() {if (mQ.isEmpty()) {   //当双向链表中没有元素时,说明没有可用的事件generateEvents(); //构造事件,可能构造一个,也可能构造多个,构造的事件对象会添加到m}mEventCount++; //MonkeySourceRandom对象持有的事件数量增加1,表示已经提取出的事件数量MonkeyEvent e = mQ.getFirst(); //获取双向链表中的第一个元素,赋值给局部变量e保存mQ.removeFirst(); //删除掉该事件(第一个元素)return e; //向调用者返回MonkeyEvent对象}

getNextEvent()方法不长,但是信息量不错,mQ是个双向链表,用于保存每个事件对象,两种情况

1、mQ中没有事件

mQ.isEmpty()位true,表示没有事件,此时会调用genrateEvents()方法构造事件,我们的touch事件就是在这个方法里构建,接下来就去这个

2、如果mQ中有事件

取出来链表中保存的第一个事件对象

最后总会返回一个事件对象e

五、MonkeySourceRandom的generateEvents()方法

    private void generateEvents() {float cls = mRandom.nextFloat(); int lastKey = 0; if (cls < mFactors[FACTOR_TOUCH]) { generatePointerEvent(mRandom, GESTURE_TAP); //构造Pointer事件(点击事件)return; }// ………………省略…………  } 

这是根据命令行中的比例,构造各类事件的方法,touch事件是最先判断的,它的默认比例(当我们不指定比例,monkey会有默认touch事件比例)

1、随机数

mRandom.nextFloat()会产生一个float型的随机数,mFactors是个数组,它存储着每个事件的比例,touch事件对应的数组下标是FACTOR_TOUCH

2、构造一个点事件

通过调用generatePointerEvent()方法来构造touch事件

六、MonkeySourceRandom的generatePointerEvent()方法

    private void generatePointerEvent(Random random, int gesture) {//………………省略……mQ.addLast(new MonkeyTouchEvent(MotionEvent.ACTION_DOWN) //此处传入的按下的动作.setDownTime(downAt) //记录按下的时间戳.addPointer(0, p1.x, p1.y) //id都传0……,把获取到的x坐标与y坐标也传进去.setIntermediateNote(false)); //false表示这不是一个过渡事件//向双向链表中添加事件,添加一个元素,即MonkeyTouchEvent对象//………………省略……mQ.addLast(new MonkeyTouchEvent(MotionEvent.ACTION_UP).setDownTime(downAt) //为啥还记录的按下的时间?.addPointer(0, p1.x, p1.y).setIntermediateNote(false)); //最后再添加一个ACTION_UP事件,如果是点事件,则至少添加了两个元素对象到mQ中,一个ACTION_DOWN、一个ACTION_UP、并且不是过渡事件}

 touch事件,会向mQ中传入两个元素,一个down表示按下、另一个是up表示抬起,组合在一起就是touch事件

mQ中有了事件,事件就可以被获取到了,接下来事件的injectEvent()方法会被调用了 

由于mQ中实际持有的是MonkeyTouchEvent对象,所以它的injectEvent()方法会被调用

七、MonkeyTouchEvent的injectEvent()方法

如果你是第一次进入MonkeyTouchEvent,你会发现它根本没有injectEvent()方法,这个时候我们要向上查找,去它的父类MonkeyMotionEvent中在找找,这里要注意了

八、MonkeyMotionEvent的injectEvent()方法

    public int injectEvent(IWindowManager iwm, IActivityManager iam, int verbose) {//…………省略……try {if (!InputManager.getInstance().injectInputEvent(me,  //走到这里才是真的向手机注入事件,通过InputManager的injectInputEvent注入事件,传入MotionEvent对象InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT)) { //依赖InputManagerService系统服务注入事件,看来还得死磕Android的系统服务return MonkeyEvent.INJECT_FAIL; //只要IMS返回的是失败,则证明注入失败,看来这里也是同步方法,Monkey主线程会等待执行完……}} finally {me.recycle(); //将缓存的MotionEvent对象回收掉,牛逼!}return MonkeyEvent.INJECT_SUCCESS; //走到这里说明注入事件成功,系统封装好了,靠你来执行了}

我们只关注与事件注入相关的内容,重点来了,injectEvent()方法中并没有使用传入的wms系统服务、也么有使用ams系统服务,而是另外使用了一个ims系统服务

InputManager对象,这个InputManger对象是用来操作InputManagerService系统服务

调用ims系统服务的injectInputEvent()方法,发出touch事件

总结

1、monkey作者熟悉Android框架,它巧妙的使用了系统预留的系统服务,InputMangerService发出了touch事件

2、InputMangerService预留的injectEvent()方法可以发出touch事件,只要熟悉这个方法的使用,我们自己也能写个monkey程序

这篇关于Android之Monkey源码分析(第十三篇:触摸事件流程分析)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/268017

相关文章

通过Docker容器部署Python环境的全流程

《通过Docker容器部署Python环境的全流程》在现代化开发流程中,Docker因其轻量化、环境隔离和跨平台一致性的特性,已成为部署Python应用的标准工具,本文将详细演示如何通过Docker容... 目录引言一、docker与python的协同优势二、核心步骤详解三、进阶配置技巧四、生产环境最佳实践

MyBatis分页查询实战案例完整流程

《MyBatis分页查询实战案例完整流程》MyBatis是一个强大的Java持久层框架,支持自定义SQL和高级映射,本案例以员工工资信息管理为例,详细讲解如何在IDEA中使用MyBatis结合Page... 目录1. MyBATis框架简介2. 分页查询原理与应用场景2.1 分页查询的基本原理2.1.1 分

redis-sentinel基础概念及部署流程

《redis-sentinel基础概念及部署流程》RedisSentinel是Redis的高可用解决方案,通过监控主从节点、自动故障转移、通知机制及配置提供,实现集群故障恢复与服务持续可用,核心组件包... 目录一. 引言二. 核心功能三. 核心组件四. 故障转移流程五. 服务部署六. sentinel部署

SpringBoot集成XXL-JOB实现任务管理全流程

《SpringBoot集成XXL-JOB实现任务管理全流程》XXL-JOB是一款轻量级分布式任务调度平台,功能丰富、界面简洁、易于扩展,本文介绍如何通过SpringBoot项目,使用RestTempl... 目录一、前言二、项目结构简述三、Maven 依赖四、Controller 代码详解五、Service

Android协程高级用法大全

《Android协程高级用法大全》这篇文章给大家介绍Android协程高级用法大全,本文结合实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友跟随小编一起学习吧... 目录1️⃣ 协程作用域(CoroutineScope)与生命周期绑定Activity/Fragment 中手

MySQL 临时表与复制表操作全流程案例

《MySQL临时表与复制表操作全流程案例》本文介绍MySQL临时表与复制表的区别与使用,涵盖生命周期、存储机制、操作限制、创建方法及常见问题,本文结合实例代码给大家介绍的非常详细,感兴趣的朋友跟随小... 目录一、mysql 临时表(一)核心特性拓展(二)操作全流程案例1. 复杂查询中的临时表应用2. 临时

Android 缓存日志Logcat导出与分析最佳实践

《Android缓存日志Logcat导出与分析最佳实践》本文全面介绍AndroidLogcat缓存日志的导出与分析方法,涵盖按进程、缓冲区类型及日志级别过滤,自动化工具使用,常见问题解决方案和最佳实... 目录android 缓存日志(Logcat)导出与分析全攻略为什么要导出缓存日志?按需过滤导出1. 按

MySQL 升级到8.4版本的完整流程及操作方法

《MySQL升级到8.4版本的完整流程及操作方法》本文详细说明了MySQL升级至8.4的完整流程,涵盖升级前准备(备份、兼容性检查)、支持路径(原地、逻辑导出、复制)、关键变更(空间索引、保留关键字... 目录一、升级前准备 (3.1 Before You Begin)二、升级路径 (3.2 Upgrade

Linux中的HTTPS协议原理分析

《Linux中的HTTPS协议原理分析》文章解释了HTTPS的必要性:HTTP明文传输易被篡改和劫持,HTTPS通过非对称加密协商对称密钥、CA证书认证和混合加密机制,有效防范中间人攻击,保障通信安全... 目录一、什么是加密和解密?二、为什么需要加密?三、常见的加密方式3.1 对称加密3.2非对称加密四、

MySQL中读写分离方案对比分析与选型建议

《MySQL中读写分离方案对比分析与选型建议》MySQL读写分离是提升数据库可用性和性能的常见手段,本文将围绕现实生产环境中常见的几种读写分离模式进行系统对比,希望对大家有所帮助... 目录一、问题背景介绍二、多种解决方案对比2.1 原生mysql主从复制2.2 Proxy层中间件:ProxySQL2.3