Monkey基本原理及应用学习[2]

Monkey基本原理及应用学习[2]

十一月 15, 2019

上一次(Monkey基本原理及应用学习[1])我们学习了Monkey的一些基本知识以及应用方法,可以针对Android手机进行一定的Monkey测试,但是Monkey本身功能也存在了一定的局限性,因此我们需要对其进行一些改造,当然了,如果你要进行改造,那么首先你必须得学习Monkey的实现原理。

Monkey的基本原理

Monkey代码框架

之前说到过Monkey是通过一个名为“Monkey”的shell脚本来启动的。该脚本在Android文件系统中存放的路径是:/system/bin/monkey。下面是代码:

1
2
3
4
5
6
7
8
9
10
# Script to start "monkey" on the device, which has a very rudimentary
# shell.
#
base=/system
export CLASSPATH=$base/framework/monkey.jar
trap "" HUP
for a in "$@"; do
echo " bash arg:" $a
done
exec app_process $base/bin com.android.commands.monkey.Monkey "$@"

通过观察上述脚本,该批处理通过app_process命令指向的是手机上framework目录下的一个monkey.jar包中的“com.android.commands.monkey.Monkey”类(这个类即为Monkey的入口函数所在类)。monkey的源码下载的话有两个地址,一个是官方地址,另外一个是Github的地址,这里博主下载的是官方的地址,下面是源码列表:

Monkey的代码核心模块主要包括主控,监控,事件源和事件四大部分:

  • 主控模块:主控模块即Monkey类,是入口函数所在类,主要负责参数解析和赋值、初始化运行环境、执行runMonkeyCycles()方法针对不同的事件源开始获取并执行不同的事件。
  • 监控模块:监控部分包括异常监控和网络监控两部分。异常监控通过ActivityWatch类来实现,主要监控Activity的Crash和ANR事件。网络监控通过MonkeyNetworkMonitor类来实现,主要用于统计运行期间移动网络和Wi-Fi网络的链接时长。
  • 事件源模块:事件源代表不同的事件来源。以MonkeyEventSource为基类,衍生出三个Source类,粉别代表网络来源(Monkeyrunner)、随机事件来源(常规Monkey命令)、脚本来源(通过-f参数指定的脚本)三种事件来源。事件来源要做的事情有:从对应来源获取信息,并生成对应的事件,将其插入事件队列中。
  • 事件模块:事件代表了各种用户操作类型。以MonkeyEvent为基类,衍生出各种Event类,每一个Event类代表一种用户操作类型,如常见的点击,输入,滑动事件等。MonkeyEvent抽象类中提供了intinjectEvent()方法,用于执行对应的事件。

Monkey代码逻辑详情

接下来看一下Monkey运行的流程。

首先从入口函数入手,Monkey.java的main方法如下所示:

1
2
3
4
5
6
7
8
public static void main(String[] args) {
// Set the process name showing in "ps" or "top"
Process.setArgV0("com.android.commands.monkey");
// 运行Monkey
int resultCode = (new Monkey()).run(args);
// 退出
System.exit(resultCode);
}

