Monkey源代码分析之事件注入

本系列的上一篇文章《Monkey源代码分析之事件源》中我们描写叙述了monkey是怎么从事件源取得命令。然后将命令转换成事件放到事件队列里面的。可是到如今位置我们还没有了解monkey里面的事件是怎么一回事,本篇文章就以这个问题作为切入点。尝试去搞清楚monkey的event架构是怎么样的。然后为什么是这样架构的,以及它又是怎么注入事件来触发点击等动作的。

在看这篇文章之前,希望大家最好先去看下另外几篇博文,这样理解起来就会更easy更清晰了:

1. 事件架构

这里我们先从上一篇文章《Monkey源代码分析之事件源》中来自网上的monkey架构图中截取MonkeyEvent相关的部分来看下MonkeyEvent的架构是怎么样的。

从上图能够看到。MonkeyEvent定义了三个public方法。然后继承下来的有5个不同的类,每一个类相应一种事件类型:

  • MonkeyActivityEvent: 代表Activity相关的事件
  • MonkeyMotionEvent:代表Motion相关的事件
  • MonkeyKeyEvent: 代表Key相关的事件
  • MonkeyFlibEvent: 代表Flib相关的事件
  • MonkeyThrottleEvent:代表睡眠事件

图中还描写叙述了这是一个command设计模式,事实上只在这个图里面是没有看出来就是command设计模式的,往下我们会描写叙述它到底是怎么实现了command设计模式的。

这里我们先拿一个实例来看下一个详细的event是怎么构成的,这里为了连贯性,我们就拿一个上一篇文章描写叙述的通过网络事件源过来的一个事件做描写叙述吧。这里我挑了MonkeyKeyEvent。

2. 构建MonkeyKeyEvent

这里它的父类MonkeyEvent我们就不深入描写叙述了。由于它仅仅是声明了几个方法而已,仅仅要脑袋里知道其声明了一个非常重要的injectKeyEvent的方法。每一个子类都须要通过实现它来注入事件就能够了。

如今我们先来看下MonkeyKeyEvent的构造函数:

public class MonkeyKeyEvent extends MonkeyEvent {
    private long mDownTime = -1;
    private int mMetaState = -1;
    private int mAction = -1;
    private int mKeyCode = -1;
    private int mScancode = -1;
    private int mRepeatCount = -1;
    private int mDeviceId = -1;
    private long mEventTime = -1;

    private KeyEvent keyEvent = null;

    public MonkeyKeyEvent(int action, int keycode) {
        super(EVENT_TYPE_KEY);
        mAction = action;
        mKeyCode = keycode;
    }

    public MonkeyKeyEvent(KeyEvent e) {
        super(EVENT_TYPE_KEY);
        keyEvent = e;
    }

    public MonkeyKeyEvent(long downTime, long eventTime, int action,
            int code, int repeat, int metaState,
            int device, int scancode) {
        super(EVENT_TYPE_KEY);

        mAction = action;
        mKeyCode = code;
        mMetaState = metaState;
        mScancode = scancode;
        mRepeatCount = repeat;
        mDeviceId = device;
        mDownTime = downTime;
        mEventTime = eventTime;
    }

MonkeyKeyEvent有多个构造函数,參数都不一样,可是目的都仅仅有一个,通过传进来的參数获得足够的信息保存成成员变量。以便今后创建一个android.view.KeyEvent,皆因该系统事件就是能够依据不同的參数进行初始化的。比方以下的getEvent方法就是依据不同的參数创建相应的KeyEvent的。注意这系统KeyEvent是很重要的,由于我们今后通过WindowManager注入事件就要把它的对象传进去去驱动相应的按键相关的事件。

     * @return the key event
     */
    private KeyEvent getEvent() {
        if (keyEvent == null) {
            if (mDeviceId < 0) {
                keyEvent = new KeyEvent(mAction, mKeyCode);
            } else {
                // for scripts
                keyEvent = new KeyEvent(mDownTime, mEventTime, mAction,
                                        mKeyCode, mRepeatCount, mMetaState, mDeviceId, mScancode);
            }
        }
        return keyEvent;
    }

支持的成员变量比較多,名字都挺浅显易懂,我这里就简单描写叙述两个我们最经常使用的:

