【Android Monkey源码解析五】- 异常处理
2025-10-08 / 龙之叶   

在【Android Monkey源码解析四】- 异常捕获/页面控制 中我们知道了如何进行异常的捕获,那异常捕获后是如何进行处理的呢?

在Monkey启动的时候,有提到runMonkeyCycles这个方法,它的主要作用是循环执行随机的事件,直到有异常(如果命令行没有设置忽略异常)和事件全部完成后结束,下面看下它的实现,代码:(https://android.googlesource.com/platform/development/+/refs/heads/android12-release/cmds/monkey/src/com/android/commands/monkey/Monkey.java#1106)[https://android.googlesource.com/platform/development/+/refs/heads/android12-release/cmds/monkey/src/com/android/commands/monkey/Monkey.java#1106]:

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
/**
* Run mCount cycles and see if we hit any crashers.
* <p>
* TODO: Meta state on keys
*
* @return Returns the last cycle which executed. If the value == mCount, no
* errors detected.
*/
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.
// systemCrashed只有在injectEvent时发生了RemoteException 或者 SecurityException生效
// 如果cycleCounter计数器小于命令行指定的最大事件数,则循环执行
while (!systemCrashed && cycleCounter < mCount) {
synchronized (this) {
// 根据命令行的参数设置,在执行每一个事件前初始化操作
if (mRequestProcRank) {
reportProcRank();
mRequestProcRank = false;
}
if (mRequestAnrTraces) {
mRequestAnrTraces = false;
shouldReportAnrTraces = true;
}
if (mRequestAnrBugreport){
getBugreport("anr_" + mReportProcessName + "_");
mRequestAnrBugreport = false;
}
if (mRequestWatchdogBugreport) {
Logger.out.println("Print the watchdog report");
getBugreport("anr_watchdog_");
mRequestWatchdogBugreport = false;
}
if (mRequestAppCrashBugreport){
getBugreport("app_crash" + mReportProcessName + "_");
mRequestAppCrashBugreport = false;
}
if (mRequestPeriodicBugreport){
getBugreport("Bugreport_");
mRequestPeriodicBugreport = false;
}
if (mRequestDumpsysMemInfo) {
mRequestDumpsysMemInfo = false;
shouldReportDumpsysMemInfo = true;
}
if (mMonitorNativeCrashes) {
// first time through, when eventCounter == 0, just set up
// the watcher (ignore the error)
// 通过每次查看/data/tombstones下是否有新文件生成来确定是否有Native异常出现
if (checkNativeCrashes() && (eventCounter > 0)) {
Logger.out.println("** New native crash detected.");
if (mRequestBugreport) {
getBugreport("native_crash_");
}
mAbort = mAbort || !mIgnoreNativeCrashes || mKillProcessAfterError;
}
}
if (mAbort) {
shouldAbort = true;
}
if (mWatchdogWaiting) {
mWatchdogWaiting = false;
notifyAll();
}
}
// Report ANR, dumpsys after releasing lock on this.
// This ensures the availability of the lock to Activity controller's appNotResponding
// 处理ANR异常
if (shouldReportAnrTraces) {
shouldReportAnrTraces = false;
reportAnrTraces();
}
// dump内存信息
if (shouldReportDumpsysMemInfo) {
shouldReportDumpsysMemInfo = false;
reportDumpsysMemInfo();
}
if (shouldAbort) {
shouldAbort = false;
Logger.out.println("** Monkey aborted due to error.");
Logger.out.println("Events injected: " + eventCounter);
return eventCounter;
}
// In this debugging mode, we never send any events. This is
// primarily here so you can manually test the package or category
// limits, while manually exercising the system.
if (mSendNoEvents) {
eventCounter++;
cycleCounter++;
continue;
}
// 每隔一定的事件数输出当前的时间和已操作的事件数
if ((mVerbose > 0) && (eventCounter % 100) == 0 && eventCounter != 0) {
String calendarTime = MonkeyUtils.toCalendarTime(System.currentTimeMillis());
long systemUpTime = SystemClock.elapsedRealtime();
Logger.out.println(" //[calendar_time:" + calendarTime + " system_uptime:"
+ systemUpTime + "]");
Logger.out.println(" // Sending event #" + eventCounter);
}
MonkeyEvent ev = mEventSource.getNextEvent();
if (ev != null) {
// 执行具体的随机事件,通过返回值判断成功异常信息,后续对事件的触发作重点介绍
int injectCode = ev.injectEvent(mWm, mAm, mVerbose);
if (injectCode == MonkeyEvent.INJECT_FAIL) {
Logger.out.println(" // Injection Failed");
if (ev instanceof MonkeyKeyEvent) {
mDroppedKeyEvents++;
} else if (ev instanceof MonkeyMotionEvent) {
mDroppedPointerEvents++;
} else if (ev instanceof MonkeyFlipEvent) {
mDroppedFlipEvents++;
} else if (ev instanceof MonkeyRotationEvent) {
mDroppedRotationEvents++;
}
} else if (injectCode == MonkeyEvent.INJECT_ERROR_REMOTE_EXCEPTION) {
systemCrashed = true;
Logger.err.println("** Error: RemoteException while injecting event.");
} else if (injectCode == MonkeyEvent.INJECT_ERROR_SECURITY_EXCEPTION) {
systemCrashed = !mIgnoreSecurityExceptions;
if (systemCrashed) {
Logger.err.println("** Error: SecurityException while injecting event.");
}
}
// Don't count throttling as an event. 不记录休眠等待事件到事件计数器中
if (!(ev instanceof MonkeyThrottleEvent)) {
eventCounter++;
if (mCountEvents) {
cycleCounter++;
}
}
} else {
// Monkey脚本相关处理逻辑,后续分析Monkey脚本时解析流程
if (!mCountEvents) {
cycleCounter++;
writeScriptLog(cycleCounter);
//Capture the bugreport after n iteration
if (mGetPeriodicBugreport) {
if ((cycleCounter % mBugreportFrequency) == 0) {
mRequestPeriodicBugreport = true;
}
}
} else {
// Event Source has signaled that we have no more events to process
break;
}
}
}
} catch (RuntimeException e) {
Logger.error("** Error: A RuntimeException occurred:", e);
}
Logger.out.println("Events injected: " + eventCounter);
return eventCounter;
}

