【Android Monkey源码解析五】- 异常处理

在【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信息,内存信息等),输出到命令行。

相关推荐
明道源码2 小时前
Android Studio AVD 模拟器的使用与配置
android·android studio
学海无涯书山有路2 小时前
Android FragmentContainerView 新手详解(Java 版)
android·java·开发语言
马克学长3 小时前
SSM医院门诊管理系统u4pw5(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面
数据库·ssm 框架·javaweb 开发·门诊管理
TDengine (老段)3 小时前
使用安装包快速体验 TDengine TSDB
大数据·数据库·物联网·时序数据库·tdengine·涛思数据
上海控安3 小时前
Android生态中的SDK安全问题解析
android·安全
aidou13143 小时前
Android中自定义日期选择器样式DatePickerDialog
android·dialog·日期选择器·时间选择器·datepicker·自定义样式·timepicker
闻哥3 小时前
Redis 避坑指南:从命令到主从的全链路踩坑实录
java·数据库·redis·缓存·面试·springboot
建群新人小猿4 小时前
陀螺匠企业助手—个人简历
android·大数据·开发语言·前端·数据库
小毅&Nora5 小时前
【后端】【Redis】② Redis事务管理全解:从“购物车结算“到“银行转账“,一文彻底掌握事务机制
数据库·redis·事务