Android 性能测试之 Monkey
上一篇 / 下一篇 2017-07-17 14:36:40 / 个人分类:其他测试工具
这里简单说一下monkey的实现原理。
起步
当你执行adb shell monkey的时候,它到底干了什么。
monkey位于/system/bin目录下。内容为:
首先,这个app_process是什么呢?
app_process是Android的系统启动进程,用于启动zygote和其他java进程:
更详细的内容,需要阅读android源码,这里不做详细扩展。
adb这里是runtime执行com.android.internal.os.RuntimeInit来启动,位置在:
/system/framework/下面。有很多系统的包,其中有一个/system/framework/monkey.jar为monkey的所在包。
com.android.commands.monkey.Monkey
Application that injects random key events and other actions into the system.
下面,我们一步一步讲解一下:
看一下run具体方法:
monkey中注入系统事件是通过使用内部API来实现的(activemanger, windowmanger, packagemanger),其他方式(instrumentation)只能是二等公民。
我们看一下monkey的事件列表类:
monkey有11种事件,在MonkeyEventSource中有事件的比例设置。
下面,我们来看monekey的核心执行逻辑;
回到之前的代码逻辑,这个mEventSource有三种来源:
好,我们这里展开说一下脚本模式怎么使用monkey.先写一个简单的monkey事件脚本文件:
那这个脚本是怎么解析的呢?(这里不详细展开):
就是酱紫。执行一下我们的脚本(命令列表):
你可以看到,我们滑动到了底部,然后打开了第二个TAB。当然,我们可以直接通过adb shell来执行上面的操作:
这里使用的是input命令来执行。和monkey一样,input是一个脚本,执行的是/system/framework/input.jar:
回到monkey上去,上面说到
int injectCode = ev.injectEvent(mWm, mAm, mVerbose);
这个事件来源三类,我们现在看默认的随机事件(MonkeySourceRandom),它的getnextevent返回多种随机事件,这里以MonkeyMotionEvent为例进行说明
其他如MonkeyRotationEvent,使用iwm.freezeRotation(mRotationDegree);来实现旋转屏幕。
总结
monkey事件来源三种:默认随机事件、脚本定义事件、network网络事件;
monkey事件根据类型比例生成事件队列,循环查找事件;
monkey事件的实现使用系统内部API(activemanager,inputmanager,windowmanager)来实现;
起步
当你执行adb shell monkey的时候,它到底干了什么。
monkey位于/system/bin目录下。内容为:
# Script. to start "monkey" on the device, which has a very rudimentary # shell. # base=/system export CLASSPATH=$base/framework/monkey.jar trap "" HUP exec app_process $base/bin com.android.commands.monkey.Monkey $* |
app_process是Android的系统启动进程,用于启动zygote和其他java进程:
if (zygote) { runtime.start("com.android.internal.os.ZygoteInit", args, zygote); } else if (className) { runtime.start("com.android.internal.os.RuntimeInit", args, zygote); } |
adb这里是runtime执行com.android.internal.os.RuntimeInit来启动,位置在:
/system/framework/下面。有很多系统的包,其中有一个/system/framework/monkey.jar为monkey的所在包。
com.android.commands.monkey.Monkey
Application that injects random key events and other actions into the system.
下面,我们一步一步讲解一下:
public static void main(String[] args) { // Set the process name showing in "ps" or "top" Process.setArgV0("com.android.commands.monkey"); int resultCode = (new Monkey()).run(args); System.exit(resultCode); } |
monkey中注入系统事件是通过使用内部API来实现的(activemanger, windowmanger, packagemanger),其他方式(instrumentation)只能是二等公民。
private int run(String[] args) { processOptions();//处理参数 loadPackageLists();//加载黑白名单,可测的有效包名 getSystemInterfaces();//获取系统接口,都是系统的隐藏接口。 //mAm = ActivityManagerNative.getDefault(); //这里返回了一个ActivityManagerProxy对象,用来执行mangerservice接口。 //mWm = IWindowManager.Stub.asInterface(ServiceManager.getService("window")); //上面,获取了系统窗口服务 //mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package")); getMainApps();//获取要执行的activity mEventSource = new MonkeySourceRandom(mRandom, mMainApps, mThrottle, mRandomizeThrottle, mPermissionTargetSystem);//产生一个随机事件 ((MonkeySourceRandom) mEventSource).setFactors(i, mFactors[i]); mEventSource.validate();//验**,并调整比例 mNetworkMonitor.start();//监听网络变化 crashedAtCycle = runMonkeyCycles();//monkey核心逻辑 } |
public abstract class MonkeyEvent { protected int eventType; public static final int EVENT_TYPE_KEY = 0; public static final int EVENT_TYPE_TOUCH = 1; public static final int EVENT_TYPE_TRACKBALL = 2; public static final int EVENT_TYPE_ROTATION = 3; // Screen rotation public static final int EVENT_TYPE_ACTIVITY = 4; public static final int EVENT_TYPE_FLIP = 5; // Keyboard flip public static final int EVENT_TYPE_THROTTLE = 6; public static final int EVENT_TYPE_PERMISSION = 7; public static final int EVENT_TYPE_NOOP = 8; public static final int INJECT_SUCCESS = 1; public static final int INJECT_FAIL = 0; // error code for remote exception during injection public static final int INJECT_ERROR_REMOTE_EXCEPTION = -1; // error code for security exception during injection public static final int INJECT_ERROR_SECURITY_EXCEPTION = -2; public MonkeyEvent(int type) { eventType = type; } ... |
下面,我们来看monekey的核心执行逻辑;
while (!systemCrashed && cycleCounter < mCount) { //检查是否发生**R if (mRequestAnrBugreport){ getBugreport("anr_" + mReportProcessName + "_"); mRequestAnrBugreport = false; } //检查系统watchdog是否报告bug if (mRequestWatchdogBugreport) { System.out.println("Print the watchdog report"); getBugreport("anr_watchdog_"); mRequestWatchdogBugreport = false; } //检查是否发生了CRASH if (mRequestAppCrashBugreport){ getBugreport("app_crash" + mReportProcessName + "_"); mRequestAppCrashBugreport = false; } //检查bugreport报告生成 if (mRequestPeriodicBugreport){ getBugreport("Bugreport_"); mRequestPeriodicBugreport = false; } //报告系统信息,ANR时出发 if (mRequestDumpsysMemInfo) { mRequestDumpsysMemInfo = false; shouldReportDumpsysMemInfo = true; } //获取下一个随机时间 MonkeyEvent ev = mEventSource.getNextEvent(); //注入事件 int injectCode = ev.injectEvent(mWm, mAm, mVerbose); } |
//脚本模式 mEventSource = new MonkeySourceScript(mRandom, mScriptFileNames.get(0), mThrottle, mRandomizeThrottle, mProfileWaitTime, mDeviceSleepTime); mEventSource = new MonkeySourceRandomScript(mSetupFileName, mScriptFileNames, mThrottle, mRandomizeThrottle, mRandom, mProfileWaitTime, mDeviceSleepTime, mRandomizeScript); //网络模式,monkeyrunner的使用方式 mEventSource = new MonkeySourceNetwork(mServerPort); //默认模式,一般都使用随机事件 mEventSource = new MonkeySourceRandom(mRandom, mMainApps, mThrottle, mRandomizeThrottle, mPermissionTargetSystem); |
/** * monkey event queue. It takes a script. to produce events sample script. format: * * <pre> * type= raw events * count= 10 * speed= 1.0 * start data >> * captureDispatchPointer(5109520,5109520,0,230.75429,458.1814,0.20784314,0.06666667,0,0.0,0.0,65539,0) * captureDispatchKey(5113146,5113146,0,20,0,0,0,0) * captureDispatchFlip(true) * ... * </pre> */ #我们以小米商城为例,进入商城,滑动到最下面 type= user count= 49 speed= 1.0 start data >> LaunchActivity(com.xiaomi.shop, com.xiaomi.shop.activity.MainTabActivity) #wait for launch UserWait(10000) #drag to down Drag(542,1326,542,560,15) #wait for 500 milliseconds UserWait(500) #tap second tab Tap(346,1868) |
readHeader();//打开文件,读文件头,设置参数,文件头的结尾必须是:STARTING_DATA_LINE 当然,脚本中也可以不写文件头的。 readLines(); readNextBatch(); processLine();//处理每一行命令,加入事件队列中。命令包括: ```java // event key word in the capture log private static final String EVENT_KEYWORD_POINTER = "DispatchPointer"; private static final String EVENT_KEYWORD_TRACKBALL = "DispatchTrackball"; private static final String EVENT_KEYWORD_ROTATION = "RotateScreen"; private static final String EVENT_KEYWORD_KEY = "DispatchKey"; private static final String EVENT_KEYWORD_FLIP = "DispatchFlip"; private static final String EVENT_KEYWORD_KEYPRESS = "DispatchPress"; private static final String EVENT_KEYWORD_ACTIVITY = "LaunchActivity"; private static final String EVENT_KEYWORD_INSTRUMENTATION = "LaunchInstrumentation"; private static final String EVENT_KEYWORD_WAIT = "UserWait"; private static final String EVENT_KEYWORD_LONGPRESS = "LongPress"; private static final String EVENT_KEYWORD_POWERLOG = "PowerLog"; private static final String EVENT_KEYWORD_WRITEPOWERLOG = "WriteLog"; private static final String EVENT_KEYWORD_RUNCMD = "RunCmd"; private static final String EVENT_KEYWORD_TAP = "Tap";//点击,轻触 private static final String EVENT_KEYWORD_PROFILE_WAIT = "ProfileWait"; private static final String EVENT_KEYWORD_DEVICE_WAKEUP = "DeviceWakeUp"; private static final String EVENT_KEYWORD_INPUT_STRING = "DispatchString"; private static final String EVENT_KEYWORD_PRESSANDHOLD = "PressAndHold"; // private static final String EVENT_KEYWORD_DRAG = "Drag"; //拖动 private static final String EVENT_KEYWORD_PINCH_ZOOM = "PinchZoom"; private static final String EVENT_KEYWORD_START_FRAMERATE_CAPTURE = "StartCaptureFramerate"; private static final String EVENT_KEYWORD_END_FRAMERATE_CAPTURE = "EndCaptureFramerate"; private static final String EVENT_KEYWORD_START_APP_FRAMERATE_CAPTURE = "StartCaptureAppFramerate"; private static final String EVENT_KEYWORD_END_APP_FRAMERATE_CAPTURE = "EndCaptureAppFramerate"; |
adb -s 8b52f091 push d:\script.txt /sdcard/data monkey -f /sdcard/data/script.txt 1 |
adb shell input swipe 542 1326 560 15 adb shell input swipe 542 1326 560 15 adb shell input tap 346 1868 |
$ cat /system/bin/input # Script. to start "input" on the device, which has a very rudimentary # shell. # base=/system export CLASSPATH=$base/framework/input.jar exec app_process $base/bin com.android.commands.input.Input "$@" |
int injectCode = ev.injectEvent(mWm, mAm, mVerbose);
这个事件来源三类,我们现在看默认的随机事件(MonkeySourceRandom),它的getnextevent返回多种随机事件,这里以MonkeyMotionEvent为例进行说明
@Override 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 { //InputManager.getInstance返回input manager的实例 //Injects an input event into the event system on behalf of an application //注入事件 if (!InputManager.getInstance().injectInputEvent(me, InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT)) { return MonkeyEvent.INJECT_FAIL; } } finally { me.recycle(); } return MonkeyEvent.INJECT_SUCCESS; } |
总结
monkey事件来源三种:默认随机事件、脚本定义事件、network网络事件;
monkey事件根据类型比例生成事件队列,循环查找事件;
monkey事件的实现使用系统内部API(activemanager,inputmanager,windowmanager)来实现;
TAG: