一、Monkey测试简介

monkey测试是Android压力测试的一种手段,通过monkey程序随机模拟用户触摸屏幕、滑动Trackball、按键等操作来对设备上的程序进行压力测试,检测程序会发生的各种异常。
我们可以通过执行:adb shell monkey { +命令参数 } 进行Monkey测试。
例如执行一次最简单的monkey测试:

adb shell monkey -p yourPackageName -v optionTime

其中-p参数后跟着想要测试的apk包名,-v参数后跟着要执行的操作次数。
下面我执行的就是网易云阅读随机操作200次的monkey测试。


monkey测试遇到crash会停止,但是通过一些参数的设置可以设置遇到crash继续,也可以设置多个测试包、延时时间等。具体的参数设置可参考Android monkey官网介绍:
https://developer.android.com/studio/test/monkey.html

二、Monkey源码分析

monkey源码下载地址:
https://code.google.com/archive/p/androidmonkey/downloads


其中AndroidMonkey.tgz就是Android monkey的源代码。

1. 当执行adb shell monkey的时候发生了什么

我们通过adb shell进入模拟器或者真机的Android系统,在/system/bin/目录下可以看到monkey执行脚本,也就是执行adb shell monkey的时候执行到的文件。我们看下这个文件:


这个普通的shell文件,关键执行了app_process这条命令。app_process是Android的系统启动进程,用于启动zygote和其他java进程(使用adb的诸多java命令许多都是app_process启动的)。monkey.jar包的位置位于/system/framework/目录下,而进程的入口就是monkey.jar包的com.android.commands.monkey.Monkey.java文件。

2.monkey命令入口

在Monkey.java文件中,我们可以找到monkey命令的入口main方法。

public static void main(String[] args) {        int resultCode = (new Monkey()).run(args);        System.exit(resultCode);    }

这里可以看到入口Monkey.java的run(args)方法。

3.monkey执行流程

下面将monkey执行流程里的关键代码摘出来了,从中可以较为清晰地看出monkey的一般执行流程。