我们再看下 reportAnrTraces 和 reportDumpsysMemInfo 的处理逻辑。

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
/**
* Dump the most recent ANR trace. Wait about 5 seconds first, to let the
* asynchronous report writing complete.
* 当执行的事件出现了ANR时,在执行后将会进入此处理逻辑,在开始处理之前,将会等待5秒的时长,等异步的报告处理完毕
*/
private void reportAnrTraces() {
try {
Thread.sleep(5 * 1000);
} catch (InterruptedException e) {
}
// The /data/anr directory might have multiple files, dump the most
// recent of those files.
// 循环迭代/data/anr目录下的trace文件,比较文件的最后修改时间来找到最新的一个,然后在命令行进行打印输出
File[] recentTraces = new File("/data/anr/").listFiles();
if (recentTraces != null) {
File mostRecent = null;
long mostRecentMtime = 0;
for (File trace : recentTraces) {
final long mtime = trace.lastModified();
if (mtime > mostRecentMtime) {
mostRecentMtime = mtime;
mostRecent = trace;
}
}
if (mostRecent != null) {
commandLineReport("anr traces", "cat " + mostRecent.getAbsolutePath());
}
}
}

下面看下 commandLineReport 的处理逻辑:

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
/**
* Print report from a single command line.
* <p>
* TODO: Use ProcessBuilder & redirectErrorStream(true) to capture both
* streams (might be important for some command lines)
* 通过执行命令,然后通过输出流进行显示
*
* @param reportName Simple tag that will print before the report and in
* various annotations.
* @param command Command line to execute.
*/
private void commandLineReport(String reportName, String command) {
Logger.err.println(reportName + ":");
Runtime rt = Runtime.getRuntime();
Writer logOutput = null;
try {
// Process must be fully qualified here because android.os.Process
// is used elsewhere
java.lang.Process p = Runtime.getRuntime().exec(command);
if (mRequestBugreport) {
logOutput =
new BufferedWriter(new FileWriter(new File(Environment
.getLegacyExternalStorageDirectory(), reportName), true));
}
// pipe everything from process stdout -> System.err
InputStream inStream = p.getInputStream();
InputStreamReader inReader = new InputStreamReader(inStream);
BufferedReader inBuffer = new BufferedReader(inReader);
String s;
while ((s = inBuffer.readLine()) != null) {
// 如果命令行参数有设置--bugreport,则会把文件输出到手机的存储目录上;
// 如果没设置的话,则会通过日志打印出来
if (mRequestBugreport) {
try {
// When no space left on the device the write will
// occurs an I/O exception, so we needed to catch it
// and continue to read the data of the sync pipe to
// aviod the bugreport hang forever. 发现错别字 aviod -> avoid
logOutput.write(s);
logOutput.write("\n");
} catch (IOException e) {
while(inBuffer.readLine() != null) {}
Logger.err.println(e.toString());
break;
}
} else {
Logger.err.println(s);
}
}
int status = p.waitFor();
Logger.err.println("// " + reportName + " status was " + status);
if (logOutput != null) {
logOutput.close();
}
} catch (Exception e) {
Logger.err.println("// Exception from " + reportName + ":");
Logger.err.println(e.toString());
}
}

reportDumpsysMemInfo 也类似,只是执行的命令不一样而已,这里不再赘述。

1
2
3
4
5
6
7
8
9
10
/**
* Run "dumpsys meminfo"
* <p>
* NOTE: You cannot perform a dumpsys call from the ActivityController
* callback, as it will deadlock. This should only be called from the main
* loop of the monkey.
*/
private void reportDumpsysMemInfo() {
commandLineReport("meminfo", "dumpsys meminfo");
}

从上可以看出,异常的处理逻辑其实很简单,就是在每次执行事件前先重置异常的一系列标志位,然后再执行一个随机的事件(如点击,滑动,启动应用等),在事件执行完之后看各标志位的状态,判断是否异常以及需要做异常的处理(如dump anr trace信息,内存信息等),输出到命令行。

本文链接:
http://longzhiye.top/2025/10/08/2025-10-08/