  • mAction:代表了这个keyevent的动作,就是系统KeyEvent里面定义的ACTION_DOWN,ACTION_UP或者ACTION_MULTIPLE.
  • mKeyCode: 代表了你按下的到底是哪个按键,相同是在系统的KeyEvent定义的,比方82就代表了我们的系统菜单这个键值。
    public static final int KEYCODE_MENU            = 82;

3. 获取窗体事件注入者WindowManager

既然要往系统注入事件。那么首先要做的事情当然是先去获得注入事件的管理类,然后实例化它来给我们调用了。我们注入事件用的就是WindowManager这个类,而它的实例化是在monkey启动的时候通过main函数调用的run那里開始初始化的:

    private int run(String[] args) {
        ...
        if (!getSystemInterfaces()) {
            return -3;
        }
        ....
}

那么我们进入该方法看下我们须要的WindowManager是怎么初始化的。

    private boolean getSystemInterfaces() {
        mAm = ActivityManagerNative.getDefault();
        if (mAm == null) {
            System.err.println("** Error: Unable to connect to activity manager; is the system "
                    + "running?");
            return false;
        }

        mWm = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
        if (mWm == null) {
            System.err.println("** Error: Unable to connect to window manager; is the system "
                    + "running?");
            return false;
        }

        mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
        if (mPm == null) {
            System.err.println("** Error: Unable to connect to package manager; is the system "
                    + "running?");
            return false;
        }

        try {
            mAm.setActivityController(new ActivityController());
            mNetworkMonitor.register(mAm);
        } catch (RemoteException e) {
            System.err.println("** Failed talking with activity manager!");
            return false;
        }

        return true;
    }

这里我们主要是要理解里面用到的一些管理类。

这里事实上我们真正值得关注的就是WindowManager这个类,由于我们注入真实时间的时候事实上就是调用了它的方法。其它的类事实上在我们这篇文章中并没实用到的,可是既然看到了就顺便了解下吧。

我们先看下代码中提到的ActivityManagerNative这个类相关的信息,详细请查看转发的博文《ActivityManager框架解析》。个人觉得写的挺不错的。下面我依照自己的理解简单描写叙述了下

  • ActivityManager: 管理着系统的全部正在执行的activities。通过它能够获得系统正在执行的tasks,services,内存信息等。正常来说我们的应用能够同通过(ActivityManager)getSystemService(Context.ACTIVITY_SERVICE)实例化。而它提供的方法的操作都是依赖于ActivityManagerNativeProxy这个代理类来实现的
  • ActivityManagerNative:ActivityManagerProxy实现了接口IActivitManager,但并不真正实现这些方法,它仅仅是一个代理类。真正动作的执行为Stub类ActivityManagerService,ActivityManagerService对象仅仅有一个并存在于system_process进程中,ActivityManagerService继承于ActivityManagerNative存根类。
  • ActivityManagerProxy:代码中的第一行mAm = ActivityManagerNative.getDefault();获得的事实上就是ActivityManagerProxy的对象,而不是ActivityManagerNative

下一个就是IWindowManager类,不清楚的请看本人较早转发的博客<Android
之 Window、WindowManager 与窗体管理
>

  • IWindowManager:WindowManager主要用来管理窗体的一些状态、属性、view添加、删除、更新、窗体顺序、消息收集和处理等,但在android1.6以后隐藏掉了。这里之所以还能调用是由于monkey是在有android源代码的情况下编译出来的。假设没有源代码的话。那么就须要用到反射机制利用Class.forName来调用获取了。

然后是PackageManager:

  • PackageManager:本类API是对全部基于载入信息的数据结构的封装,包含下面功能:
  • 安装,卸载应用查询permission相关信息
  • 查询Application相关信息(application。activity,receiver。service,provider及对应属性等)
  • 查询已安装应用
  • 添加。删除permission
  • 清除用户数据、缓存,代码段等

最后是SeriviceManager,详细描写叙述请看《Android
之 ServiceManager与服务管理

  • ServiceManager:ServiceMananger是android中比較重要的一个进程,它是在init进程启动之后启动。从名字上就能够看出来它是用来管理系统中的service。比方:InputMethodService、ActivityManagerService等。

