Monkey压力测试源码执行流程分析
Monkey是Android提供的用于应用程序自动化测试、压力测试的测试工具。
其源码路径(Android12)位于
/development/cmds/monkey/
部署形式为Java Binary
# development/cmds/monkey/Android.bp
// Copyright 2008 The Android Open Source Project
//
package {
default_applicable_licenses: ["development_cmds_monkey_license"],
}
// See: http://go/android-license-faq
license {
name: "development_cmds_monkey_license",
visibility: [":__subpackages__"],
license_kinds: [
"SPDX-license-identifier-Apache-2.0",
],
license_text: [
"NOTICE",
],
}
//###############################################################
java_binary {
name: "monkey",
srcs: ["**/*.java"],
wrapper: "monkey",
}
通过Monkey,可以模拟用户的Touch(单指、多指、手势)、按键(key)事件等,检测应用程序发生的ANR、Crash事件,并收集相关Debug信息等。
例如测试应用com.package.linduo,
adb shell monkey -p com.package.linduo --pct-touch 10 --pct-motion 20 10000
# 该命令表示,执行1万次测试事件,其中Touch事件占10%,Motion事件占20%
# 或者adb shell进入android终端,直接使用monkey命令
Monkey支持的命令
private void showUsage() {
StringBuffer usage = new StringBuffer();
usage.append("usage: monkey [-p ALLOWED_PACKAGE [-p ALLOWED_PACKAGE] ...]\n");
usage.append(" [-c MAIN_CATEGORY [-c MAIN_CATEGORY] ...]\n");
usage.append(" [--ignore-crashes] [--ignore-timeouts]\n");
usage.append(" [--ignore-security-exceptions]\n");
usage.append(" [--monitor-native-crashes] [--ignore-native-crashes]\n");
usage.append(" [--kill-process-after-error] [--hprof]\n");
usage.append(" [--match-description TEXT]\n");
usage.append(" [--pct-touch PERCENT] [--pct-motion PERCENT]\n");
usage.append(" [--pct-trackball PERCENT] [--pct-syskeys PERCENT]\n");
usage.append(" [--pct-nav PERCENT] [--pct-majornav PERCENT]\n");
usage.append(" [--pct-appswitch PERCENT] [--pct-flip PERCENT]\n");
usage.append(" [--pct-anyevent PERCENT] [--pct-pinchzoom PERCENT]\n");
usage.append(" [--pct-permission PERCENT]\n");
usage.append(" [--pkg-blacklist-file PACKAGE_BLACKLIST_FILE]\n");
usage.append(" [--pkg-whitelist-file PACKAGE_WHITELIST_FILE]\n");
usage.append(" [--wait-dbg] [--dbg-no-events]\n");
usage.append(" [--setup scriptfile] [-f scriptfile [-f scriptfile] ...]\n");
usage.append(" [--port port]\n");
usage.append(" [-s SEED] [-v [-v] ...]\n");
usage.append(" [--throttle MILLISEC] [--randomize-throttle]\n");
usage.append(" [--profile-wait MILLISEC]\n");
usage.append(" [--device-sleep-time MILLISEC]\n");
usage.append(" [--randomize-script]\n");
usage.append(" [--script-log]\n");
usage.append(" [--bugreport]\n");
usage.append(" [--periodic-bugreport]\n");
usage.append(" [--permission-target-system]\n");
usage.append(" COUNT\n");
Logger.err.println(usage.toString());
}
Monkey执行测试的源码分析
这里主要关注模式事件的执行流程
- Monkey启动
- Monkey生成模拟事件
- Monkey向系统发送模拟事件
Monkey启动
Monkey.java中定义了程序入口函数main,该函数中启动了Monkey程序。
// development/cmds/monkey/src/com/android/commands/monkey/Monkey.java
public static void main(String[] args) {
// Set the process name showing in "ps" or "top"
Process.setArgV0("com.android.commands.monkey");
Logger.err.println("args: " + Arrays.toString(args));
int resultCode = (new Monkey()).run(args);
System.exit(resultCode);
}
// development/cmds/monkey/src/com/android/commands/monkey/Monkey.java
/**
* Run the command!
*
* @param args The command-line arguments
* @return Returns a posix-style result code. 0 for no error.
*/
private int run(String[] args) {
// Default values for some command-line options
mVerbose = 0;
// 默认的测试次数
mCount = 1000;
// 生成radom的seed
mSeed = 0;
// 记录事件之间的延迟,就是每个事件执行的间隔
mThrottle = 0;
// prepare for command-line processing
mArgs = args;
// 解析参数
if (!processOptions()) {
return -1;
}
// 确定待测试的Package
if (!loadPackageLists()) {
return -1;
}
// now set up additional data in preparation for launch
if (mMainCategories.size() == 0) {
mMainCategories.add(Intent.CATEGORY_LAUNCHER);
mMainCategories.add(Intent.CATEGORY_MONKEY);
}
if (mSeed == 0) {
mSeed = System.currentTimeMillis() + System.identityHashCode(this);
}
// 获取系统服务接口(AMS、PMS、WMS)
if (!getSystemInterfaces()) {
return -3;
}
// 获取用于启动应用的Activity
if (!getMainApps()) {
return -4;
}
if (mScriptFileNames != null && mScriptFileNames.size() == 1) {
// script mode, ignore other options
} else if (mScriptFileNames != null && mScriptFileNames.size() > 1) {
} else if (mServerPort != -1) {
} else {
// 创建用于产生模拟器事件的Source对象
mEventSource = new MonkeySourceRandom(mRandom, mMainApps,
mThrottle, mRandomizeThrottle, mPermissionTargetSystem);
mEventSource.setVerbose(mVerbose);
// 设置各测试类型的测试比例
// set any of the factors that has been set
for (int i = 0; i < MonkeySourceRandom.FACTORZ_COUNT; i++) {
if (mFactors[i] <= 0.0f) {
((MonkeySourceRandom) mEventSource).setFactors(i, mFactors[i]);
}
}
// 产生activity事件,该事件用来启动应用
// in random mode, we start with a random activity
((MonkeySourceRandom) mEventSource).generateActivity();
}
try {
// 执行模拟测试事件
crashedAtCycle = runMonkeyCycles();
} finally {
// Release the rotation lock if it's still held and restore the
// original orientation.
new MonkeyRotationEvent(Surface.ROTATION_0, false).injectEvent(
mWm, mAm, mVerbose);
}
}
Monkey解析输入参数
processOptions函数解析输入参数(就是monkey命令后跟着的参数信息),根据入参设置Monkey类中相关成员变量。
// development/cmds/monkey/src/com/android/commands/monkey/Monkey.java
private boolean processOptions() {
// quick (throwaway) check for unadorned command
if (mArgs.length < 1) {
showUsage();
return false;
}
try {
String opt;
Set<String> validPackages = new HashSet<>();
while ((opt = nextOption()) != null) {
if (opt.equals("-s")) {
mSeed = nextOptionLong("Seed");
} else if (opt.equals("-p")) {
validPackages.add(nextOptionData());
} else if (opt.equals("-c")) {
// 省略
} else {
Logger.err.println("** Error: Unknown option: " + opt);
showUsage();
return false;
}
}
// 根据输入参数,设置待测试的应用
MonkeyUtils.getPackageFilter().addValidPackages(validPackages);
} catch (RuntimeException ex) {
Logger.err.println("** Error: " + ex.toString());
showUsage();
return false;
}
// If a server port hasn't been specified, we need to specify
// a count
if (mServerPort == -1) {
// 省略
}
return true;
}
Monkey获取系统服务
getSystemInterfaces函数用于获取Android系统服务,包括AMS、PMS、WMS服务。调用AMS服务的setActivityController接口,通过该接口向AMS设置IActivityController.Stub对象,通过该对象监听应用(Activity)的ANR和Crash事件。
/**
* Attach to the required system interfaces.
*
* @return Returns true if all system interfaces were available.
*/
private boolean getSystemInterfaces() {
mAm = ActivityManager.getService();
if (mAm == null) {
Logger.err.println("** Error: Unable to connect to activity manager; is the system "
+ "running?");
return false;
}
mWm = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
if (mWm == null) {
Logger.err.println("** Error: Unable to connect to window manager; is the system "
+ "running?");
return false;
}
mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
if (mPm == null) {
Logger.err.println("** Error: Unable to connect to package manager; is the system "
+ "running?");
return false;
}
try {
mAm.setActivityController(new ActivityController(), true);
mNetworkMonitor.register(mAm);
} catch (RemoteException e) {
Logger.err.println("** Failed talking with activity manager!");
return false;
}
return true;
}
/**
* Monitor operations happening in the system.
*/
private class ActivityController extends IActivityController.Stub {
public boolean activityStarting(Intent intent, String pkg) {
// 省略
}
private boolean isActivityStartingAllowed(Intent intent, String pkg) {
// 省略
}
public boolean activityResuming(String pkg) {
// 省略
}
public boolean appCrashed(String processName, int pid,
String shortMsg, String longMsg,
long timeMillis, String stackTrace) {
// 省略
}
public int appEarlyNotResponding(String processName, int pid, String annotation) {
return 0;
}
public int appNotResponding(String processName, int pid, String processStats) {
// 省略
}
public int systemNotResponding(String message) {
// 省略
}
}
Monkey获取待测试应用的Activity
monkey通过PackageManager的queryIntentActivities接口,查询带有 Intent.CATEGORY_LAUNCHER和Intent.CATEGORY_MONKEY信息的Activity,并判断Activity是否属于待测试应用。将待测试应用的Activity添加到mMainApps变量中。
// development/cmds/monkey/src/com/android/commands/monkey/Monkey.java
/**
* Using the restrictions provided (categories & packages), generate a list
* of activities that we can actually switch to.
*
* @return Returns true if it could successfully build a list of target
* activities
*/
private boolean getMainApps() {
try {
final int N = mMainCategories.size();
for (int i = 0; i < N; i++) {
Intent intent = new Intent(Intent.ACTION_MAIN);
String category = mMainCategories.get(i);
if (category.length() > 0) {
intent.addCategory(category);
}
// 查找带有 Intent.CATEGORY_LAUNCHER、Intent.CATEGORY_MONKEY的Activity
List<ResolveInfo> mainApps = mPm.queryIntentActivities(intent, null, 0,
ActivityManager.getCurrentUser()).getList();
final int NA = mainApps.size();
for (int a = 0; a < NA; a++) {
ResolveInfo r = mainApps.get(a);
String packageName = r.activityInfo.applicationInfo.packageName;
if (MonkeyUtils.getPackageFilter().checkEnteringPackage(packageName)) {
// 如果Activity属于待测试Package,将其添加到mMainApps中。
mMainApps.add(new ComponentName(packageName, r.activityInfo.name));
} else {
}
}
}
} catch (RemoteException e) {
Logger.err.println("** Failed talking with package manager!");
return false;
}
if (mMainApps.size() == 0) {
Logger.out.println("** No activities found to run, monkey aborted.");
return false;
}
return true;
}
Monkey生成模拟测试事件,并执行
// development/cmds/monkey/src/com/android/commands/monkey/Monkey.java
private int run(String[] args) {
// 创建该对象,用于产生测试事件
mEventSource = new MonkeySourceRandom(mRandom, mMainApps,
mThrottle, mRandomizeThrottle, mPermissionTargetSystem);
for (int i = 0; i < MonkeySourceRandom.FACTORZ_COUNT; i++) {
if (mFactors[i] <= 0.0f) {
((MonkeySourceRandom) mEventSource).setFactors(i, mFactors[i]);
}
}
try {
// 执行Monkey测试
crashedAtCycle = runMonkeyCycles();
} finally {
// Release the rotation lock if it's still held and restore the
// original orientation.
new MonkeyRotationEvent(Surface.ROTATION_0, false).injectEvent(
mWm, mAm, mVerbose);
}
}
runMonkeyCycles函数中调用MonkeySourceRandom的getNextEvent函数生成模拟测试事件(MonkeyEvent),调用MonkeyEvent的injectEvent执行模拟测试。
private int runMonkeyCycles() {
int eventCounter = 0;
int cycleCounter = 0;
boolean shouldReportAnrTraces = false;
boolean shouldReportDumpsysMemInfo = false;
boolean shouldAbort = false;
boolean systemCrashed = false;
try {
// TO DO : The count should apply to each of the script file.
while (!systemCrashed && cycleCounter < mCount) {
synchronized (this) {
// 注意:因为先执行过generateActivity,所以第一次调用会,会获得启动Activity的模拟测试事件
MonkeyEvent ev = mEventSource.getNextEvent();
if (ev != null) {
int injectCode = ev.injectEvent(mWm, mAm, mVerbose);
} else {
}
}
} catch (RuntimeException e) {
Logger.error("** Error: A RuntimeException occurred:", e);
}
Logger.out.println("Events injected: " + eventCounter);
return eventCounter;
}
MonkeySourceRandom的getNextEvent,会在事件队列(存储模拟测试事件)为空时,产生测试对象。生成测试事件时,先生成一个随机数,然后根据测试类型所占比例(比例越大,生成该测试类型的概率越大),生成不同测试类型。
// development/cmds/monkey/src/com/android/commands/monkey/MonkeySourceRandom.java
/**
* generate an activity event
*/
public void generateActivity() {
MonkeyActivityEvent e = new MonkeyActivityEvent(mMainApps.get(
mRandom.nextInt(mMainApps.size())));
mQ.addLast(e);
}
/**
* if the queue is empty, we generate events first
* @return the first event in the queue
*/
public MonkeyEvent getNextEvent() {
if (mQ.isEmpty()) {
generateEvents();
}
mEventCount++;
MonkeyEvent e = mQ.getFirst();
mQ.removeFirst();
return e;
}
/**
* generate a random event based on mFactor
*/
private void generateEvents() {
// 生成随机数
float cls = mRandom.nextFloat();
int lastKey = 0;
// 根据Factor,即不同测试类型所占的比例,生成测试事件
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;
} else if (cls < mFactors[FACTOR_ROTATION]) {
generateRotationEvent(mRandom);
return;
} else if (cls < mFactors[FACTOR_PERMISSION]) {
mQ.add(mPermissionUtil.generateRandomPermissionEvent(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
&& lastKey != KeyEvent.KEYCODE_SLEEP
&& lastKey != KeyEvent.KEYCODE_SOFT_SLEEP
&& 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);
}
以Ttouch事件为例子,调用generatePointerEvent函数。通过DMS获取Display对象(用于得知屏幕大小),生成MonkeyTouchEvent对象。
// development/cmds/monkey/src/com/android/commands/monkey/MonkeySourceRandom.java
private void generatePointerEvent(Random random, int gesture) {
Display display = DisplayManagerGlobal.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY);
PointF p1 = randomPoint(random, display);
PointF v1 = randomVector(random);
long downAt = SystemClock.uptimeMillis();
mQ.addLast(new MonkeyTouchEvent(MotionEvent.ACTION_DOWN)
.setDownTime(downAt)
.addPointer(0, p1.x, p1.y)
.setIntermediateNote(false));
// 省略
randomWalk(random, display, p1, v1);
mQ.addLast(new MonkeyTouchEvent(MotionEvent.ACTION_UP)
.setDownTime(downAt)
.addPointer(0, p1.x, p1.y)
.setIntermediateNote(false));
}
调用MonkeyTouchEvent的injectEvent函数,使用InputManager向系统派发TouchEvent。
// development/cmds/monkey/src/com/android/commands/monkey/MonkeyMotionEvent.java
@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(")");
}
Logger.out.println(msg.toString());
}
try {
// 派发TouchEvent
if (!InputManager.getInstance().injectInputEvent(me,
InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT)) {
return MonkeyEvent.INJECT_FAIL;
}
} finally {
me.recycle();
}
return MonkeyEvent.INJECT_SUCCESS;
}