前言

    由第一篇文章可知,monkey程序先由shell脚本程序执行,shell脚本程序接着使用exec命令替换成一个新的c++程序app_process,再接着由app_process程序创建ART虚拟机实例,然后app_process程序会根据命令行参数中传递的参数,再去加载Java程序,位于/system/framework/monkey.jar这个jar文件中的Monkey类(字节码dex格式)会加载到ART虚拟机内存中,Monkey类是Java程序的入口类,它的静态方法main()会被调用,今天一起学习monkey程序中入口类Monkey.java的设计思想

Monkey类介绍

    Monkey类是monkey程序的入口类,它位于com.android.commands.monkey包中,文件名是Monkey.java,一个符合Java规范的文本文件,也称java源代码文件

    当Monkey类加载到虚拟机内存后,它的静态方法main()会被调用,Monkey类是一个继承Object的java类

public class Monkey {
      ……省略很多代码……
}

    按照Java中类加载的过程,我将先分析静态成员,接着是实例成员,并且从头到尾理清楚Monkey程序的执行过程,let us go…………

Monkey静态成员分析(5个)

    private final static int DEBUG_ALLOW_ANY_STARTS = 0;

    private final static int DEBUG_ALLOW_ANY_RESTARTS = 0;

    Monkey类加载时,静态成员与静态代码块会按照Monkey.java文件代码中的书写顺序依次执行,由于Monkey类直接继承于Object类,所以父类的代码执行情况我就不介绍了。

    两个常量,DEBUG_ALLOW_ANY_STARTS和DEBUG_ALLOW_ANY_RESTARTS是Monkey的开发者在调试阶段使用的标志值,作者已经明确说明,这两个值在Monkey程序上线的时候,必须要再设置为0!

DEBUG_ALLOW_ANY_STARTS:用于调试是否同意启动某个Activity,用于和AMS系统服务之间的调试,它是在ActivityController的isActivityStartingAllowed()方法中使用的

DEBUG_ALLOW_ANY_RESTARTS:也是用于和AMS系统服务调试的,表示是否统一重启Activity,它的使用位置是在ActivityController的activityResuming()方法中

    上面两个常量在这里暂时不必纠结,后面有文章会专门讲解这两个常量的使用,接下来我们继续……

    private static final File TOMBSTONES_PATH = new File("/data/tombstones"); //native崩溃日志目录

    private static final String TOMBSTONE_PREFIX = "tombstone_";

    private static int NUM_READ_TOMBSTONE_RETRIES = 5;

    上面3个是用于收集native层程序崩溃的常量,它们依次是

TOMBSTONRES_PATH:一个File对象,表示存储发生native进程崩溃后,由系统保存日志文件的目录为/data/tombstones

TOMBSTONE_PREFIX:表示native崩溃发生后,由系统生成的文件名前缀,值是tombstones_

NUM_READ_TOMBSTONE_RETRIES:表示读取tombstone文件的重试次数,值为5,说明最多重试5次

    public static Intent currentIntent; //Monkey类持有的currentIntent,表示当前发出的Intent

    public static String currentPackage; //Monkey类持有的currentPackage,表示当前操作包名

    由Monkey类持有的currentIntent与currentPackage,它们是

currentIntent:表示启动当前Activity的Intent对象

currentPackage:表示当前启动App的包名

    总结:至此Monkey类加载到虚拟机内存的所有静态成员(静态变量、常量)全部分析结束,可见Monkey的开发者非常克制的使用类变量,他/她果然是面向对象的大佬!!

    接下来继续学习作为Monkey类静态成员的静态方法,首先从作为程序入口的静态方法main()开始分析……

静态方法main()分析

    public static void main(String[] args) {
        // Set the process name showing in "ps" or "top"
        Process.setArgV0("com.android.commands.monkey"); //修改monkey程序的进程名称

        Logger.err.println("args: " + Arrays.toString(args)); //向标准错误流,输出命令行参数信息
        int resultCode = (new Monkey()).run(args); //创建Monkey对象,调用run()方法,将数组对象(命令行参数)传进去
        System.exit(resultCode); //退出虚拟机进程,返回退出状态码
    }

    Monkey类加载到ART虚拟机后,静态成员首先(静态变量、静态代码块)执行,根据java标准,接下来作为程序入口的静态方法main()会被执行,静态方法main()可传入一个字符串数组对象,args数组对象中存储的每一个元素,都是传给Monkey类的命令行参数(Java程序的命令行参数),这些命令行参数是app_process程序中直接透传过来的,哈哈。具体的案例可以参考我的第一篇文章,接下来分析静态方法main()的方法体