    在ServiceManager中有两个比較重要的方法:add_service、check_service。系统的service须要通过add_service把自己的信息注冊到ServiceManager中。当须要使用时,通过check_service检查该service是否存在

4.WindowManager往系统窗体注入事件

那么到了如今我们已经获得了要WindowManager对象了,下一步就要看MonkeyKeyEvent是怎么使用这个对象来向系统窗体发送按键key事件的了。

我们定位到injectEvent这种方法。

    @Override
    public int injectEvent(IWindowManager iwm, IActivityManager iam, int verbose) {
        if (verbose > 1) {
            String note;
            if (mAction == KeyEvent.ACTION_UP) {
                note = "ACTION_UP";
            } else {
                note = "ACTION_DOWN";
            }

            try {
                System.out.println(":Sending Key (" + note + "): "
                        + mKeyCode + "    // "
                        + MonkeySourceRandom.getKeyName(mKeyCode));
            } catch (ArrayIndexOutOfBoundsException e) {
                System.out.println(":Sending Key (" + note + "): "
                        + mKeyCode + "    // Unknown key event");
            }
        }

        // inject key event
        try {
            if (!iwm.injectKeyEvent(getEvent(), false)) {
                return MonkeyEvent.INJECT_FAIL;
            }
        } catch (RemoteException ex) {
            return MonkeyEvent.INJECT_ERROR_REMOTE_EXCEPTION;
        }

        return MonkeyEvent.INJECT_SUCCESS;
    }

注意传入參数

  • iwm:这个就是我们前面获取到的WindowManager的实例对象
  • iam:ActivityManager的实例对象,事实上在这里我们并不须要用到,可是为了兼容其它MonkeyXXXEvent对这个接口方法的实现,这里还是要传进来。但不作处理

整个方法代码不多,终于就通过调用iwm.injectKeyEvent方法,传入上面MonkeyKeyEvent初始化的时候创建的是系统KeyEvent对象。来实现按键事件的注入,这样就能模拟用户按下系统菜单等按键的功能了。

5.monkey注入事件处理方式分类

刚才以MonkeyKeyEvent作为实例来描写叙述了该类型的事件是怎么构造以及怎样在重写MonkeyEvent抽象父类的injectEvent时调用iWindowManager这个隐藏类的injectKeyEvent方法来注入按键事件的。事实上其它的事件类型重写MonkeyEvent的injectEvent方法的时候并不一定会真正的往系统窗体注入事件的,比方MonkeyThrottleEvent实现的injectEvent事实上就不过睡眠一下而已:

    @Override
    public int injectEvent(IWindowManager iwm, IActivityManager iam, int verbose) {

        if (verbose > 1) {
            System.out.println("Sleeping for " + mThrottle + " milliseconds");
        }
        try {
            Thread.sleep(mThrottle);
        } catch (InterruptedException e1) {
            System.out.println("** Monkey interrupted in sleep.");
            return MonkeyEvent.INJECT_FAIL;
        }

        return MonkeyEvent.INJECT_SUCCESS;
    }

所以尽管不同的MonkeyEvent实现类都实现了父类的injectEvent方法,可是并非全部的的MonkeyEvent都须要注入事件的。全部这个接口方法的名字我认为Google project师起得不好,比方叫做handleEvent就不会造成混乱了(个人见解)

下面列表列出了monkey支持的关键事件的不同处理方法:


事件处理方式


MonkeyEvent实现类


关键代码


凝视


通过WindowManager注入事件


MonkeyKeyEvent


injectKeyiwm.injectKeyEvent(getEvent(),false)Event


MonkeyTouchEvent


iwm.injectPointerEvent(me,false)


MonkeyTrackballEvent


iwm.injectTrackballEvent(me,false)


通过往事件设备/dev/input/event0发送命令注入事件


MonkeyFlipEvent


FileOutputStream("/dev/input/event0")


通过ActvityManager的startInstrumentation方法启动一个应用


MonkeyInstrumentationEvent


iam.startInstrumentation(cn,null,
0,args,null)


睡眠


MonkeyThrottleEvent


Thread.sleep(mThrottle)


MonkeyWaitEvent


Thread.sleep(mWaitTime)

6. MonkeyEvent之Command模式

都说MonkeyEvent使用Command模式来设计得。那么到底command设计模式是怎么样得呢?我们先看下下图。

那么我们对号入座,看下MonkeyEvent得设计是否满足该command模式的要求:

