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

天地会珠海分舵注:本来这一系列是准备出一本书的,详情请见早前博文“寻求合作伙伴编写《深入理解 MonkeyRunner》书籍“。但因为诸多原因,没有如愿。所以这里把草稿分享出来,所以错误在所难免。有需要的就参考下吧,转发的话还请保留每篇文章结尾的出处等信息。

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

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

代码2-5-1 Monkey - run

 431     private int run(String[] args) {
        ...
 450         if (!processOptions()) {
 451             return -1;
 452         }
        ...
 488         if (!getSystemInterfaces()) {
 489             return -3;
 490         }
        ...
}

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示例

 85     @Override
 86     public int injectEvent(IWindowManager iwm, IActivityManager iam, int verbose) {
 87         if (verbose > 1) {
 88             String note;
 89             if (mAction == KeyEvent.ACTION_UP) {
 90                 note = "ACTION_UP";
 91             } else {
 92                 note = "ACTION_DOWN";
 93             }
 94             try {
 95                 System.out.println(":Sending Key (" + note + "): "
 96                         + mKeyCode + "    // "
 97                         + MonkeySourceRandom.getKeyName(mKeyCode));
 98             } catch (ArrayIndexOutOfBoundsException e) {
 99                 System.out.println(":Sending Key (" + note + "): "
100                         + mKeyCode + "    // Unknown key event");
101             }
102         }
103         KeyEvent keyEvent = mKeyEvent;
104         if (keyEvent == null) {
105             long eventTime = mEventTime;
106             if (eventTime <= 0) {
107                 eventTime = SystemClock.uptimeMillis();
108             }
109             long downTime = mDownTime;
110             if (downTime <= 0) {
111                 downTime = eventTime;
112             }
113             keyEvent = new KeyEvent(downTime, eventTime, mAction, mKeyCode,
114                     mRepeatCount, mMetaState, mDeviceId, mScanCode,
115                     KeyEvent.FLAG_FROM_SYSTEM, InputDevice.SOURCE_KEYBOARD);
116         }
117         if (!InputManager.getInstance().injectInputEvent(keyEvent,
118                 InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT)) {
119             return MonkeyEvent.INJECT_FAIL;
120         }
121         return MonkeyEvent.INJECT_SUCCESS;
122     }
123 }

参数中虽然是传进来了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

 39     @Override
 40     public int injectEvent(IWindowManager iwm, IActivityManager iam, int verbose) {
        ...
 45         // inject rotation event
 46         try {
 47             iwm.freezeRotation(mRotationDegree);
        ...
 50             }
        ...
 55     }

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

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

代码5-5-4 Monkey - getSystemInterfaces

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

从以上代码可以看到这些系统服务接口的获取方式,除了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方法

 183       public static InputManager getInstance()
 184    {
 185      synchronized (InputManager.class) {
 186       if (sInstance == null) {
 187         IBinder b = ServiceManager.getService("input");
 188          sInstance = new InputManager(IInputManager.Stub.asInterface(b));
 189        }
 190        return sInstance;
 191      }
 192    }

从以上代码我们看到它确实是以单例模式进行实现的,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机制的知识是远不止此的,但由于它们的机制以及实现的细节并不是本书的重点,所以这里只是简单的描述,如果大家对它们的机制和实现感兴趣的,可以自行进行安卓操作系统源码的分析,或者查阅其他相关书籍,比如上面提到的罗升阳著的《安卓系统源码情景分析》。

——— 未完待续———



作者:天地会珠海分舵

微信公众号:TechGoGoGo

微博:http://weibo.com/techgogogo

CSDN:http://blog.csdn.net/zhubaitian

时间: 2024-10-12 18:36:29

第5章5节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 获取系统服务引用(原创)的相关文章

第5章2节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 启动流程概览(原创)

天地会珠海分舵注:本来这一系列是准备出一本书的,详情请见早前博文"寻求合作伙伴编写<深入理解 MonkeyRunner>书籍".但因为诸多原因,没有如愿.所以这里把草稿分享出来,所以错误在所难免.有需要的就参考下吧,转发的话还请保留每篇文章结尾的出处等信息. 每个应用都会有一个入口方法来供操作系统调用执行,Monkey这个应用的入口方法就是在Monkey.java这个类里面的,也就是说Monkey.java就是整个Monkey应用的入口类. Monkey作为一个命令行应用,

第5章3节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 启动脚本(原创)

