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

相关文章

python panda库从基础到高级操作分析

《pythonpanda库从基础到高级操作分析》本文介绍了Pandas库的核心功能,包括处理结构化数据的Series和DataFrame数据结构,数据读取、清洗、分组聚合、合并、时间序列分析及大数据... 目录1. Pandas 概述2. 基本操作:数据读取与查看3. 索引操作:精准定位数据4. Group

MySQL中EXISTS与IN用法使用与对比分析

《MySQL中EXISTS与IN用法使用与对比分析》在MySQL中,EXISTS和IN都用于子查询中根据另一个查询的结果来过滤主查询的记录,本文将基于工作原理、效率和应用场景进行全面对比... 目录一、基本用法详解1. IN 运算符2. EXISTS 运算符二、EXISTS 与 IN 的选择策略三、性能对比

MySQL 内存使用率常用分析语句

《MySQL内存使用率常用分析语句》用户整理了MySQL内存占用过高的分析方法,涵盖操作系统层确认及数据库层bufferpool、内存模块差值、线程状态、performance_schema性能数据... 目录一、 OS层二、 DB层1. 全局情况2. 内存占js用详情最近连续遇到mysql内存占用过高导致

Android Paging 分页加载库使用实践

《AndroidPaging分页加载库使用实践》AndroidPaging库是Jetpack组件的一部分,它提供了一套完整的解决方案来处理大型数据集的分页加载,本文将深入探讨Paging库... 目录前言一、Paging 库概述二、Paging 3 核心组件1. PagingSource2. Pager3.

深度解析Nginx日志分析与499状态码问题解决

《深度解析Nginx日志分析与499状态码问题解决》在Web服务器运维和性能优化过程中,Nginx日志是排查问题的重要依据,本文将围绕Nginx日志分析、499状态码的成因、排查方法及解决方案展开讨论... 目录前言1. Nginx日志基础1.1 Nginx日志存放位置1.2 Nginx日志格式2. 499

Spring Boot 中的默认异常处理机制及执行流程

《SpringBoot中的默认异常处理机制及执行流程》SpringBoot内置BasicErrorController,自动处理异常并生成HTML/JSON响应,支持自定义错误路径、配置及扩展,如... 目录Spring Boot 异常处理机制详解默认错误页面功能自动异常转换机制错误属性配置选项默认错误处理

Spring Boot从main方法到内嵌Tomcat的全过程(自动化流程)

《SpringBoot从main方法到内嵌Tomcat的全过程(自动化流程)》SpringBoot启动始于main方法,创建SpringApplication实例,初始化上下文,准备环境,刷新容器并... 目录1. 入口:main方法2. SpringApplication初始化2.1 构造阶段3. 运行阶

Olingo分析和实践之EDM 辅助序列化器详解(最佳实践)

《Olingo分析和实践之EDM辅助序列化器详解(最佳实践)》EDM辅助序列化器是ApacheOlingoOData框架中无需完整EDM模型的智能序列化工具,通过运行时类型推断实现灵活数据转换,适用... 目录概念与定义什么是 EDM 辅助序列化器?核心概念设计目标核心特点1. EDM 信息可选2. 智能类

Olingo分析和实践之OData框架核心组件初始化(关键步骤)

《Olingo分析和实践之OData框架核心组件初始化(关键步骤)》ODataSpringBootService通过初始化OData实例和服务元数据,构建框架核心能力与数据模型结构,实现序列化、URI... 目录概述第一步:OData实例创建1.1 OData.newInstance() 详细分析1.1.1

Olingo分析和实践之ODataImpl详细分析(重要方法详解)

《Olingo分析和实践之ODataImpl详细分析(重要方法详解)》ODataImpl.java是ApacheOlingoOData框架的核心工厂类,负责创建序列化器、反序列化器和处理器等组件,... 目录概述主要职责类结构与继承关系核心功能分析1. 序列化器管理2. 反序列化器管理3. 处理器管理重要方