android Monkey测试源码分析之一

1, 概述

Monkey模拟用户的按键输入,手势输入等各种事件,看设备多长时间会出异常,是一种简单粗暴的压力测试。

同其他大多数adb命令一样,Monkey命令也是shell脚本,system/bin 目录下有一个monkey文件脚本。主要内容如下

base=/system
export CLASSPATH=$base/framework/monkey.jar
trap "" HUP
exec app_process $base/bin com.android.commands.monkey.Monkey $*

首先会导入monkey  jar包, 然后通过app_process来指定命令工作路径为'/system/bin/'

以启动指定类com.android.commands. monkey.Monkey,启动该类传入的参数就是指定的测试用例类. 最后会调用Monkey类中的main方法。

路径: development\cmds\monkey\src\com\android\commands\monkey

所有和monkey有关的代码都在该目录下,结构也很容易。

Monkey命令的使用在此就不多论述了,直接看实现原理。

2,main方法

Monkey的main方法如下,

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

        int resultCode = (new Monkey()).run(args); //调用run方法
        System.exit(resultCode);// 退出
    }

run方法首先设置默认参数

mVerbose = 0;
        mCount = 1000;
        mSeed = 0;
        mThrottle = 0;

        // prepare for command-line processing
        mArgs = args;//命令参数
        mNextArg = 0;

为字符数组mFactors赋初值,

for (int i = 0; i < MonkeySourceRandom.FACTORZ_COUNT; i++) {
            mFactors[i] = 1.0f;
        }

该数组定义如下,

float[] mFactors = new float[MonkeySourceRandom.FACTORZ_COUNT];

数组大小为12

public static final int FACTORZ_COUNT       = 12;

然后依次调用以下方法。

2.1 processOptions

processOptions主要解析输入命令。

首先解析命令,为对应的变量赋值