从代码中可以看出main方法通过调用run方法来运行Monkey的。Monkey.java的run方法核心处理代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
private int run(String[] args) {
// Super-early debugger wait
for (String s : args) {
if ("--wait-dbg".equals(s)) {
Debug.waitForDebugger();
}
}

// Default values for some command-line options
mVerbose = 0;
mCount = 1000;
mSeed = 0;
mThrottle = 0;

// prepare for command-line processing
mArgs = args;
mNextArg = 0;

// set a positive value, indicating none of the factors is provided yet
for (int i = 0; i < MonkeySourceRandom.FACTORZ_COUNT; i++) {
mFactors[i] = 1.0f;
}
// 解析Monkey参数并逐一赋值
if (!processOptions()) {
return -1;
}
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 (mVerbose > 0) {
System.out.println(":Monkey: seed=" + mSeed + " count=" + mCount);
if (mValidPackages.size() > 0) {
Iterator<String> it = mValidPackages.iterator();
while (it.hasNext()) {
System.out.println(":AllowPackage: " + it.next());
}
}
if (mInvalidPackages.size() > 0) {
Iterator<String> it = mInvalidPackages.iterator();
while (it.hasNext()) {
System.out.println(":DisallowPackage: " + it.next());
}
}
if (mMainCategories.size() != 0) {
Iterator<String> it = mMainCategories.iterator();
while (it.hasNext()) {
System.out.println(":IncludeCategory: " + it.next());
}
}
}

if (!checkInternalConfiguration()) {
return -2;
}
// 以上获取到了ActivityManager,WindowManager,PackageManager三大系统资源
// 通过ActivityManager调用setActivityController()方法对测试生命周期进行监控
// 通过WindowManager的方法将事件注入到应用中,实现事件操作
// 通过PackageManager获取Intent中应用列表,方便多应用之间切换
if (!getSystemInterfaces()) {
return -3;
}

if (!getMainApps()) {
return -4;
}

mRandom = new SecureRandom();
mRandom.setSeed((mSeed == 0) ? -1 : mSeed);
// 脚本事件源:带-f参数且脚本数等于1
if (mScriptFileNames != null && mScriptFileNames.size() == 1) {
// script mode, ignore other options
mEventSource = new MonkeySourceScript(mRandom, mScriptFileNames.get(0), mThrottle,
mRandomizeThrottle, mProfileWaitTime, mDeviceSleepTime);
mEventSource.setVerbose(mVerbose);

mCountEvents = false;
// 脚本事件源:带-f参数且脚本数大于1
} else if (mScriptFileNames != null && mScriptFileNames.size() > 1) {
if (mSetupFileName != null) {
mEventSource = new MonkeySourceRandomScript(mSetupFileName,
mScriptFileNames, mThrottle, mRandomizeThrottle, mRandom,
mProfileWaitTime, mDeviceSleepTime, mRandomizeScript);
mCount++;
} else {
mEventSource = new MonkeySourceRandomScript(mScriptFileNames,
mThrottle, mRandomizeThrottle, mRandom,
mProfileWaitTime, mDeviceSleepTime, mRandomizeScript);
}
mEventSource.setVerbose(mVerbose);
mCountEvents = false;
// 远程调用事件源:带--port参数
} else if (mServerPort != -1) {
try {
mEventSource = new MonkeySourceNetwork(mServerPort);
} catch (IOException e) {
System.out.println("Error binding to network socket.");
return -5;
}
mCount = Integer.MAX_VALUE;
// 随机事件源
} else {
// random source by default
if (mVerbose >= 2) { // check seeding performance
System.out.println("// Seeded: " + mSeed);
}
mEventSource = new MonkeySourceRandom(mRandom, mMainApps, mThrottle, mRandomizeThrottle);
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]);
}
}

// in random mode, we start with a random activity
((MonkeySourceRandom) mEventSource).generateActivity();
}

// validate source generator
if (!mEventSource.validate()) {
return -5;
}

// If we're profiling, do it immediately before/after the main monkey
// loop
if (mGenerateHprof) {
signalPersistentProcesses();
}
// 启动网络监控程序
mNetworkMonitor.start();
// 开始执行Monkey
int crashedAtCycle = runMonkeyCycles();
mNetworkMonitor.stop();
// 打印ANR,Crash报告
synchronized (this) {
if (mRequestAnrTraces) {
reportAnrTraces();
mRequestAnrTraces = false;
}
if (mRequestAnrBugreport){
System.out.println("Print the anr report");
getBugreport("anr_" + mReportProcessName + "_");
mRequestAnrBugreport = false;
}
if (mRequestAppCrashBugreport){
getBugreport("app_crash" + mReportProcessName + "_");
mRequestAppCrashBugreport = false;
}
if (mRequestDumpsysMemInfo) {
reportDumpsysMemInfo();
mRequestDumpsysMemInfo = false;
}
if (mRequestPeriodicBugreport){
getBugreport("Bugreport_");
mRequestPeriodicBugreport = false;
}
}

if (mGenerateHprof) {
signalPersistentProcesses();
if (mVerbose > 0) {
System.out.println("// Generated profiling reports in /data/misc");
}
}

try {
mAm.setActivityController(null);
mNetworkMonitor.unregister(mAm);
} catch (RemoteException e) {
// just in case this was latent (after mCount cycles), make sure
// we report it
if (crashedAtCycle >= mCount) {
crashedAtCycle = mCount - 1;
}
}

