老李推荐:第5章5节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 获取系统服务引用

 

上一节我们描述了monkey的命令处理入口函数run是如何调用optionProcess方法来解析命令行参数的。启动参数主要时去指导Monkey时怎么运行起来的,但Monkey作为MonkeyRunner框架的一部分,更重要的是如何将从MonkeyRunner测试脚本出发的命令转化成事件来注入到系统中以进行测试自动化。如前面所说,run方法除了对启动参数进行解析之外还做了很多其他的事情,比如这一小节需要分析的去建立对系统服务的引用。因为只有获得这些引用之后才能实现对系统的事件注入。当然,run方法中其中有一部分代码是跟MonkeyRunner框架不相干的,所以我们不会花时间去分析它,也免得钻进去后影响大家对monkey作为MonkeyRunner框架的服务的理解。

下面我们先看下run方法在processOptions之后调用的下一个关键方法getSystemInterfaces:

代码2-5-1 Monkey - run

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

processOptions方法之后到488行之前的代码所做的去准备monkey测试目标packages和生成随机测试seed这些都跟作为MonkeyRunner的一个服务的monkey没有多大关系的。这些主要是当monkey扮演的是一个独立的随机压力测试工具来进行随机对指定的package进行随机压力测试才有意义。所以这里我们没有必要花篇幅去分析它,这不会影响我们对MonkeyRunner框架的理解。

这里需要关注的是488行的getSystemInterfaces的一个调用,这个方法做了一个很重要的事情,就是去获得与Android操作系统交互的3个引用:

  • Activity交互控制服务ActivityManagerService

  • 应用包管理服务PackageManagerService

  • 窗口管理服务WindowManagerService。

这些引用在Monkey作为一个MonkeyRunner一个服务运行的时候重要性已经没有在老版本中那么明显了。以往系统注入按键事件为例,我们现在分析的安卓4.4.2版本中,Monkey服务是用InputManagerService服务来注入事件以触发按键等动作的。但是在比较老的版本中,往窗口注入事件主要是通过WindowManagerService服务来完成的,等会我们会给出两个不同版本的按键事件注入源码来印证这个转变。

以下我们先描述下这几个服务的作用以及获取的方式:

  • ActivityManagerService: 按照官方的解析,这个类的作用主要是用来为与系统中所有的正在运行的Activity进行交互提供交互接口,主要是围绕着运行中的进程信息,任务信息,服务信息等。但在Monkey中主要是在当monkey作为随机压力测试工具的时候用到。该服务的引用可以通过”ActivityManagerNative.getDefault()”方法获得

  • PackageManagerService:按照官方的解析,它的作用主要就是用来获取系统已经安装的包的不同的信息。也就是说它主要是用来管理应用程序包的。 同样,它也是在当monkey作为随机压力测试工具才会用到,作为MonkeyRunner服务的时候并不会用到。该服务的引用可以通过AIDL机制来获得。

  • WindowManagerService: WindowManagerService主要用来管理窗口的一些状态、属性、view增加、删除、更新、窗口顺序、消息收集和处理等。在稍微老点的Android版本中,Monkey主要是用它直接来注入窗口事件的,在Android 4.1之后才引入InputManagerService服务来处理相应的事件注入请求。该服务的引用可以通过AIDL机制来获得。其实InputManagerServce并不是说最近的版本才有的,之前就一直存在,只不过之前它是作为WindowManagerService的一个服务类存在。而自从安卓4.1版本后它就独立出来作为一个服务运行而已。

  • InputManagerService: 主要负责的就是用户从键盘,屏幕等进行操作的管理。WindowManagerService是整个窗口的大管家,而InputManagerService在监控接收到用户出发的相应的输入事件后最终是会调用WindowManagerService服务来进行处理的。

下面的安卓架构图显示了这些服务是处在安卓操作系统的什么位置,我相信读者肯定之前已经看过了,但读者请注意该图并没把InputManager服务给画出来,相信是该图并没有及时更新的原因。

图5-5-1 安卓架构图

从中我们可以看到整个安卓操作系统从上往下分为多个层次,其中最上层就是应用程,比如我们电话本,浏览器之类的应用就运行在这一层。支撑这些应用运行的背后是一些列的服务和系统,应用层下一层的应用程序框架层就是专门提供这种服务的,比如我们这里提供Activity管理服务的ActivityManagerService,提供窗口和控件管理服务的WindowManagerService,提供应用包管理服务的PackageManagerService,以及提供用户输入管理的InputManagerService都是运行在这一层的。