try {
            String opt;
            Set<String> validPackages = new HashSet<>();
            while ((opt = nextOption()) != null) {
                if (opt.equals("-s")) {
                    mSeed = nextOptionLong("Seed");
                } else if (opt.equals("-p")) {
                    validPackages.add(nextOptionData());
                } else if (opt.equals("-c")) {
                    mMainCategories.add(nextOptionData());
                } else if (opt.equals("-v")) {
                    mVerbose += 1;
                } else if (opt.equals("--ignore-crashes")) {
                    mIgnoreCrashes = true;
                } else if (opt.equals("--ignore-timeouts")) {
                    mIgnoreTimeouts = true;
                } else if (opt.equals("--ignore-security-exceptions")) {
                    mIgnoreSecurityExceptions = true;
                } else if (opt.equals("--monitor-native-crashes")) {
                    mMonitorNativeCrashes = true;
                } else if (opt.equals("--ignore-native-crashes")) {
                    mIgnoreNativeCrashes = true;
                } else if (opt.equals("--kill-process-after-error")) {
                    mKillProcessAfterError = true;
                } else if (opt.equals("--hprof")) {
                    mGenerateHprof = true;
                } else if (opt.equals("--pct-touch")) {
                    int i = MonkeySourceRandom.FACTOR_TOUCH;
                    mFactors[i] = -nextOptionLong("touch events percentage");
                }•••

其中调用的nextOption方法主要解析命令参数名

private String nextOption() {
        if (mNextArg >= mArgs.length) {
            return null;
        }
        String arg = mArgs[mNextArg];
        if (!arg.startsWith("-")) {
            return null;
        }
        mNextArg++;
        if (arg.equals("--")) {
            return null;
        }
        if (arg.length() > 1 && arg.charAt(1) != '-') {
            if (arg.length() > 2) {
                mCurArgData = arg.substring(2);
                return arg.substring(0, 2);
            } else {
                mCurArgData = null;
                return arg;
            }
        }
        mCurArgData = null;
        return arg;
    }

nextOptionLong以及nextOptionData主要是解析命令参数名后面的参数值。

private String nextOptionData() {
        if (mCurArgData != null) {
            return mCurArgData;
        }
        if (mNextArg >= mArgs.length) {
            return null;
        }
        String data = mArgs[mNextArg];
        mNextArg++;
        return data;
    }

这样就可以解析出对应的参数了,而且写monkey命令时,不需要写出所有的命令参数。

待测试的apk包名都放在validPackages哈希表中,

Set<String> validPackages = new HashSet<>();

解析完之后,将该值保存在MonkeyUtils内部类PackageFilter的mValidPackages变量中。

MonkeyUtils.getPackageFilter().addValidPackages(validPackages);

其addValidPackages方法如下,

public void addValidPackages(Set<String> validPackages) {
            mValidPackages.addAll(validPackages);
        }

通过解析方法知道,变量mFactors数组中对应的位置存放的是对应UI界面各种事件的百分比。

public static final int FACTOR_TOUCH        = 0;
    public static final int FACTOR_MOTION       = 1;
    public static final int FACTOR_PINCHZOOM    = 2;
    public static final int FACTOR_TRACKBALL    = 3;
    public static final int FACTOR_ROTATION     = 4;
    public static final int FACTOR_PERMISSION   = 5;
    public static final int FACTOR_NAV          = 6;
    public static final int FACTOR_MAJORNAV     = 7;
    public static final int FACTOR_SYSOPS       = 8;
    public static final int FACTOR_APPSWITCH    = 9;
    public static final int FACTOR_FLIP         = 10;
    public static final int FACTOR_ANYTHING     = 11;

2.2 系统日志

if (!loadPackageLists()) { //导入package列表
            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) {  // 添加系统日志
            System.out.println(":Monkey: seed=" + mSeed + " count=" + mCount);
            MonkeyUtils.getPackageFilter().dump();
            if (mMainCategories.size() != 0) {
                Iterator<String> it = mMainCategories.iterator();
                while (it.hasNext()) {
                    System.out.println(":IncludeCategory: " + it.next());
                }
            }
        }

        if (!checkInternalConfiguration()) { //检查内部配置
            return -2;
        }

        if (!getSystemInterfaces()) { // 获取系统接口
            return -3;
        }

getSystemInterfaces方法如下,

private boolean getSystemInterfaces() {
        mAm = ActivityManagerNative.getDefault();
        if (mAm == null) {
            System.err.println("** Error: Unable to connect to activity manager; is the system "
                    + "running?");
            return false;
        }

        mWm = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
        if (mWm == null) {
            System.err.println("** Error: Unable to connect to window manager; is the system "
                    + "running?");
            return false;
        }

        mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
        if (mPm == null) {
            System.err.println("** Error: Unable to connect to package manager; is the system "
                    + "running?");
            return false;
        }

        try {
            mAm.setActivityController(new ActivityController());
            mNetworkMonitor.register(mAm);
        } catch (RemoteException e) {
            System.err.println("** Failed talking with activity manager!");
            return false;
        }

        return true;
    }
if (!getMainApps()) { // 获取合法的package列表
            return -4;
        }

三个重要服务,

通过AM调用setActivityController方法来控制整个测试生命周期。

通过WM中提供的方法可以顺利注入各种事件。

通过PM可以在测试时在多个应用之间进行切换。

mNetworkMonitor变量是MonkeyNetworkMonitor对象,主要监听网络状态的变化。

各种环境设置好之后,开始进行测试。

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) {
                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, 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();
        }

Monkey运行模式主要有4种:

1,直接运行脚本

2,运行setup脚本/随机运行所有脚本

3,运行远程调用

4,直接随机运行

主要是构造不同的MonkeyEventSource子类,子类的构造方法在此就不分析了。

最后终于调用runMonkeyCycles方法进行测试了

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();

2.3 runMonkeyCycles

runMonkeyCycles代码虽然长,最重要的代码如下,

MonkeyEvent ev = mEventSource.getNextEvent();
            if (ev != null) {
                int injectCode = ev.injectEvent(mWm, mAm, mVerbose);

根据mEventSource对象,取出下一条消息,然后调用MonkeyEvent的injectEvent方法注入事件。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值