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

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://techgogogo.blog.csdn.net/article/details/44121375

天地会珠海分舵注:本来这一系列是准备出一本书的,详情请见早前博文“寻求合作伙伴编写《深入理解 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 安卓架构图
图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

展开阅读全文

求助,monkeymonkeyrunner相关

02-13

最近研究了一下monkeyrunner的源码,发现它其实是依赖chimpchat实现的,跟了一下代码,最主要的原理就是在使用了adb forward和adb shell monkey --port两个命令后,通过socket将自定义格式的命令从PC端发送给设备上的monkey,让monkey去执行。对应的代码在MonkeySourceNetwork和MonkeySourceNetworkViews两个类中。rnSDK文档说The monkeyrunner tool is not related to the UI/Application Exerciser Monkey,其实他们两个关系非常密切。rn我直接在命令行执行forward和monkey --port命令后,用telnet去输入那些命令,这里就遇到问题了,像press,touch,key这些命令都能执行并得到回馈,但是如果执行listviews或者queryview这样的命令,就会没反应,如果是直接调试PC端这边的chimpchat,可以发现这边的代码是一直阻塞在readLine()方法。rn想请教一下高手,有些命令为什么直接在telnet里面无法执行,是还需要其他的命令还配合还是什么?rn下面是命令列表,大家可以试一下[code=Java]static rn // Add in all the commands we supportrn COMMAND_MAP.put("flip", new FlipCommand());rn COMMAND_MAP.put("touch", new TouchCommand());rn COMMAND_MAP.put("trackball", new TrackballCommand());rn COMMAND_MAP.put("key", new KeyCommand());rn COMMAND_MAP.put("sleep", new SleepCommand());rn COMMAND_MAP.put("wake", new WakeCommand());rn COMMAND_MAP.put("tap", new TapCommand());rn COMMAND_MAP.put("press", new PressCommand());rn COMMAND_MAP.put("type", new TypeCommand());rn COMMAND_MAP.put("listvar", new MonkeySourceNetworkVars.ListVarCommand());rn COMMAND_MAP.put("getvar", new MonkeySourceNetworkVars.GetVarCommand());rn COMMAND_MAP.put("listviews", new MonkeySourceNetworkViews.ListViewsCommand());rn COMMAND_MAP.put("queryview", new MonkeySourceNetworkViews.QueryViewCommand());rn COMMAND_MAP.put("getrootview", new MonkeySourceNetworkViews.GetRootViewCommand());rn COMMAND_MAP.put("getviewswithtext",rn new MonkeySourceNetworkViews.GetViewsWithTextCommand());rn COMMAND_MAP.put("deferreturn", new DeferReturnCommand());rn [/code]rnrn //queryview [id type] [id(s)] [command]rn //queryview viewid button1 gettextrn //queryview accessibilityids 12 5 getparent 论坛

最全面试题第5

04-07

<p>n <span style="color:#000000;"><strong>本套面试题将Java中的各个知识点模块混合详讲,讲解的非常详细,是一套极好的面试题宝典哦。本套课程涵盖的知识点有:SpringBoot、SpringCloud、SpringMVC、Spring、Mybatis、MySQL、Redis、Oracle、秒杀等</strong></span>n</p>n<p>n <span style="color:#424242;">我所有课程的学习主线为:java基础---&gt;面向对象---&gt;java高级部分(集合、多线程、正则表达式等)---&gt;html5+css3---&gt;javascript---&gt;jquery---&gt;mysql数据库---&gt;jsp/servlet---&gt;oracle数据库---&gt;项目讲解---&gt;Mybatis框架----&gt;spring框架----&gt;springmvc框架---&gt;ssm三大框架整合---&gt;maven---&gt;SVN/GIT---&gt;hibernate框架---&gt;struts2框架---&gt;linux---&gt;SSM项目综合小练习---&gt;SpringBoot---&gt;SpringCloud---&gt;Redis---&gt;MongoDB---&gt;FreeMaker---&gt;Nginx---&gt;RabbitMQ---&gt;Dubbo---&gt;Zookeeper---&gt;分布式项目实战等。红色部分是本套课程的教学内容,学习贵在坚持,希望同学们按照这个学习主线坚持下去,后面一定会找到满意的工作的。不懂得可以多咨询我,我收到消息后会给大家回复的。</span>n</p>n<p>n <span style="color:#424242;"><img src="https://img-bss.csdn.net/201904070903499180.jpg" alt="" /><img src="https://img-bss.csdn.net/201904070903557913.jpg" alt="" /><img src="https://img-bss.csdn.net/201904070904029548.jpg" alt="" /><br /></span> n</p>

没有更多推荐了,返回首页