1、修改进程名称

monkey程序最初只是一个shell脚本进程,虽然虽然中间替换为app_process这个可执行程序,所以进程名仍是app_process?Java程序启动后,最先要做的是修改进程名为com.android.commands.monkey。这个进程名,当我们使用ps或者top命令查看monkey的进程时,会看到进程名为com.android.commands.monkey,具体修改的工作由Process类的静态方法setArg0()实现【底层当然是Java虚拟机了】【看来以后我也会在运行时修改进程名了,哈哈】

2、标准错误中写入传入的命令行参数

在标准错误流中输出日志:args:命令行参数,把所有命令行参数输出到屏幕上,此处使用Arrays的toString()方法,将数组对象转换为字符串对象,然后写入到标准错误(默认为屏幕)【我们启动monkey程序的时候,看到的就是这些命令行参数】

3、创建Monkey对象

new Monkey(),使用默认构造方法创建Monkey对象

4、调用Monkey对象的run()方法

Monkey的实例方法run()开始执行,并同时将表示所有命令行参数的数组对象传入到run()方法中

5、将run()方法返回的退出状态码赋值给局部变量resultCode保存

6、退出虚拟机进程,并返回退出状态码

使用System的静态方法exit(),退出虚拟机进程,同时将退出状态码传入,此时整个Monkey程序结束,可见Monkey程序一直运行在主进程中(主线程),随着运行的结束,整个monkey程序也结束了,【Java版本的命令行程序写的是这么优雅】

    接下来我们分析run()方法具体做了些什么,run()方法里有monkey程序的很多业务逻辑,非常值得我们学习,不过再此之前,我先分析Monkey对象的创建,没有Monkey对象,实例方法run()也不会执行!!

Monkey对象的创建分析

    Monkey对象使用默认构造方法创建,此时Monkey类中定义的实例变量、普通代码块、普通内部类会先执行,按照Monkey.java文件中的文本书写顺序依次执行,一起学习这部分代码,看看Monkey对象持有什么,持有的每个实例变量又各自有什么用途?

    private IActivityManager mAm;
    private IWindowManager mWm;
    private IPackageManager mPm;
    private String[] mArgs;
    private int mNextArg;
    private String mCurArgData;
    private int mVerbose;
    private boolean mIgnoreCrashes;
    private boolean mIgnoreTimeouts;
    private boolean mIgnoreSecurityExceptions;
    private boolean mMonitorNativeCrashes;
    private boolean mIgnoreNativeCrashes;
    private boolean mSendNoEvents;
    private boolean mAbort;
    private boolean mCountEvents = true;
    private boolean mRequestAnrTraces = false;
    private boolean mRequestDumpsysMemInfo = false;
    private boolean mRequestAnrBugreport = false;
    private boolean mRequestWatchdogBugreport = false;
    private boolean mWatchdogWaiting = false;
    private boolean mRequestAppCrashBugreport = false;
    private boolean mGetPeriodicBugreport = false;
    private boolean mRequestPeriodicBugreport = false;
    private long mBugreportFrequency = 10;
    private String mReportProcessName;
    private boolean mRequestProcRank = false;
    private boolean mKillProcessAfterError;
    private boolean mGenerateHprof;
    private String mMatchDescription;
    private String mPkgBlacklistFile;
    private String mPkgWhitelistFile;
    private ArrayList<String> mMainCategories = new ArrayList<String>();
    private ArrayList<ComponentName> mMainApps = new ArrayList<ComponentName>();
    long mThrottle = 0;
    boolean mRandomizeThrottle = false;
    int mCount = 1000;
    long mSeed = 0;
    Random mRandom = null;
    long mDroppedKeyEvents = 0;
    long mDroppedPointerEvents = 0;
    long mDroppedTrackballEvents = 0;
    long mDroppedFlipEvents = 0;
    long mDroppedRotationEvents = 0;
    long mProfileWaitTime = 5000;
    long mDeviceSleepTime = 30000;
    boolean mRandomizeScript = false;
    boolean mScriptLog = false;
    private boolean mRequestBugreport = false;
    private String mSetupFileName = null;
    private ArrayList<String> mScriptFileNames = new ArrayList<String>();
    private int mServerPort = -1;
    private HashSet<Long> mTombstones = null;
    float[] mFactors = new float[MonkeySourceRandom.FACTORZ_COUNT];
    MonkeyEventSource mEventSource;
    private MonkeyNetworkMonitor mNetworkMonitor = new MonkeyNetworkMonitor();
    private boolean mPermissionTargetSystem = false;

    Monkey对象持有的实例变量非常多,这说明monkey程序的业务逻辑复杂,先说几个Monkey对象持有的主要实例变量

