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

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

 

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类,不清楚的请看本人较早在csdn转发的博客<Android 之 Window、WindowManager 与窗口管理>
  • IWindowManager:WindowManager主要用来管理窗口的一些状态、属性、view增加、删除、更新、窗口顺序、消息收集和处理等,但在android1.6以后隐藏掉了。这里之所以还能调用是因为monkey是在有android源码的情况下编译出来的,如果没有源码的话,那么就需要用到反射机制利用Class.forName来调用获取了。

然后是PackageManager:

  • PackageManager:本类API是对所有基于加载信息的数据结构的封装,包括以下功能:
  • 安装,卸载应用查询permission相关信息
  • 查询Application相关信息(application,activity,receiver,service,provider及相应属性等)
  • 查询已安装应用
  • 增加,删除permission
  • 清除用户数据、缓存,代码段等
最后是SeriviceManager,具体描述请看较早在csdn转载的文章《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 工程师起得不好,比如叫做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)命令模式使新的命令很容易地被加入到系统里 :诚然!如果增加个实现处理吹下屏幕的事件(Command)的话我们只需要增加个类MonkeyBlowEvent,并实现injectEvent接口,然后在里面调用相应的Receiver来注入Blow这个事件就行了
  •   (2)允许接收请求的一方决定是否要否决请求 :这点本人没有领悟好处是什么,谁清楚的请comment
  •   (3)能较容易地设计一个命令队列 :确实!monkey中就是把所有的事件抽象成MonkeyEvent然后放到我们的EventQueque里面的
  •   (4)可以容易地实现对请求的撤销和恢复 :这里没有用到,因为一个event消费掉后是不能撤销的。你总不能说你现在点击了个按钮后悔了,程序会点击后先不执行等待你发送个undo命令吧。不过如果用在文档编辑的undo功能中应该是挺不错的
  •   (5)在需要的情况下,可以较容易地将命令记入日志 :也是,每个ConcreteCommand类都是独立的,所以想把命令记录下来是很简单的事情该是我MonkeyKeyEvent的命令总不会变成是你MonkeyTouchEvent的命令嘛
作者 自主博客 微信服务号及扫描码 CSDN
天地会珠海分舵 http://techgogogo.com 服务号:TechGoGoGo扫描码: http://blog.csdn.net/zhubaitian

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

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

    在上一篇文章<Monkey源码分析之事件注入>中,我们看到了monkey在注入事件的时候用到了<Monkey源码分析番外篇之Android注入事件的三种方法比较>中的第一种方法 ...

  2. Monkey源码分析之事件源

    上一篇文章<Monkey源码分析之运行流程>给出了monkey运行的整个流程,让我们有一个概貌,那么往后的文章我们会尝试进一步的阐述相关的一些知识点. 这里先把整个monkey类的结构图给 ...

  3. Spring Ioc源码分析系列--自动注入循环依赖的处理

    Spring Ioc源码分析系列--自动注入循环依赖的处理 前言 前面的文章Spring Ioc源码分析系列--Bean实例化过程(二)在讲解到Spring创建bean出现循环依赖的时候并没有深入去分 ...

  4. 安卓Monkey源码分析之运行流程

    在<MonkeyRunner源码分析之与Android设备通讯方式>中,我们谈及到MonkeyRunner控制目标android设备有多种方法,其中之一就是在目标机器启动一个monkey服 ...

  5. [Abp vNext 源码分析] - 3. 依赖注入与拦截器

    一.简要说明 ABP vNext 框架在使用依赖注入服务的时候,是直接使用的微软提供的 Microsoft.Extensions.DependencyInjection 包.这里与原来的 ABP 框架 ...

  6. AngularJS源码分析之依赖注入$injector

    开篇 随着javaEE的spring框架的兴起,依赖注入(IoC)的概念彻底深入人心,它彻底改变了我们的编码模式和思维.在IoC之前,我们在程序中需要创建一个对象很简单也很直接,就是在代码中new O ...

  7. Yarn源码分析之事件异步分发器AsyncDispatcher

    AsyncDispatcher是Yarn中事件异步分发器,它是ResourceManager中的一个基于阻塞队列的分发或者调度事件的组件,其在一个特定的单线程中分派事件,交给AsyncDispatch ...

  8. 非常适合新手的jq/zepto源码分析06 -- 事件模型

    复习下事件的有关内容: 1.现在用的绑定/删除: obj.addEventListener(type,fn,false) obj.removeEventListener(type) obj.attac ...

  9. jquery源码分析(七)——事件模块 event(二)

    上一章节探讨了事件的一些概念,接下来看下jQuery的事件模块. jQuery对事件的绑定分别有几个API:.bind()/.live()/.delegate()/.on()/click(), 不管是 ...

随机推荐

  1. outlook 当关闭时最小化到任务栏完美的解决方案

    近期使用Outlook,但很发现easy退出关闭.不能达到最小化封. 在网上找了很长时间也用outlook on the desktop插件,但该插件安装后的执行错误和被遗弃. 最后,我发现了一个叫k ...

  2. swift 笔记 (十九) —— 协议

    协议(Protocols) 协议仅是用定义某些任务或者是功能必须的方法和属性. 类似于java里的interface的作用.但协议并不会实现详细的功能. 我猜这个名字源于OO中提到的"契约& ...

  3. Sizzle.selectors.relative [ 源代码分析 ]

    1 jQuery 对象Sizzle.selectors.relative中存放了块间关系符和相应的块间关系过滤函数,称为"块间关系过滤函数集" 块间关系符共同拥有4种,其含义和过滤 ...

  4. bzoj 2437 [Noi2011]兔子和鸡蛋 [二分图匹配]

    叙述性说明 这些日子.兔子和蛋像一个新的棋盘游戏. 这场比赛是在 n 行 m 在船上进行列. 前,棋盘上有一 个格子是空的,其他的格子中都放置了一枚棋子,棋子或者是黑色,或者是白色. 每一局游戏总是兔 ...

  5. spring mvc 控制器方法传递一些经验对象的数组

    由于该项目必须提交一个表单,其中多个对象,更好的方法是直接通过在控制器方法参数的数组. 因为Spring mvc框架在反射生成控制方法的參数对象的时候会调用这个类的getDeclaredConstru ...

  6. 【POJ3268】Silver Cow Party 最短

    意甲冠军:一群奶牛去的地方.去回,然后回去寻找最伟大值. 题解:两遍最短路,结束.邻接矩阵存边能够避免建反图. #include <cstdio> #include <cstring ...

  7. Base64中文不能加密问题

    最近用到了Base64.js来对url参数进行加密,字母和数字都可以很好地加密/解密. 但测试中文时发现不能进行转换,貌似Base64.js不支持中文字符. 联想到encodeURI()对url的编码 ...

  8. React实践

    React实践(一)   该实践取自官方教程:https://github.com/reactjs/react-tutorial 主要是自实现的过程以及一些心得体会 该实践是实现一个评论框. 一个展示 ...

  9. 第14章 命令模式(Command Pattern)

    原文 第14章 命令模式(Command Pattern) 命令模式(Command Pattern) 概述   在软件系统中,“行为请求者”与“行为实现者”通常呈现一种“紧耦合”.但在某些场合,比如 ...

  10. Java数据结构与算法(20) - ch08树

    树的主要算法有插入,查找,显示,遍历,删除,其中显示和删除略微复杂. package chap08.tree; import java.io.BufferedReader; import java.i ...