天地会珠海分舵注:本来这一系列是准备出一本书的,详情请见早前博文"寻求合作伙伴编写<深入理解 MonkeyRunner>书籍".但因为诸多原因,没有如愿.所以这里把草稿分享出来,所以错误在所难免.有需要的就参考下吧,转发的话还请保留每篇文章结尾的出处等信息. 本节我们先看下Monkey是怎么启动起来的.在今后分析到MonkeyRunner的原理的时候我们会看到客户端是通过ADB往Android目标测试机器发送一个"monkey -port 12345"的

第3章2节《MonkeyRunner源码剖析》脚本编写示例: MonkeyDevice API使用示例(原创)

天地会珠海分舵注:本来这一系列是准备出一本书的,详情请见早前博文"寻求合作伙伴编写<深入理解 MonkeyRunner>书籍".但因为诸多原因,没有如愿.所以这里把草稿分享出来,所以错误在所难免.有需要的就参考下吧,转发的话还请保留每篇文章结尾的出处等信息. 上一节我们学习了如何通过MonkeyRunner这个类的静态方法waitForConnection来把后台和设备建立好连接,且看到了在建立连接成功后,该方法会返回来一个MonkeyDevice的实例对象.那么这一节我们

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

天地会珠海分舵注:本来这一系列是准备出一本书的,详情请见早前博文"寻求合作伙伴编写<深入理解 MonkeyRunner>书籍".但因为诸多原因,没有如愿.所以这里把草稿分享出来,所以错误在所难免.有需要的就参考下吧,转发的话还请保留每篇文章结尾的出处等信息. 在MonkeyRunner的框架中,Monkey是作为一个服务来接受来自MonkeyRunner客户端发送过来的命令,然后针对每条命令请求进行相应的处理,所以它并不是作为一个随机压力测试的工具来运作.本书中的Monke

第5章6节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 初始化事件源(原创)

天地会珠海分舵注:本来这一系列是准备出一本书的,详情请见早前博文"寻求合作伙伴编写<深入理解 MonkeyRunner>书籍".但因为诸多原因,没有如愿.所以这里把草稿分享出来,所以错误在所难免.有需要的就参考下吧,转发的话还请保留每篇文章结尾的出处等信息. 事件源代表要注入系统的命令事件数据是从哪里过来的.这一小节我们不会对事件源的实现进行深入的分析,因为下一章会做这个事情.这里大家对事件源有个基本概念就足够了. 对Monkey来说,事件的来源可以有多个地方,比如我们用它

第5章4节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 命令行参数解析(原创)

天地会珠海分舵注:本来这一系列是准备出一本书的,详情请见早前博文"寻求合作伙伴编写<深入理解 MonkeyRunner>书籍".但因为诸多原因,没有如愿.所以这里把草稿分享出来,所以错误在所难免.有需要的就参考下吧,转发的话还请保留每篇文章结尾的出处等信息. 设置好Monkey的CLASSPATH环境变量以指定"/system/framework /framework/monkey.jar"后,/system/bin/monkey这个shell脚本就会通

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

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

第3章3节《MonkeyRunner源码剖析》脚本编写示例: MonkeyImage API使用示例(原创)

天地会珠海分舵注:本来这一系列是准备出一本书的,详情请见早前博文"寻求合作伙伴编写<深入理解 MonkeyRunner>书籍".但因为诸多原因,没有如愿.所以这里把草稿分享出来,所以错误在所难免.有需要的就参考下吧,转发的话还请保留每篇文章结尾的出处等信息. 在上一节的第一个"增加日记"的示例中,我们并没有看到日记是否真的增加成功了,也就是说当时并没有进行结果比较.其实在MonkeyRunner框架中,测试结果的比较往往都是通过截屏比对来完成的.而截屏比

老李推荐: 第8章4节《MonkeyRunner源码剖析》MonkeyRunner启动运行过程-启动AndroidDebugBridge 1

老李推荐: 第8章4节<MonkeyRunner源码剖析>MonkeyRunner启动运行过程-启动AndroidDebugBridge 上一节我们看到在启动AndroidDebugBridge的过程中会调用其start方法,而该方法会做2个主要的事情: 715行startAdb:开启AndroidDebugBridge 722-723行:初始化android设备监控并启动DeviceMonitor设备监控线程. 其中第一点我们上一小节已经做了详尽分析了,那么我们往下就去分析下第2点. Dev