在【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\]:
java
/**
* 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 的处理逻辑。
java
/**
* 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 的处理逻辑:
java
/**
* 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 也类似,只是执行的命令不一样而已,这里不再赘述。
java
/**
* 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信息,内存信息等),输出到命令行。