// report dropped event stats
if (mVerbose > 0) {
System.out.print(":Dropped: keys=");
System.out.print(mDroppedKeyEvents);
System.out.print(" pointers=");
System.out.print(mDroppedPointerEvents);
System.out.print(" trackballs=");
System.out.print(mDroppedTrackballEvents);
System.out.print(" flips=");
System.out.println(mDroppedFlipEvents);
}

// report network stats
mNetworkMonitor.dump();

if (crashedAtCycle < mCount - 1) {
System.err.println("** System appears to have crashed at event " + crashedAtCycle
+ " of " + mCount + " using seed " + mSeed);
return crashedAtCycle;
} else {
if (mVerbose > 0) {
System.out.println("// Monkey finished");
}
return 0;
}
}

/**
* Process the command-line options
*
* @return Returns true if options were parsed with no apparent errors.
*/

从上述代码来看,run方法主要做了几个事情:

1.参数解析和参数赋值

2.申请系统资源

3.初始化事件源

4.启动网络监控程序

5.调用runMonkeyCycles方法执行Monkey

我们在来看一下runMonkeyCycles方法又做了什么。runMonkeyCycles中最核心逻辑如下所示:

1
2
3
4
5
6
7
8
9
10
while (!systemCrashed && cycleCounter < mCount){
...
//通过不同时间源读取下一个时间getNextEvent()
MonkeyEvent ev = mEventSource.getNextEvent();
if (ev != null){
// 通过injectEvent()将事件注入系统中
int injectCode = ev.injectEvent(mWm,mAm,mVerbose);
...
}
}

从上述代码中看到,runMonkeyCycles做了两件事,一是调用了事件源的getNextEvent方法读取下一事件,而是调用事件的injectEvent方法来执行该事件。

针对不同的事件源,getNextEvent方法的具体实现是不同的,不过目的都是一样的,就是获取下一个事件。以monkeySourceRandom.java里的getNextEvent方法为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public MonkeyEvent getNextEvent() {
// mQ表示当前事件队列
// 在monkeySourceRandom初始化时会创建一个实践队列用于存放被执行事件
// 加入当前事件队列为空,则创建事件并加入队列中
if (mQ.isEmpty()) {
generateEvents();
}
// 获取当前队列的第一个事件
mEventCount++;
// 取出队列中的第一个事件
MonkeyEvent e = mQ.getFirst();
// 删除第一个事件
mQ.removeFirst();
// 返回第一个事件
return e;
}

monkeySourceRandom.java在初始化的时候会创建一个mQ的事件队列用于存放Monkey事件。当外部调用getNextEvent方法时,会首先去判断mQ队列里有没有事件,如果队列中没有事件,则调用getNextEvent方法,根据事件源自身的规则生成事件流,并将其存放在mQ队列中;如果队列中有事件,则会首先取出第一个事件返回,然后在mQ队列中删除该事件。

而获取事件后需要做的就是执行相应的事件,也就是将事件通过injectEvent方法逐一注入系统。几乎每个Event事件都有对injectEvent方法的具体实现。这里以MonkeyThrottleEvent.java为例来看一下如何进行事件注入的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public int injectEvent(IWindowManager iwm, IActivityManager iam, int verbose) {

if (verbose > 1) {
// 输入事件执行日志 ,延迟的秒速是mThrottle,延迟的单位是milliseconds
System.out.println("Sleeping for " + mThrottle + " milliseconds");
}
try {
// 这里进程执行了一个等待秒数
Thread.sleep(mThrottle);
} catch (InterruptedException e1) {
// 如果上面的功能没有正常执行,就会抛出一个InterruptedException异常,如果有这个异常执行下面程序
// 这里输入事件日志,打印在等待过程中出现了中断
System.out.println("** Monkey interrupted in sleep.");
// 返回给MonkeyEvent说明inject失败
return MonkeyEvent.INJECT_FAIL;
}
// 若等待事件正常执行,返回给MonkeyEvent说明inject成功
return MonkeyEvent.INJECT_SUCCESS;
}

最后来回顾一下整个Monkey事件执行的流程:

(1)初始化事件源

(2)通过getNextEvent()方法循环获取事件

(3)通过injectEvent方法将执行的事件注入系统中。