前面3个服务虽然有些已经不会用到,但是由于历史的原因,为了保持调用的一致性,有些接口还是需要传入相应的变量到相应的方法里面,虽然该方面并不会用到该服务。比如Monkey服务在需要往系统注入按键事件的时候会调用到MonkeyKeyEvent这个类的injectEvent方法,该方法支持的输入参数就有上面提到的WindowManagerService和ActivityManagerService,但实际上这两个服务并没有用到的。请看代码如下:

代码5-5-2 MonkeyKeyEvent - 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");
}
}
KeyEvent keyEvent = mKeyEvent;
if (keyEvent == null) {
long eventTime = mEventTime;
if (eventTime <= 0) {
eventTime = SystemClock.uptimeMillis();
}
long downTime = mDownTime;
if (downTime <= 0) {
downTime = eventTime;
}
keyEvent = new KeyEvent(downTime, eventTime, mAction, mKeyCode,
mRepeatCount, mMetaState, mDeviceId, mScanCode,
KeyEvent.FLAG_FROM_SYSTEM, InputDevice.SOURCE_KEYBOARD);
}
if (!InputManager.getInstance().injectInputEvent(keyEvent,
InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT)) {
return MonkeyEvent.INJECT_FAIL;
}
return MonkeyEvent.INJECT_SUCCESS;
}
}
 

参数中虽然是传进来了WindowManagerService和ActivityManagerService的服务的引用,但是最终整个注入按键事件的方法体中并没有用到。最终注入事件也是通过InputManager来实现的,并没有通过上面的这些服务。其实如果我们返回老一点的版本,会看到这里注入按键事件时使用到的会是WindowManagerService。比如我查到的MonkeyKeyEvent最后一次使用WindowManagerService来进行按键事件注入的版本是android-4.0.4_r2.1,请看下图:

图5-5-2 MonkeyKeyEvent老版本事件注入方式

MonkeyKeyEvent相关的详细分析我们留给下一章来描述,这里我们只是想通过MonkeyKeyEvent事件注入方式的变化来告诉大家其实新版本的MonkeyRunner不会再直接使用WindowManagerService来进行按键事件注入而已。

当然,InputManagerService服务管理的主要是用户输入的操作,其他一些窗口相关的操作还是需要用到WindowManagerService来进行处理的。比如模拟屏幕旋转的MonkeyRotationEvent就不属于用户输入的范畴,就需要使用到WindowManagerService服务来往系统注入相应的事件。请看下面代码:

代码5-5-3 MonkeyRotationEvent - injectEvent

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

// inject rotation event

try { 47 iwm.freezeRotation(mRotationDegree); ...

}

...

}

从以上代码我们可以看到模拟屏幕旋转的操作是通过调用WindowManagerService服务的freezeRotation方法来实现的。

那么我们往下还是看下getSystemInterfaces这个方法是如何获得这些服务的引用的:

代码5-5-4 Monkey - getSystemInterfaces

      /**
* Attach to the required system interfaces.
*
* @return Returns true if all system interfaces were available.
*/
private boolean getSystemInterfaces() {
mAm = ActivityManagerNative.getDefault();
...
mWm = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
...
mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
...
}

从以上代码可以看到这些系统服务接口的获取方式,除了ActivityManagerService外都是使用了AIDL的机制,以下简要解析下这三个服务获取的一些基本知识:

  • ActivityManagerNative.getDefault返回来的其实并不是ActivityManagerService的实例,而是代理类ActivityManagerProxy的实例,而该代理类实际上代理的就是ActivityManagerService。这里ActivityManager,ActivityManagerService和ActivityManagerProxy使用了设计模式中的代理模式。至于它们是怎么实现的就超出了本书的边界了,读者如果感兴趣的本人推荐你去看罗升阳写的《Android系统源代码情景分析》。

  • 下面的WindowManager和PackageManager都是通过Android的AIDL机制来获得的。大家应该都清楚,Android系统中的每个进程都是独立运行的,进程之间是不能直接互相调用的,它们是各自活在自己的虚拟世界里的。因此,当两个进程需要互动时就需要提供一些机制在不同进程之间进行数据通信。比如我们要使用到的这些服务都是独立的,为了使其他的应用程序也可以访问这些服务,Android系统采用了远程过程调用(Remote Procedure Call,RPC)方式来实现,在安卓中该机制叫做Binder机制,类似于Windows上的COM和Linux上的Corba。而与很多其他的基于RPC的解决方案一样,Android使用一种接口定义语言(Interface Definition Language,IDL)来公开服务的接口。841-842行调用的IWindowManagerService和IPackageManagerService的Stub.asInterface方法就是AIDL机制中获取对应远程服务的代理引用的方法。有了这些服务的代理引用后,用户就可以像本地调用一样来调用这些远程服务了。

