Android测试之Monkey原理及源码分析(二)
monkey源码分析
monkey源码位置:development/cmds/monkey/cmds/monkey/src/com/android/commands/monkey/
adb shell monkey时是执行了位于/system/bin/下的monkey脚本:
# Script to start "monkey" on the device, which has a very rudimentary# shell.#base=/system #将base设为system路径export CLASSPATH=$base/framework/monkey.jar #将monkey.jar路径设置为拉萨市path环境变量trap "" HUPexec app_process $base/bin com.android.commands.monkey.Monkey $* #通过app_process启动monkey
monkey.java
一.从monkey.java入手,源代码:
https://github.com/aosp-mirror/platform_development/blob/master/cmds/monkey/src/com/android/commands/monkey/Monkey.java
二.monkey命令入口
monkey.java::main()如下:
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); } ……
1.将进程名"com.android.commands.monkey"加入进程显示列表,以便通过ps
或者top
查看
2.log打印
3.通过(new Monkey()).run()运行monkey,处理参数
三.run()
monkey.java::run()第一部分源码——传参处理:
/** * 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) { // 如果参数带有“--wait-debug”进入debug状态 for (String s : args) { if ("--wait-dbg".equals(s)) { Debug.waitForDebugger(); } } // 为部分命令行参数设置默认值 mVerbose = 0; mCount = 1000; mSeed = 0; mThrottle = 0; // 将参数字符串赋值给mArgs mArgs = args; for (String a: args) { Logger.err.println(" arg: \"" + a + "\""); } mNextArg = 0; // 对mFactors[]数组赋初值,这些元素将用于存储“--pct-xxxx”参数 for (int i = 0; i < MonkeySourceRandom.FACTORZ_COUNT; i++) { mFactors[i] = 1.0f; }
1.很容易就发现run(args)是用来处理输入参数的
2.为了了解FACTORZ_COUNT这个参数,我们先去monkeySourceRandom.java看一下相关源码:
public static final int FACTORZ_COUNT = 12; // should be last+1
FACTORZ_COUNT应该为最后一个值加1,那么前面都是些什么值呢?前面定义了12个事件,即为monkey参数“–pct-xxxx”触发的相应事件,且这些参数都是final常量,不能修改其值(0一定是FACTOR_TOUCH),源码如下:
public static final int FACTOR_TOUCH = 0; public static final int FACTOR_MOTION = 1; public static final int FACTOR_PINCHZOOM = 2; public static final int FACTOR_TRACKBALL = 3; public static final int FACTOR_ROTATION = 4; public static final int FACTOR_PERMISSION = 5; public static final int FACTOR_NAV = 6; public static final int FACTOR_MAJORNAV = 7; public static final int FACTOR_SYSOPS = 8; public static final int FACTOR_APPSWITCH = 9; public static final int FACTOR_FLIP = 10; public static final int FACTOR_ANYTHING = 11; …… public MonkeySourceRandom(Random random, List MainApps, long throttle, boolean randomizeThrottle, boolean permissionTargetSystem) { // 各事件初始百分占比 mFactors[FACTOR_TOUCH] = 15.0f; mFactors[FACTOR_MOTION] = 10.0f; mFactors[FACTOR_TRACKBALL] = 15.0f; mFactors[FACTOR_ROTATION] = 0.0f; mFactors[FACTOR_NAV] = 25.0f; mFactors[FACTOR_MAJORNAV] = 15.0f; mFactors[FACTOR_SYSOPS] = 2.0f; mFactors[FACTOR_APPSWITCH] = 2.0f; mFactors[FACTOR_FLIP] = 1.0f; // disbale permission by default mFactors[FACTOR_PERMISSION] = 0.0f; mFactors[FACTOR_ANYTHING] = 13.0f; mFactors[FACTOR_PINCHZOOM] = 2.0f;
上一篇有提到说各个事件有初始占比,在构造函数中的源码如上所示,同时monkey可以通过设置参数改变事件的占用百分比,但是总百分比不能超过100%。如果保持默认比例,运行结果应该如下图:
3.monkeySourceRandom.java实现了monkeyEventSource中提供的接口public class MonkeySourceRandom implements MonkeyEventSource
,monkeyEventSource接口源码如下:
public interface MonkeyEventSource { public MonkeyEvent getNextEvent(); //返回下一个monkey event public void setVerbose(int verbose); //设置log等级 public boolean validate(); //验证是否有效}
monkey.java::run()第二部分源码——参数分析:
if (!processOptions()) { return -1; } if (!loadPackageLists()) { return -1; }
4.processOptions()是一个重要的方法,用于解析命令行传入的参数,然后将传入的参数结合默认的参数进行校验和匹配:
private boolean processOptions() { //如果参数长度小于1,则显示help usage if (mArgs.length < 1) { showUsage(); return false; } try { String opt; Set 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")) { mMainCategories.add(nextOptionData()); } else if (opt.equals("-v")) { mVerbose += 1; } else if (opt.equals("--ignore-crashes")) { mIgnoreCrashes = true; } else if (opt.equals("--ignore-timeouts")) { mIgnoreTimeouts = true; } else if (opt.equals("--ignore-security-exceptions")) { mIgnoreSecurityExceptions = true; } else if (opt.equals("--monitor-native-crashes")) { mMonitorNativeCrashes = true; } else if (opt.equals("--ignore-native-crashes")) { mIgnoreNativeCrashes = true; } else if (opt.equals("--kill-process-after-error")) { mKillProcessAfterError = true; } else if (opt.equals("--hprof")) { mGenerateHprof = true; } else if (opt.equals("--match-description")) { mMatchDescription = nextOptionData(); } else if (opt.equals("--pct-touch")) { int i = MonkeySourceRandom.FACTOR_TOUCH; mFactors[i] = -nextOptionLong("touch events percentage"); } else if (opt.equals("--pct-motion")) { int i = MonkeySourceRandom.FACTOR_MOTION; mFactors[i] = -nextOptionLong("motion events percentage"); } else if (opt.equals("--pct-trackball")) { int i = MonkeySourceRandom.FACTOR_TRACKBALL; mFactors[i] = -nextOptionLong("trackball events percentage"); } else if (opt.equals("--pct-rotation")) { int i = MonkeySourceRandom.FACTOR_ROTATION; mFactors[i] = -nextOptionLong("screen rotation events percentage"); } else if (opt.equals("--pct-syskeys")) { int i = MonkeySourceRandom.FACTOR_SYSOPS; mFactors[i] = -nextOptionLong("system (key) operations percentage"); } else if (opt.equals("--pct-nav")) { int i = MonkeySourceRandom.FACTOR_NAV; mFactors[i] = -nextOptionLong("nav events percentage"); } else if (opt.equals("--pct-majornav")) { int i = MonkeySourceRandom.FACTOR_MAJORNAV; mFactors[i] = -nextOptionLong("major nav events percentage"); } else if (opt.equals("--pct-appswitch")) { int i = MonkeySourceRandom.FACTOR_APPSWITCH; mFactors[i] = -nextOptionLong("app switch events percentage"); } else if (opt.equals("--pct-flip")) { int i = MonkeySourceRandom.FACTOR_FLIP; mFactors[i] = -nextOptionLong("keyboard flip percentage"); } else if (opt.equals("--pct-anyevent")) { int i = MonkeySourceRandom.FACTOR_ANYTHING; mFactors[i] = -nextOptionLong("any events percentage"); } else if (opt.equals("--pct-pinchzoom")) { int i = MonkeySourceRandom.FACTOR_PINCHZOOM; mFactors[i] = -nextOptionLong("pinch zoom events percentage"); } else if (opt.equals("--pct-permission")) { int i = MonkeySourceRandom.FACTOR_PERMISSION; mFactors[i] = -nextOptionLong("runtime permission toggle events percentage"); } else if (opt.equals("--pkg-blacklist-file")) { mPkgBlacklistFile = nextOptionData(); } else if (opt.equals("--pkg-whitelist-file")) { mPkgWhitelistFile = nextOptionData(); } else if (opt.equals("--throttle")) { mThrottle = nextOptionLong("delay (in milliseconds) to wait between events"); } else if (opt.equals("--randomize-throttle")) { mRandomizeThrottle = true; } else if (opt.equals("--wait-dbg")) { // do nothing - it's caught at the very start of run() } else if (opt.equals("--dbg-no-events")) { mSendNoEvents = true; } else if (opt.equals("--port")) { mServerPort = (int) nextOptionLong("Server port to listen on for commands"); } else if (opt.equals("--setup")) { mSetupFileName = nextOptionData(); } else if (opt.equals("-f")) { mScriptFileNames.add(nextOptionData()); } else if (opt.equals("--profile-wait")) { mProfileWaitTime = nextOptionLong("Profile delay" + " (in milliseconds) to wait between user action"); } else if (opt.equals("--device-sleep-time")) { mDeviceSleepTime = nextOptionLong("Device sleep time" + "(in milliseconds)"); } else if (opt.equals("--randomize-script")) { mRandomizeScript = true; } else if (opt.equals("--script-log")) { mScriptLog = true; } else if (opt.equals("--bugreport")) { mRequestBugreport = true; } else if (opt.equals("--periodic-bugreport")){ mGetPeriodicBugreport = true; mBugreportFrequency = nextOptionLong("Number of iterations"); } else if (opt.equals("--permission-target-system")){ mPermissionTargetSystem = true; } else if (opt.equals("-h")) { showUsage(); return false; } 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; }
1)nextOption()
private String nextOption() { if (mNextArg >= mArgs.length) { //如果参数序号大于等于存储参数的数组的总长度(数组是从0开始的,0~length-1) return null; } String arg = mArgs[mNextArg]; //否则就获取序号为mNextArg的参数 if (!arg.startsWith("-")) { //如果参数不是以“-”开头,返回null return null; } mNextArg++; if (arg.equals("--")) { //如果参数只有“--”,返回null return null; } if (arg.length() > 1 && arg.charAt(1) != '-') { //如果参数长度大于1,且只有一个“-”:-z 或者 -z args if (arg.length() > 2) { //如果参数长度大于2,从第3位开始是参数后面跟的args并赋值给mCurArgData,返回“-参数” mCurArgData = arg.substring(2); return arg.substring(0, 2); } else { //否则该参数后面没有args,直接返回 mCurArgData = null; return arg; } } mCurArgData = null; Logger.err.println("arg=\"" + arg + "\" mCurArgData=\"" + mCurArgData + "\" mNextArg=" + mNextArg + " argwas=\"" + mArgs[mNextArg-1] + "\"" + " nextarg=\"" + mArgs[mNextArg] + "\""); return arg; }
如果参数有“–”或者不含数值args直接返回该参数,如果只有“-”且含数值args只返回该参数的前2位,然后将args交给nextOptionLong()处理。
2)nextOptionLong()
private long nextOptionLong(final String opt) { long result; try { result = Long.parseLong(nextOptionData()); //还是调用nextOptionData() } catch (NumberFormatException e) { Logger.err.println("** Error: " + opt + " is not a number"); throw e; } return result; }
args的处理又再交给nextOptionData()去完成。
会调用nextOptionLong()的参数:
- “-s”:随机数seed,赋值给mSeed
- “–throttle”:输入延迟,赋值给mThrottle
- “–port”:tcp端口,赋值给mServerPort
- “–device-sleep-time”:设备空闲时间,赋值给mDevicesSleepTime
- “–periodic-bugreport”:bugreport频率,赋值给mBugreportFrequency
- “–pct-touch”:对应monkeySourceRandom.FACTOR_TOUCH,赋值给mFactors[i]
- “–pct-motion”:对应monkeySourceRandom.FACTOR_MOTION,赋值给mFactors[i]
- “–pct-trackball”:对应monkeySourceRandom.FACTOR_TRACKBALL,赋值给mFactors[i]
- “–pct-rotation”:对应monkeySourceRandom.FACTOR_ROTATION,赋值给mFactors[i]
- “–pct-syskeys”:对应monkeySourceRandom.FACTOR_SYSOPS,赋值给mFactors[i]
- “–pct-nav”:对应monkeySourceRandom.FACTOR_NAV,赋值给mFactors[i]
- “–pct-majornav”:对应monkeySourceRandom.FACTOR_MAJORNAV,赋值给mFactors[i]
- “–pct-appswitch”:对应monkeySourceRandom.FACTOR_APPSWITCH,赋值给mFactors[i]
- “–pct-flip”:对应monkeySourceRandom.FACTOR_FLIP,赋值给mFactors[i]
- “–pct-anyevent”:对应monkeySourceRandom.FACTOR_ANYTHING,赋值给mFactors[i]
- “–pct-pinchzoom”:对应monkeySourceRandom.FACTOR_PINCHZOOM,赋值给mFactors[i]
- “–pct-permission”:对应monkeySourceRandom.FACTOR_PERMISSION,赋值给mFactors[i]
3)nextOptionData()
private String nextOptionData() { //返回参数,如果参数有带args也返回 if (mCurArgData != null) { return mCurArgData; } if (mNextArg >= mArgs.length) { return null; } String data = mArgs[mNextArg]; Logger.err.println("data=\"" + data + "\""); mNextArg++; return data; }
会调用nextOptionData()的参数:
- “-p”:添加允许的包名,添加到mValidPackages
- “-c”:添加允许的类名,添加到mmainCategories
- “–pkg-blacklist-file”:列入黑名单的包,添加到mPkgBlacklistfile
- “–pkg-whitelist-file”:列入白名单的包,添加到mPkgWhitelistfile
- “–setup”:setup脚本名,赋值给mSetupfileName
- “-f”:monkey脚本名,赋值给mScriptfileNames
4)会返回true的参数:
- “–ignore-crashes”:运行时忽略所有崩溃,赋值给mIgnoreCrashes
- “–ignore-timeout”:运行时忽略相应超时,赋值给mIgnoreTimeouts
- “–ignore-security-exceptions”:运行时忽略安全异常,赋值给mIgnoreSecurityExceptions
- “–monitor-native-crashes”:运行时若出现native异常则监控,且将错误报告存储在/data/tombstones/下,赋值给mMonitorNativeCrashes
- “–ignore-native-crashes”:运行时忽略所有系统级native异常,赋值给mIgnoreNativeCrashes
- “–kill-process-after-error”:运行时出现error则杀掉进程,赋值给mKillProcessAfterError
- “–hprof”:生成hprof报告,赋值给mGenerateHprof
- “–randomize-throttle”:插入随机输入延迟,赋值给mRandomizeThrottle
- “–dbg-no-events”:不发送任何时间只作为长延时观察用户操作,赋值给mSendNoEvents
- “–randomize-script”:随机运行monkey脚本,赋值给mRandomizeScript
- “–script-log”:记录monkey脚本日志
- “–bugreport”:运行崩溃时捕获bugreport,赋值给mRequestBugreport
- “–periodic-bugreport”:根据bugreport频率来捕获bugreport,赋值给mGetPeriodicBugreport
- “–permission-target-system”:赋值给mPermissionTargetSystem
5)比较特殊的几个参数:
- “-v”:增加log日志级别,每写一个-v,mVerbose+=1(初始值为0)
- “-h”/错误参数:显示help usage
- “–wait-dbg”:什么都不做,在run()方法一开始就会判断是否有这个参数是否要进入debug模式
5.loadPackageLists()
//导入package列表,白名单和黑名单 private boolean loadPackageLists() { //检查白名单是不是空的 if (((mPkgWhitelistFile != null) || (MonkeyUtils.getPackageFilter().hasValidPackages())) && (mPkgBlacklistFile != null)) { Logger.err.println("** Error: you can not specify a package blacklist " + "together with a whitelist or individual packages (via -p)."); return false; } Set validPackages = new HashSet<>(); //检查黑名单是不是空的 if ((mPkgWhitelistFile != null) && (!loadPackageListFromFile(mPkgWhitelistFile, validPackages))) { return false; } MonkeyUtils.getPackageFilter().addValidPackages(validPackages); Set invalidPackages = new HashSet<>(); if ((mPkgBlacklistFile != null) && (!loadPackageListFromFile(mPkgBlacklistFile, invalidPackages))) { return false; } MonkeyUtils.getPackageFilter().addInvalidPackages(invalidPackages); return true; }
这部分我没有太深究,应该就是加载名单为true之后才能进行之后的操作,否则会有相应的错误提示之类的。。。
更多相关文章
- mybatisplus的坑 insert标签insert into select无参数问题的解决
- 箭头函数的基础使用
- Python技巧匿名函数、回调函数和高阶函数
- python list.sort()根据多个关键字排序的方法实现
- Android(安卓)多版本多渠道打包
- Ubuntu12.04 安装ADB调试环境
- Android中关于ComponentName的使用
- attrs.xml的使用
- Android(安卓)学习笔记 Contacts (一)ContentResolver query 参数