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

编程入门 行业动态 更新时间:2024-10-24 10:14:04

Android之Monkey<a href=https://www.elefans.com/category/jswz/34/1770099.html style=源码分析(第十三篇:触摸事件流程分析)"/>

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源码分析(第十三篇:触摸事件流程分析)

本文发布于:2023-12-04 15:33:07,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1661397.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:源码   流程   事件   第十三篇   Android

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!