看完这几个服务的初始化,大家可能会有点疑问,不是说现在主要是用InputManagerService服务来进行事件注入吗?怎么没有看到对这个服务的引用进行初始化啊? 其实从代码5-5-2中我们可以看到,对该服务的引用是通过InputManager的getInstance方法来实现的。其实看到这个getInstance方法,我们应该立刻能判断出它其实是一个单例模式实现的类。

代码5-5-5 InputManager - getInstance方法

        public static InputManager getInstance()
{
synchronized (InputManager.class) {
if (sInstance == null) {
IBinder b = ServiceManager.getService("input");
sInstance = new InputManager(IInputManager.Stub.asInterface(b));
}
return sInstance;
}
}

从以上代码我们看到它确实是以单例模式进行实现的,186行判断如果已经存在一个InputManager这个引用InputManagerService服务的实例的话就会跳到190行直接把改引用对象返回给调用者。

当然,如果之前就没有建立好该引用的话,那么就需要在189-188行先对InputManager这个引用对象进行初始化了。之所以这了把InputManager称呼为应用对象主要是因为它接受到用户的命令请求后,主要就是直接抛给InputManagerService服务来进行处理的。这里187行先通过ServiceManager的getService方法获得代表InputManagerService这个远程服务对象的IBinder接口(Binder是安卓进程间通信的核心机制,往往配合AIDL这个接口定义机制来使用)。所以在188行我们可以看到该代码先是通过InputManager的接口IIputManager.Stub.asInterface方法获得InputManagerService的代理引用,然后再把该引用对象作为参数传给InputManager的构造函数来保存起来。这样的话当我们调用InputManager实例对象来进行事件注入的话就可以直接通过刚保存起来的对InputManagerService的引用来请求InputManagerService服务来注入事件了。

这一节我们在分析获取系统服务引用的过程中顺带简单描述了下这些服务的一些背景知识,但这里需要再次强调的是安卓的服务和进程间通信IPC机制的知识是远不止此的,但由于它们的机制以及实现的细节并不是本书的重点,所以这里只是简单的描述,如果大家对它们的机制和实现感兴趣的,可以自行进行安卓操作系统源码的分析,或者查阅其他相关书籍,比如上面提到的罗升阳著的《安卓系统源码情景分析》。

