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


    前言

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

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

    adb shell monkey -p com.android.camera1000

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

    执行流程

    一、入口函数

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

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

    二、Monkey的run()方法

    1. private int run(String[] args) {
    2. //………………省略…………
    3. mEventSource = new MonkeySourceRandom(mRandom, mMainApps,
    4. mThrottle, mRandomizeThrottle, mPermissionTargetSystem);
    5. //………………省略…………
    6. crashedAtCycle = runMonkeyCycles();
    7. //…………………省略………
    8. }

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

    1、创建事件来源对象

    这里创建的是:MonkeySourceRandom对象

    2、调用runMonkeyCycles()方法

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

    三、Monkey的runMonkeyCycles()方法

    1. private int runMonkeyCycles() {
    2. //…………省略……
    3. while (!systemCrashed && cycleCounter < mCount) {
    4. //………………省略……
    5. if (shouldAbort) {
    6. //………………省略……
    7. return eventCounter;
    8. }
    9. //…………………省略……
    10. MonkeyEvent ev = mEventSource.getNextEvent();
    11. if (ev != null) {
    12. int injectCode = ev.injectEvent(mWm, mAm, mVerbose);
    13. //……………………省略……
    14. }
    15. //……………………省略……
    16. }

    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()方法

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

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

    1、mQ中没有事件

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

    2、如果mQ中有事件

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

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

    五、MonkeySourceRandom的generateEvents()方法

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

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

    1、随机数

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

    2、构造一个点事件

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

    六、MonkeySourceRandom的generatePointerEvent()方法

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

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

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

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

    七、MonkeyTouchEvent的injectEvent()方法

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

    八、MonkeyMotionEvent的injectEvent()方法

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

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

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

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

    总结

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

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

  • 相关阅读:
    web前端期末大作业【 大学生抗疫感动专题网页设计】HTML+CSS
    第三章、注册中心-Zookeeper(简介与安装)
    redis的使用场景
    [NOIP1999 普及组] 导弹拦截
    Scrapy Splash--appinum-->介绍-环境配置
    漏洞检测与EPSS评分
    Java项目论文+PPT+源码等]基于javaweb的网上订餐管理系统|点餐餐饮餐厅
    基于java的图书馆座位系统的设计与实现
    超级明星们的人物化身 NFT 将来到 The Sandbox 元宇宙
    在Mac m1运行ChatGLM3-6B cpu版本1-3秒出结果,速度明显超过T4 GPU,接近V100。
  • 原文地址:https://blog.csdn.net/cadi2011/article/details/133983535