private int run(String[] args) {  //初始化各个参数  processOptions();//处理从命令行接收到的参数,并赋给相应的变量  loadPackageLists();//加载设置的白名单或者黑名单  getSystemInterfaces();//获得系统接口  getMainApps();//获取要执行的activity  if (mScriptFileName != null) {      mEventSource = new MonkeySourceScript(mScriptFileName);//如果有脚本文件输入的话,就new一个MonkeySourceScript对象。      mEventSource.setVerbose(mVerbose);  } else {      mEventSource = new MonkeySourceRandom(mSeed, mMainApps, mThrottle, mThrottleTouch, mThrottleKey);//如果没有脚本文件输入的话,就new一个MonkeySourceRandom对象。也是我们常用的简单操作。      mEventSource.setVerbose(mVerbose);  }      ((MonkeySourceRandom) mEventSource).setFactors(i, mFactors[i]);//设置事件的操作比例。  mEventSource.validate();//调整事件比例。  ((MonkeySourceRandom) mEventSource).generateActivity();//随机事件下,产生一个随机的Activity。   int crashedAtCycle = runMonkeyCycles();//monkey的核心事件注入逻辑}

在runMonkeyCycles()方法中,就是不断地执行注入事件操作。在介绍这部分代码之前,有必要先宏观上了解下monkey的类结构。

4.monkey类结构

monkey源代码主要有若干个java文件,除了入口Monkey.java文件及工具文件外,另外还有两组类文件,分别是以MonkeyEventSource为接口类的事件流处理类(MonkeySourceRandom、MonkeySourceScript等类,分别处理随机事件流和monkey脚本事件流,类图如下图一所示),以及以MonkeyEvent为接口类的事件操作类(MonkeyActivityEvent、MonkeyKeyEvent、MonkeyMotionEvent、MonkeyFlipEvent、MonkeyThrottleEvent等类,类图如下图二所示)。因事件操作类较多,只列出来常用的几个。


图一,事件流处理类 图二,事件操作类

事件流处理:我们一般默认的monkey命令都是使用random伪事件流(MonkeySourceRandom),因而一般用不到monkey脚本和网络事件流。

4.monkey核心事件注入逻辑

我们继续回到之前Monkey.java入口方法处,runMonkeyCycles()方法里处理了monkey的核心事件注入逻辑,该方法的核心代码整理如下:

while (!systemCrashed && i < mCount) {   if (mRequestAnrBugreport){     //如果页面发生了ANR,上报ANR          getBugreport("anr_" + mReportProcessName + "_");          mRequestAnrBugreport = false;   }  if (mRequestAppCrashBugreport){   //如果页面发生了crash,上报crash          getBugreport("app_crash" + mReportProcessName + "_");          mRequestAppCrashBugreport = false;    }   if (mRequestDumpsysMemInfo) {    //如果设置了发生crash后打印memento,则在发生crash后输出。         mRequestDumpsysMemInfo = false;         shouldReportDumpsysMemInfo = true;   }   if (mMonitorNativeCrashes) {   //如果在命令行设置开了监控crash开关,会打印crash        if (checkNativeCrashes() && (i > 0)) {        mAbort = mAbort || mKillProcessAfterError;        }    }   MonkeyEvent ev = mEventSource.getNextEvent();  //获得下一个注入事件   int injectCode = ev.injectEvent(mWm, mAm, mVerbose);      //注入事件}

获得下一个注入事件,通常我们选择的事件流方法是随机事件流,因而我们主要看下MonkeySourceRandom中的getNextEvent()关键方法:

public MonkeyEvent getNextEvent() {    //从MonkeyEvent队列mQ里取出MonkeyEvent事件,如果mQ当前是空的话,就生成一个事件。否则就取出队列的第一个事件。        if (mQ.isEmpty()) {            generateEvents();        }        mEventCount++;        MonkeyEvent e = mQ.getFirst();        mQ.removeFirst();        return e;    }

接下来看下如果MonkeyEvent队列mQ为空的时候,如果产生一个事件,也就是generateEvents()方法:

private void generateEvents() {        float cls = mRandom.nextFloat();    //取一个随机数        int lastKey = 0;        //依据随机数的大小,执行生成不同手势的MotionEvent事件(generatePointerEvent实质上是生成MotionEvent事件)。        if (cls < mFactors[FACTOR_TOUCH]) {            generatePointerEvent(mRandom, GESTURE_TAP);            return;        } else if (cls < mFactors[FACTOR_MOTION]) {            generatePointerEvent(mRandom, GESTURE_DRAG);            return;        } else if (cls < mFactors[FACTOR_PINCHZOOM]) {            generatePointerEvent(mRandom, GESTURE_PINCH_OR_ZOOM);            return;        } else if (cls < mFactors[FACTOR_TRACKBALL]) {            generateTrackballEvent(mRandom);            return;        }        // The remaining event categories are injected as key events        for (;;) {            if (cls < mFactors[FACTOR_NAV]) {                lastKey = NAV_KEYS[mRandom.nextInt(NAV_KEYS.length)];            } else if (cls < mFactors[FACTOR_MAJORNAV]) {                lastKey = MAJOR_NAV_KEYS[mRandom.nextInt(MAJOR_NAV_KEYS.length)];            } else if (cls < mFactors[FACTOR_SYSOPS]) {                lastKey = SYS_KEYS[mRandom.nextInt(SYS_KEYS.length)];            } else if (cls < mFactors[FACTOR_APPSWITCH]) {                MonkeyActivityEvent e = new MonkeyActivityEvent(mMainApps.get(                        mRandom.nextInt(mMainApps.size())));                mQ.addLast(e);                return;            } else if (cls < mFactors[FACTOR_FLIP]) {                MonkeyFlipEvent e = new MonkeyFlipEvent(mKeyboardOpen);                mKeyboardOpen = !mKeyboardOpen;                mQ.addLast(e);                return;            } else {                lastKey = 1 + mRandom.nextInt(KeyEvent.getMaxKeyCode() - 1);            }            if (lastKey != KeyEvent.KEYCODE_POWER                    && lastKey != KeyEvent.KEYCODE_ENDCALL                    && PHYSICAL_KEY_EXISTS[lastKey]) {                break;            }        }        MonkeyKeyEvent e = new MonkeyKeyEvent(KeyEvent.ACTION_DOWN, lastKey);        mQ.addLast(e);        e = new MonkeyKeyEvent(KeyEvent.ACTION_UP, lastKey);        mQ.addLast(e);    }

我们拿MonkeyMotionEvent(点击操作)为例,看下关键的注入事件方法是如何操作的:

public int injectEvent(IWindowManager iwm, IActivityManager iam, int verbose) {        MotionEvent me = getEvent();        if ((verbose > 0 && !mIntermediateNote) || verbose > 1) {            StringBuilder msg = new StringBuilder(":Sending ");            msg.append(getTypeLabel()).append(" (");            switch (me.getActionMasked()) {                case MotionEvent.ACTION_DOWN:                    msg.append("ACTION_DOWN");                    break;                case MotionEvent.ACTION_MOVE:                    msg.append("ACTION_MOVE");                    break;                case MotionEvent.ACTION_UP:                    msg.append("ACTION_UP");                    break;                case MotionEvent.ACTION_CANCEL:                    msg.append("ACTION_CANCEL");                    break;                case MotionEvent.ACTION_POINTER_DOWN:                    msg.append("ACTION_POINTER_DOWN ").append(me.getPointerId(me.getActionIndex()));                    break;                case MotionEvent.ACTION_POINTER_UP:                    msg.append("ACTION_POINTER_UP ").append(me.getPointerId(me.getActionIndex()));                    break;                default:                    msg.append(me.getAction());                    break;            }            msg.append("):");            int pointerCount = me.getPointerCount();            for (int i = 0; i < pointerCount; i++) {                msg.append(" ").append(me.getPointerId(i));                msg.append(":(").append(me.getX(i)).append(",").append(me.getY(i)).append(")");            }            System.out.println(msg.toString());        }        try {//            if (!injectMotionEvent(iwm, me)) {                return MonkeyEvent.INJECT_FAIL;            }        } catch (RemoteException ex) {            return MonkeyEvent.INJECT_ERROR_REMOTE_EXCEPTION;        } finally {            me.recycle();        }        return MonkeyEvent.INJECT_SUCCESS;    }}

可见MonkeyMotionEvent事件注入最后还是利用了Android的MotionEvent和injectMotionEvent方法进行了事件注入。

以上,就是Android monkey源码的基本分析,核心就是通过随机算法选择操作行为和操作位置,从而实现完全的随机压力测试。我们可以通过对monkey做二次改造进而实现更复杂、定制化的功能。需要进一步的探究和实践。

更多相关文章

  1. Ubuntu下安装Android(安卓)Studio全过程(2015.01.27):高阶用户,绝对
  2. Android(安卓)事件分发机制(最新源码6.0分析)--ViewGrop
  3. Android触发器组件BroadcastReceiver详解
  4. Android(安卓)API Guides---Calendar Provider
  5. Android进程优先级部分整理与理解
  6. Android控件:EditText之setOnEditorActionListener的使用
  7. JAVA代码执行shell命令 并解析
  8. Android(安卓)webkit 事件传递流程通道分析
  9. android中的 touch事件

随机推荐

  1. MySQL 8.0.20 安装教程图文详解(windows
  2. MySQL8.0.20安装教程及其安装问题详细教
  3. mysql闪回工具binlog2sql安装配置教程详
  4. Mongodb中关于GUID的显示问题详析
  5. MySQL必备的常见知识点汇总整理
  6. mysql查询每小时数据和上小时数据的差值
  7. MySQL使用mysqldump+binlog完整恢复被删
  8. mysql条件查询and or使用方法及优先级实
  9. MySQL 表数据的导入导出操作示例
  10. MySQL 8.0用户和角色管理原理与用法详解