  • Command :MonkeyEvent,声明了injectEvent这个execute接口方法
  • ConcreteCommand:  各个MonkeyEvent实现类:MonkeyKeyEvent,MonkeyTouchEvent,MonkeyWaitEvent...
  • Client :  Monkey,记得它在runMonkeyCyles方法中调用了mEventSource.getNextEvent()方法来从事件源获取事件,并依据各个事件源的translateCommand方法来创建相应事件(ConcretCommand)吧?不记得的话请先看《Monkey源代码分析之执行流程》和《Monkey源代码分析之事件源
  • Receiver :  WindowManager等的实例对象。由于是它们终于实施和执行了injectXXXEvent这些请求。
  • Invoker :  Monkey,由于直接调用MonkeyKevent(command)的injectEvent(execute)这种方法的地方依旧是在Monkey的runMonkeyCeles这种方法中:ev.injectEvent(mWm,mAm,mVerbose)。

    所以Monkey在这里既扮演饿Command角色,又扮演了Invoker这个角色。

从中能够看到 MonkeyEvent的设计确实是满足了Command模式的,那么这样设计有什么优点呢?大家不知道的最好自己去google,这里我自己不精通设计模式,所以我仅仅能实际情况实际分析。看下网上描写叙述的这个设计模式的优点在我们的monkey中是否有获得:

  •   (1)命令模式使新的命令非常easy地被添加到系统里 :诚然。假设添加个实现处理吹下屏幕的事件(Command)的话我们仅仅须要添加个类MonkeyBlowEvent,并实现injectEvent接口,然后在里面调用对应的Receiver来注入Blow这个事件即可了
  •   (2)同意接收请求的一方决定是否要否决请求 :这点本人没有领悟优点是什么。谁清楚的请comment
  •   (3)能较easy地设计一个命令队列 :确实!

    monkey中就是把全部的事件抽象成MonkeyEvent然后放到我们的EventQueque里面的
  •   (4)能够easy地实现对请求的撤销和恢复 :这里没实用到,由于一个event消费掉后是不能撤销的。你总不能说你如今点击了个button懊悔了,程序会点击后先不运行等待你发送个undo命令吧。