mAm:Monkey对象持有的IActivityManager对象,用于使用AMS系统服务提供的功能(跨进程调用)

mWm:Monkey对象持有的IWindowManager对象,用于使用WMS系统服务提供的功能(跨进程调用)

mPm:Monkey对象持有的IPackageManager对象,用于使用PMS系统服务提供的功能(跨进程调用)

    其他的实例变量我将在各篇文章中,涉及到时再谈及,这里就不再细说了,由于Monkey类没有普通代码块,所以与对象相关的初始化代码已经执行完毕,接下来是run()方法的分析……

run()方法分析

    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;
        for (String a: args) {
            Logger.err.println(" arg: \"" + a + "\"");
        }
        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;
        }

        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 (mSeed == 0) {
            mSeed = System.currentTimeMillis() + System.identityHashCode(this);
        }

        if (mVerbose > 0) {
            Logger.out.println(":Monkey: seed=" + mSeed + " count=" + mCount);
            MonkeyUtils.getPackageFilter().dump();
            if (mMainCategories.size() != 0) {
                Iterator<String> it = mMainCategories.iterator();
                while (it.hasNext()) {
                    Logger.out.println(":IncludeCategory: " + it.next());
                }
            }
        }

        if (!checkInternalConfiguration()) {
            return -2;
        }

        if (!getSystemInterfaces()) {
            return -3;
        }

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

        mRandom = new Random(mSeed);

        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;
        } 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;
        } else if (mServerPort != -1) {
            try {
                mEventSource = new MonkeySourceNetwork(mServerPort);
            } catch (IOException e) {
                Logger.out.println("Error binding to network socket.");
                return -5;
            }
            mCount = Integer.MAX_VALUE;
        } else {
            // random source by default
            if (mVerbose >= 2) { // check seeding performance
                Logger.out.println("// Seeded: " + mSeed);
            }
            mEventSource = new MonkeySourceRandom(mRandom, mMainApps,
                    mThrottle, mRandomizeThrottle, mPermissionTargetSystem);
            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();
        int crashedAtCycle = 0;
        try {
            crashedAtCycle = runMonkeyCycles();
        } finally {
            // Release the rotation lock if it's still held and restore the
            // original orientation.
            new MonkeyRotationEvent(Surface.ROTATION_0, false).injectEvent(
                mWm, mAm, mVerbose);
        }
        mNetworkMonitor.stop();

        synchronized (this) {
            if (mRequestAnrTraces) {
                reportAnrTraces();
                mRequestAnrTraces = false;
            }
            if (mRequestAnrBugreport){
                Logger.out.println("Print the anr report");
                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 (mRequestDumpsysMemInfo) {
                reportDumpsysMemInfo();
                mRequestDumpsysMemInfo = false;
            }
            if (mRequestPeriodicBugreport){
                getBugreport("Bugreport_");
                mRequestPeriodicBugreport = false;
            }
            if (mWatchdogWaiting) {
                mWatchdogWaiting = false;
                notifyAll();
            }
        }

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

        try {
            mAm.setActivityController(null, true);
            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) {
            Logger.out.println(":Dropped: keys=" + mDroppedKeyEvents
                    + " pointers=" + mDroppedPointerEvents
                    + " trackballs=" + mDroppedTrackballEvents
                    + " flips=" + mDroppedFlipEvents
                    + " rotations=" + mDroppedRotationEvents);
        }

        // report network stats
        mNetworkMonitor.dump();

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

   monkey程序执行过程中的主要方法,涉及很多业务逻辑,我们一点点的分析run()方法,传入参数args表示命令行参数,它是一个String数组对象,表示所有的命令行参数

1、首先遍历命令行参数,检查选项参数--wait-dbg

遍历传入给Monkey的命令行参数,检查是否存在--wait-dbg,如果存在会执行Debug.waitForDebugger(),我今天尝试了一下,不知道为何没有任何效果,但是这里的初衷是为了debug

2、为Monkey对象持有的实例变量赋初始值

Monkey对象已经创建,相关的实例变量、构造方法已经执行完毕,开始对实例变量进行二次赋值

mVerbose :表示日志等级,用于控制日志的输出数量

mCount:表示事件数量,事件数量越大,monkey程序执行的时间也就越长,此处设置默认值为1000

mSeed:表示随机种子值,可以通过命令行参数指定,这里先赋值一个指定的初始值

3、将所有的命令行参数赋值给Monkey对象持有

由Monkey对象持有的mArgs负责保存局部变量args指向的String数组对象,该数组对象保存着所有命令行参数

4、遍历所有的命令行参数,并输出到标准错误中

for (String a: args) {
    Logger.err.println(" arg: \"" + a + "\"");  //相信你在控制台一定见过此日志
} 

5、将表示下一个命令行参数下标的实例变量赋值为0

mNextArg = 0;

6、将保存事件比例的数组对象的所有元素全部赋值为1.0f【随机事件来源中会使用该比例】

for (int i = 0; i < MonkeySourceRandom.FACTORZ_COUNT; i++) {
    mFactors[i] = 1.0f;
} 
说明:mFactors是Monkey对象持有的一个数组对象,它一共有FACTORZ_COUNT个元素,每个元素表示一个事件的出现的比例

7、开始由processOptions()方法解析命令行参数,如果解析出错,返回错误码-1

if (!processOptions()) {
    return -1;
}
通过调用processOptions()方法解析命令行参数,如果解析出错,该方法会返回false,此时整个run()方法会返回-1,后面文章会单独讲授命令行解析的代码,这里暂时不表

8、由loadPakcageLists()方法检查指定文件中加载的包名是否正确

if (!loadPackageLists()) {
    return -1;
}
通过命令行参数可以指定黑名单文件、或者白名单文件,只有按照要求指定的应用才可以启动,指定黑名单文件与白名单文件是有规则限制的,所以通过方法先检查,这个我也会后面单独开辟文章讲解!如果发现文件的解析不正确,整个run()方法也会返回一个-1,注意-1是作为错误码哦

9、为category添加元素

Monkey对象持有的mMainCategories,保存着可以使用多个category字符串,这里主要是向List中添加一个CATEGORY_LAUNCHER、以及一个CATEGORY_MONKEY作为默认的category字符串。后面会在Intent中使用这两个category字符串

10、没有设置随机种子值,创建一个随机的种子值

mSeed = System.currentTimeMillis() + System.identityHashCode(this);

使用开机距今的时间戳与当前对象的hashcode值作为随机种子值

11、向控制台(标准输出)输出一段日志,前提是mVerbose大于0

        if (mVerbose > 0) {  //如果设置-v ,Logger是自己封装的Log工具类
            Logger.out.println(":Monkey: seed=" + mSeed + " count=" + mCount);
            MonkeyUtils.getPackageFilter().dump(); //输出一次设置的有效包名、无效包名
            if (mMainCategories.size() != 0) {
                Iterator<String> it = mMainCategories.iterator();
                while (it.hasNext()) { //遍历ArrayList
                    Logger.out.println(":IncludeCategory: " + it.next()); //打印包含的主要Category
                }
            }
        }

随机种子值、事件数量

支持运行的包名

不支持运行的包名

输出插入的所有category信息

12、有checkInternalConfiguration()方法检查内部配置,1个预留方法

if (!checkInternalConfiguration()) {
    return -2;
}
这里的方法是个空实现,此处返回的错误码为-2

13、初始化各种系统服务

if (!getSystemInterfaces()) {
    return -3;
}

通过getSystemInterfaces()方法,进行系统服务的初始化,使用远程Binder对象的引用,与系统服务进行通信,使用系统服务提供的功能,如果任何一个系统服务没有获取到,将返回-3的错误码,后门单独使用文章总结系统服务的初始化

14、获取所有的根Activity

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

通过getMainApps()方法,获取手机中所有App各自拥有的根Activity(可以从Launcher启动的Activity)信息,之后也会通过单独的文章讲解,如何获取根Activity的信息,这里如果没有获取到,就会返回一个-4的错误码

15、创建Random对象

使用Monkey对象持有的mSeed作为随机种子,创建Random对象,并且由Monkey对象持有的mRandom负责保存Random对象的引用(伪随机的原因,Random对象根据整型值每次产生的序列都一样)

16、选择事件源的逻辑,也是整个run()方法的核心业务逻辑,共计4个事件源,由用户传入的命令行参数决定不同的事件源

第1个事件源:指定单个脚本文件作为事件源

当Monkey对象持有的mScriptFileNames不为空(说明已经从命令行参数中指定),且这个List对象持有的元素数量为1时,会走单个脚本文件作为事件源,注意此处Monkey对象持有的mEventSource,实际指向的是MonkeySourceScript对象

mEventSource = new MonkeySourceScript(mRandom, mScriptFileNames.get(0), mThrottle,
        mRandomizeThrottle, mProfileWaitTime, mDeviceSleepTime);

第2个事件源:指定多个脚本文件作为事件源

当Monkey对象持有的mScriptFileNames,持有的元素数量大于1,说明用户通过命令行参数指定了多个脚本文件作为事件源,此时Monkey对象持有的mEventSource,实际指向的是MonkeySourceRandomScript对象,在这里面还会根据Monkey对象持有的mSetupFileName实行进一步的判断,传入不同的参数给MonkeySourceRandomScript

mEventSource = new MonkeySourceRandomScript(mSetupFileName,
        mScriptFileNames, mThrottle, mRandomizeThrottle, mRandom,
        mProfileWaitTime, mDeviceSleepTime, mRandomizeScript);

第3个事件源:指定Socket端口作为事件源

当Monkey对象持有的mServerPort不等于-1,说明已经通过命令行参数指定了端口号,此时Monkey对象持有的mEventSource指向的实际对象为MonkeySourceNetwork对象

mEventSource = new MonkeySourceNetwork(mServerPort);

第4个事件源:通过命令行指定事件比例作为事件源(平时使用monkey,都会走这个逻辑)

这是优先级最低的事件源,只有上面的条件都不满足时,才会走到这里,也是我们平时使用monkey程序时,通过命令行参数指定事件比例的事件源,此时Monkey对象持有的mEventSource实际执行的是MonkeySourceRandom对象

mEventSource = new MonkeySourceRandom(mRandom, mMainApps,
        mThrottle, mRandomizeThrottle, mPermissionTargetSystem);

事件源创建的业务逻辑较多,且复杂,这里暂时提及,后面会开辟文章单独总结每一个事件源的逻辑

17、检查事件源是否构建有效

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

由于作为事件源的对象按照MonkeyEventSource接口规范全部实现了validate()方法,此处通过调用validate()方法检查事件源是否合理,如果不合理,返回错误码-5,每个事件源都有不同的检查逻辑,后面单独总结

18、根据配置,构建进程信息

if (mGenerateHprof) {
    signalPersistentProcesses();
}

如果通过命令行参数指定需要构建进程信息,则会通过signalpersistenProcessses()方法构建每个进程的信息,这个我后面也会单独开文章总结

19、初始化监听网络的Binder

mNetworkMonitor.start();

20、从事件源不断的获取事件

int crashedAtCycle = 0;
try {
    crashedAtCycle = runMonkeyCycles();
} finally {
    // Release the rotation lock if it's still held and restore the
    // original orientation.
    new MonkeyRotationEvent(Surface.ROTATION_0, false).injectEvent(
        mWm, mAm, mVerbose);
}

会进入重要的runMOnkeyCycles()方法,它会使monkey主线程一直在该方法中运行,从事件源中获取一个一个的事件,这个我后面单独文章总结

21、停止网络情况的监控

mNetworkMonitor.stop();

注意,代码走到这里,从事件源中持续获取事件的业务逻辑已经结束

22、兜底操作,放置未生成相关日志

        //下面这部分代码,都是在运行事件流结束后(runMonkeyCycles()方法结束)才会走到这里(应该是用于收尾工作的代码)
        synchronized (this) { //Monkey主线程需要先获取Monkey对象锁,才能继续执行以下的代码块,这是为了与binder线程进行线程间的同步,因为他们都访问同样的共享变量,缺点是下面的代码与runMonkeyCycles()中的一部分有重复,目的是为了收集日志
            if (mRequestAnrTraces) { //当AMS发现某个app出现Anr,通过远程调用appNotResponse()方法,然后该值mRequestAnrTraces会在当前monkey进程的binder线程池中某个线程中赋值为true(由于binder线程池某个线程已经持有Monkey对象锁)
                reportAnrTraces(); //调用者获取anr trace信息,其实只是向标准输出流中打印anr的数据
                mRequestAnrTraces = false; //表示已经执行过anr trace的获取,不需要获取了
            }
            if (mRequestAnrBugreport){ //获取anr相关的,bugreport命令
                Logger.out.println("Print the anr report");
                getBugreport("anr_" + mReportProcessName + "_");
                mRequestAnrBugreport = false; //防止重复bugreport anr信息
            }
            if (mRequestWatchdogBugreport) {
                Logger.out.println("Print the watchdog report");
                getBugreport("anr_watchdog_"); //请求执行bugreport,获取关于watchdogBugreport的信息
                mRequestWatchdogBugreport = false; //防止重复调用
            }
            if (mRequestAppCrashBugreport){
                getBugreport("app_crash" + mReportProcessName + "_"); //请求执行bugreport,捕获关于app崩溃时的信息
                mRequestAppCrashBugreport = false;
            }
            if (mRequestDumpsysMemInfo) { //获取内存信息
                reportDumpsysMemInfo();
                mRequestDumpsysMemInfo = false;
            }
            if (mRequestPeriodicBugreport){ //到达某个时间点,貌似是个数值,符合要求,就执行一次bugreport
                getBugreport("Bugreport_");
                mRequestPeriodicBugreport = false;
            }
            if (mWatchdogWaiting) {
                mWatchdogWaiting = false;
                notifyAll(); //通知唤醒所有在Monkey对象上停顿的线程,继续执行,执行的线程需要先获取到对象锁
            }
        }

主线程必须先获取到Monkey对象锁,才能继续执行,主要调用reportAnrTraces()、getBugreport()等方法去收集日志,且收集日志工作都是在单独的子进程中执行的,而Monkey主线程会等待子进程完成日志收集工作后才会继续执行,这里的逻辑也会在单独的文章中总结

23、再次构建进程信息(如果命令行参数指定的话)

        //继续收尾工作
        if (mGenerateHprof) {
            signalPersistentProcesses(); //Monkey程序运行结束后,生成一份内存信息位于/data/misc目录下
            if (mVerbose > 0) {
                Logger.out.println("// Generated profiling reports in /data/misc");
            }
        }

构建的数据会持久化在

Generated profiling reports in /data/misc

24、向ActivityManagerService解除设置的ActivityController

mAm.setActivityController(null, true);

25、向ActivityManagerService解除注册的MonkeyNetworkMonitor这个Binder

26、在控制台打印无效的事件状态

if (mVerbose > 0) {
    Logger.out.println(":Dropped: keys=" + mDroppedKeyEvents
            + " pointers=" + mDroppedPointerEvents
            + " trackballs=" + mDroppedTrackballEvents
            + " flips=" + mDroppedFlipEvents
            + " rotations=" + mDroppedRotationEvents);
}

27、在控制台打印网络的监控情况

mNetworkMonitor.dump();

28、处理Monkey程序发现有崩溃情况出现(App、Native、Anr)

        if (crashedAtCycle < mCount - 1) {
            Logger.err.println("** System appears to have crashed at event " + crashedAtCycle
                    + " of " + mCount + " using seed " + mSeed); //当发现的崩溃数量小于执行次数,在标准错误流中输出一段日志
            return crashedAtCycle;  //返回的是循环次数,说明在什么时候发现崩溃,且也代表退出状态码
        }

则输出一行提示,然后返回发现有崩溃时的循环次数,同时该返回值作为退出状态码

29、如果Monkey程序正常退出,直接返回一个0表示退出状态码,并且会在在标准输出写入// Monkey finished,表示Monkey程序正常退出

else {
            if (mVerbose > 0) {
                Logger.out.println("// Monkey finished");
            }
            return 0; //表示Monkey程序正常退出,并没有发现其他应用崩溃,这里的0指的是退出状态码
        }

总结

1、Monkey程序的每一处业务逻辑,值得深挖与学习,所以后面的文章,将深挖这些业务逻辑是如何实现的,作者又用到哪些Java技术!

2、学习Monkey程序的编写,让我自己的技术真是进步不少!

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