老李推荐:第5章5节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 获取系统服务引用的更多相关文章

  1. 老李推荐:第5章7节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 循环获取并执行事件 - runMonkeyCycles

    老李推荐:第5章7节<MonkeyRunner源码剖析>Monkey原理分析-启动运行: 循环获取并执行事件 - runMonkeyCycles   poptest是国内唯一一家培养测试开 ...

  2. 老李推荐:第5章6节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 初始化事件源

    老李推荐:第5章6节<MonkeyRunner源码剖析>Monkey原理分析-启动运行: 初始化事件源   poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试 ...

  3. 老李推荐:第5章3节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 启动脚本

    老李推荐:第5章3节<MonkeyRunner源码剖析>Monkey原理分析-启动运行: 启动脚本   poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性 ...

  4. 老李推荐:第5章2节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 启动流程概览

    老李推荐:第5章2节<MonkeyRunner源码剖析>Monkey原理分析-启动运行: 启动流程概览   每个应用都会有一个入口方法来供操作系统调用执行,Monkey这个应用的入口方法就 ...

  5. 老李推荐:第5章1节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 官方简介

    老李推荐:第5章1节<MonkeyRunner源码剖析>Monkey原理分析-启动运行: 官方简介   在MonkeyRunner的框架中,Monkey是作为一个服务来接受来自Monkey ...

  6. 第5章1节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 官方简介(原创)

    天地会珠海分舵注:本来这一系列是准备出一本书的,详情请见早前博文“寻求合作伙伴编写<深入理解 MonkeyRunner>书籍“.但因为诸多原因,没有如愿.所以这里把草稿分享出来,所以错误在 ...

  7. 老李推荐:第14章9节《MonkeyRunner源码剖析》 HierarchyViewer实现原理-遍历控件树查找控件

    老李推荐:第14章9节<MonkeyRunner源码剖析> HierarchyViewer实现原理-遍历控件树查找控件   poptest是国内唯一一家培养测试开发工程师的培训机构,以学员 ...

  8. 老李推荐:第14章5节《MonkeyRunner源码剖析》 HierarchyViewer实现原理-装备ViewServer-查询ViewServer运行状态

    老李推荐:第14章5节<MonkeyRunner源码剖析> HierarchyViewer实现原理-装备ViewServer-查询ViewServer运行状态   poptest是国内唯一 ...

  9. 老李推荐:第14章6节《MonkeyRunner源码剖析》 HierarchyViewer实现原理-装备ViewServer-启动ViewServer

    老李推荐:第14章6节<MonkeyRunner源码剖析> HierarchyViewer实现原理-装备ViewServer-启动ViewServer   poptest是国内唯一一家培养 ...

随机推荐

  1. DevExpress 控件使用之GridControl基本属性设置

    DEV控件:gridControl常用属性设置     1.隐藏最上面的GroupPanel(实现方法两种)     ①代码实现:gridView1.OptionsView.ShowGroupPane ...

  2. BZOJ 1103: [POI2007]大都市meg(dfs序,树状数组)

    本来还想链剖的,结果才发现能直接树状数组的= = 记录遍历到达点与退出点的时间,然后一开始每个到达时间+1,退出时间-1,置为公路就-1,+1,询问直接点1到该点到达时间求和就行了- - CODE: ...

  3. 结合prototype和xmlhttprequest封装ajax请求

    由于拖延症的严重以及年前准备年会(借口*^__^*) 导致这个小的的思考  现在才算完成 再怎么说也算是上班以来带我的前辈第一次这么正式的给我出题 不管是出于尊重还是自我要求我都决定把它简要的记下来 ...

  4. redis的数据类型 (一) 字符串

    redis中存储方式是以键值对存储的,所以叫做字典试.redis(Remote dictionary server)远程字典服务器 每个redis数据类型,都会有增加.删除,查看的功能,用实例来学习命 ...

  5. 从数组中每次取一个不同的数组成员 getRandomItem(arr)

    积累些常用的方法, 都是随写的, 不好之处, 望指出. getRandomItem(arr)函数如下: var getRandomItem = function () { var preItem = ...

  6. python实现视频下载

    最近一两年短视频业务风生水起,各个视频网站都有各自特色的短视频内容.如果有这样一个程序,可以把各大视频网站的热门用户最新发布的视频都下载下来,不仅方便自己观看,还可以将没有版权的视频发布在个人社交网站 ...

  7. SEO-友情链接注意事项

    为什么要专门给友链一个区域呢?由此就可以想象到友情链接对一个网站有多重要前期,网站没有权重的时候,跟别人换友链,人家基本是不会换的因为你网站没权重,加了友链他也获取不到权重,对网站没有多少好处一般我们 ...

  8. SQL中PIVOT和UNPIVOT行列转换

    DECLARE @sql_col VARCHAR(8000); DECLARE @sql_str VARCHAR(8000); DECLARE @sql_ VARCHAR(MAX); SELECT @ ...

  9. Docker基础入门及示例

    Docker近几年的发展可谓一日千里,特别从是2013年随着一个基于LXC的高级容器引擎开源,到现在,其在linux和windows上都有了很好的支持,并且已经有很多公司将docker用于实际的生产环 ...

  10. U-Boot 内核 (一)

    1.首先安装Vmware,安装Ubuntu 15.04 (安装时记住用户名和密码) 2.终端命令Ctrl+Alt+T 3.进行准备工作 安装工具 sudo apt-get update sudo ap ...