    只是假设用在文档编辑的undo功能中应该是挺不错的
  •   (5)在须要的情况下,能够较easy地将命令记入日志 :也是,每一个ConcreteCommand类都是独立的,所以想把命令记录下来是非常简单的事情该是我MonkeyKeyEvent的命令总不会变成是你MonkeyTouchEvent的命令嘛
 

作者


自主博客


微信


CSDN


天地会珠海分舵


http://techgogogo.com


服务号:TechGoGoGo

扫描码:


http://blog.csdn.net/zhubaitian

时间: 2025-01-07 06:20:51

Monkey源代码分析之事件注入的相关文章

monkey源码分析之事件注入方法变化

在上一篇文章<Monkey源码分析之事件注入>中,我们看到了monkey在注入事件的时候用到了<Monkey源码分析番外篇之Android注入事件的三种方法比较>中的第一种方法,通过Internal API的WindowManager的injectKeyEvent之类的方法注入事件.这种方法在android api level 16也就是android4.1.2之后已经发生了变化: 在此之后注入事件的方式变成了使用InputManager的injectInputEvent方法了 而

Monkey源代码分析番外篇WindowManager如何出的喷射事件的进程间的安全限制

在分析monkey源代码时的一些背景知识不明确,例如看到monkey它是用windowmanager的injectKeyEvent的喷射事件时的方法.我发现自己陷入疙瘩,这种方法不仅能够在当前的应用程序,注入的事件它?Google在国外找到下一个大牛离开的问题的叙述性说明痕迹,特意摘录下来并做对应部分的翻译,其它部分大家喜欢就看下.我就不翻译了. How it works Behind the scenes, Monkey uses several private interfaces to c

Monkey源码分析之事件注入

本系列的上一篇文章<Monkey源码分析之事件源>中我们描述了monkey是怎么从事件源取得命令,然后将命令转换成事件放到事件队列里面的,但是到现在位置我们还没有了解monkey里面的事件是怎么一回事,本篇文章就以这个问题作为切入点,尝试去搞清楚monkey的event架构是怎么样的,然后为什么是这样架构的,以及它又是怎么注入事件来触发点击等动作的. 在看这篇文章之前,希望大家最好先去看下另外几篇博文,这样理解起来就会更容易更清晰了: <Monkey源码分析番外篇之Android注入事件

Monkey源代码分析之执行流程

在<MonkeyRunner源代码分析之与Android设备通讯方式>中.我们谈及到MonkeyRunner控制目标android设备有多种方法.当中之中的一个就是在目标机器启动一个monkey服务来监听指定的一个port,然后monkeyrunner再连接上这个port来发送命令.驱动monkey去完毕对应的工作. 当时我们仅仅分析了monkeyrunner这个client的代码是怎么实现这一点的,但没有谈monkey那边是怎样接受命令,接受到命令又是怎样处理的. 所以自己打开源代码看了一个

第6章7节《MonkeyRunner源码剖析》Monkey原理分析-事件源-事件源概览-注入按键事件实例

在事件生成并放入到命令队列后,Monkey类的runMonkeyCycles就会去调用相应事件源的getNextEvent来获的事件来执行事件注入,那么这一小节我们通过MonkeyKeyEvent这个事件的注入方法来看下事件注入过程是怎么样的. 往系统注入按键事件最终是通过调用InputManager提供的方法来实现的,在Android系统中,按键事件是由InputManager来收集并由WindowManagerService服务来分发给各个Activity处理的,这个系统服务.它是用于管理整

第6章1节《MonkeyRunner源代码剖析》Monkey原理分析-事件源-事件源概览

在上一章中我们有简要的介绍了事件源是怎么一回事.可是并没有进行详细的描写叙述.那么往下的这几个小节我们就须要把这方面的知识给补充完整. 这一节我们先主要环绕MonkeySourceNetwork这个事件源来学习事件源的框架结构.首先,要理解事件源,必须先搞清楚几个问题: 事件从哪里来? Monkey的事件来源有多个方面,可是作为MonkeyRunner框架的一部分,它的事件来源主要是来自MonkeyRunner通过网络Socket(USB/TCP协议)发送过来的命令字串.MonkeySource

第5章7节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 循环获取并执行事件 - runMonkeyCycles(原创)

天地会珠海分舵注:本来这一系列是准备出一本书的,详情请见早前博文"寻求合作伙伴编写<深入理解 MonkeyRunner>书籍".但因为诸多原因,没有如愿.所以这里把草稿分享出来,所以错误在所难免.有需要的就参考下吧,转发的话还请保留每篇文章结尾的出处等信息. Monkey启动之后需要在整个MonkeyRunner的测试生命周期中提供服务,也就是说,一旦我们调用monkeyrunner命令来执行指定的测试脚本的时候,只要monkeyrunner还没有退出,那么Monkey就会

UiAutomator喷射事件的源代码分析

上一篇文章<UiAutomator源代码分析之UiAutomatorBridge框架>中我们把UiAutomatorBridge以及它相关的类进行的描写叙述,往下我们会尝试依据两个实例将这些类给串联起来,我准备做的是用例如以下两个非常有代表性的实例: 注入事件 获取控件 这一篇文章我们会通过分析UiDevice的pressHome这种方法来分析UiAutomator是怎样注入事件的,下一篇文章会描写叙述怎样获取控件,敬请期待. 1. UiObject.pressHome顺序图 首先我们看一下我

老李推荐:第6章5节《MonkeyRunner源码剖析》Monkey原理分析-事件源-事件源概览-事件

老李推荐:第6章5节<MonkeyRunner源码剖析>Monkey原理分析-事件源-事件源概览-事件 从网络过来的命令字串需要解析翻译出来,有些命令会在翻译好后直接执行然后返回,但有一大部分命令在翻译后需要转换成对应的事件,然后放入到命令队列里面等待执行.Monkey在取出一个事件执行的时候主要是执行其injectEvent方法来注入事件,而注入事件根据是否需要往系统注入事件分为两种: 需要通过系统服务往系统注入事件:如MonkeyKeyEvent事件会通过系统的InputManager往系