zoukankan      html  css  js  c++  java
  • 【Android测试】【第七节】Monkey——源码浅谈

     版权声明:本文出自胖喵~的博客,转载必须注明出处。

        转载请注明出处:http://www.cnblogs.com/by-dream/p/4713466.html

    前言


       根据上一篇我们学会了Monkey的用法,知道了Monkey可以非常容易的模拟伪随机的模拟事件。也许有的时候我们想让他稍微智能化一些,例如只在某个屏幕范围产生伪随机事件,或者说是只对某些指定Activity进行操作,这样就需要我们对Monkey进行改良了。而改良必须去改Monkey的源码,因此本节课们就简单的说说Monkey的源码。

      源码下载地址:https://code.google.com/p/android-source-browsing/source/browse/cmds/monkey/src/com/android/commands/monkey/?repo=platform--development&name=android-cts-4.2_r2  ( 这里只是Monkey的源码,如果要编译Monkey需要下载Android的源码 )

      

    概述


      如果你真的打算改造一个属于你的Monkey,那么过程必须要做的是:

      1、下载Android源码

      2、阅读Monkey源码如果需要修改代码

      3、代码编译

      4、运行Monkey

      本节内容主要针对第二部分的 “阅读Monkey源码”,其他的1、3、4 部分会在另外一篇“只允许注册用户访问的”的番外篇里进行介绍,因为这部分有些内容不是本人原创,因此对博客进行了加密处理,以免侵犯到源作者的权利,如需交流这部分内容,请留言给我。

    Monkey源码


          Monkey的入口在 Monkey.java中:

     /**
         * Command-line entry point.
         *
         * @param args The command-line arguments
         */
        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);
            System.exit(resultCode);
        }

      第一句的意思就是在 shell 命令行下 使用 ps | grep com.**.monkey 就找到正在运行的monkey进程

      第二句是后续的内容,我们继续看后续干了什么。

        /**
         * Run the command!
         *
         * @param args The command-line arguments
         * @return Returns a posix-style result code. 0 for no error.
         */
        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;
            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 (mVerbose > 0) {
                System.out.println(":Monkey: seed=" + mSeed + " count=" + mCount);
                if (mValidPackages.size() > 0) {
                    Iterator<String> it = mValidPackages.iterator();
                    while (it.hasNext()) {
                        System.out.println(":AllowPackage: " + it.next());
                    }
                }
                if (mInvalidPackages.size() > 0) {
                    Iterator<String> it = mInvalidPackages.iterator();
                    while (it.hasNext()) {
                        System.out.println(":DisallowPackage: " + it.next());
                    }
                }
                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;
            }
    
            if (!getMainApps()) {
                return -4;
            }
    
            mRandom = new SecureRandom();
            mRandom.setSeed((mSeed == 0) ? -1 : 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);
                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 = runMonkeyCycles();
            mNetworkMonitor.stop();
    
            synchronized (this) {
                if (mRequestAnrTraces) {
                    reportAnrTraces();
                    mRequestAnrTraces = false;
                }
                if (mRequestAnrBugreport){
                    System.out.println("Print the anr report");
                    getBugreport("anr_" + mReportProcessName + "_");
                    mRequestAnrBugreport = false;
                }
                if (mRequestAppCrashBugreport){
                    getBugreport("app_crash" + mReportProcessName + "_");
                    mRequestAppCrashBugreport = false;
                }
                if (mRequestDumpsysMemInfo) {
                    reportDumpsysMemInfo();
                    mRequestDumpsysMemInfo = false;
                }
                if (mRequestPeriodicBugreport){
                    getBugreport("Bugreport_");
                    mRequestPeriodicBugreport = false;
                }
            }
    
            if (mGenerateHprof) {
                signalPersistentProcesses();
                if (mVerbose > 0) {
                    System.out.println("// Generated profiling reports in /data/misc");
                }
            }
    
            try {
                mAm.setActivityController(null);
                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) {
                System.out.print(":Dropped: keys=");
                System.out.print(mDroppedKeyEvents);
                System.out.print(" pointers=");
                System.out.print(mDroppedPointerEvents);
                System.out.print(" trackballs=");
                System.out.print(mDroppedTrackballEvents);
                System.out.print(" flips=");
                System.out.println(mDroppedFlipEvents);
            }
    
            // report network stats
            mNetworkMonitor.dump();
    
            if (crashedAtCycle < mCount - 1) {
                System.err.println("** System appears to have crashed at event " + crashedAtCycle
                        + " of " + mCount + " using seed " + mSeed);
                return crashedAtCycle;
            } else {
                if (mVerbose > 0) {
                    System.out.println("// Monkey finished");
                }
                return 0;
            }
        }
    run

      这个run中的内容基本就是Monkey运行的流程,主要做了:

      1、处理命令行参数

    if (!processOptions()) {
                return -1;
            }

        没有什么特殊的地方,主要是针对下面这张图里我们用到的参数进行一个统计和预处理。

      

      2、处理要拉起的应用程序的Activity:

        我们在运行Monkey的时候,如果指定了“ -p 包名 ”,那么Monkey一定会拉起这个App的第一个Activity,这个究竟是怎么实现的呢?就是借助Intent这个东西:

         // now set up additional data in preparation for launch
            if (mMainCategories.size() == 0) {
                mMainCategories.add(Intent.CATEGORY_LAUNCHER);
                mMainCategories.add(Intent.CATEGORY_MONKEY);
            }

      3、处理Source模块:

        Source模块,以MonkeyEventSource为接口,衍生出三种Source类:MonkeySourceRandom类(随机生成事件)、MonkeySourceScript(从脚本获取事件)、MonkeySourceNetwork(从网络获取事件)。    

          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);
                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的事件源来自何方,根据这些事件的来源,由不同的类做处理。MonkeySourceRandom事件的来源就是我们在命令行输入参数后的伪随机压力测试;MonkeySourceScript事件来源于Monkey识别的一种脚本,事实上Monkey也可以做到通过脚本指定位置点击,滑动等操作,但是该脚本的可读性非常的差,编写不易,因此这里我也没有介绍;第三种MonkeySourceNetwork来自于后面我们要讲的Monkeyrunner,Monkeyrunner通过socket将一些要处理的事件发给Monkey,由Monkey来完成最后的处理。

      4、循环处理事件:

            mNetworkMonitor.start();
            int crashedAtCycle = runMonkeyCycles();
            mNetworkMonitor.stop();    

        主要看看 runMonkeyCycles() 这个函数主要做了什么:

        /** 
         * 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;  
      
            // TO DO : The count should apply to each of the script file.  
            while (!systemCrashed && cycleCounter < mCount) {  
                    ...  
                MonkeyEvent ev = mEventSource.getNextEvent();  
                if (ev != null) {  
                    int injectCode = ev.injectEvent(mWm, mAm, mVerbose);  
                    ...  
                 }  
            ...  
            }  
           ....  
    }  

        这里涉及到了一个重要的东西就是MonkeyEvent。

        以MonkeyEvent为基类,衍生出各种Event类,如Monkey中常见的点击,输入,滑动事件;

        那么一个点击的操作究竟是怎么进行下去的呢?我们可以到上面调用的是injectEvent,这个方法是由基类定义的,每一个子类去实现不同的内容,点击、滑动等这个方法都是通过第一个参数一个iWindowManager的对象而完成的,当然也有不需要这个参数,例如MonkeyThrottleEvent这个类的实现方法,根本没有用到iwm:

    @Override  
    public int injectEvent(IWindowManager iwm, IActivityManager iam, int verbose) {  
      
        if (verbose > 1) {  
            System.out.println("Sleeping for " + mThrottle + " milliseconds");  
        }  
        try {  
            Thread.sleep(mThrottle);  
        } catch (InterruptedException e1) {  
            System.out.println("** Monkey interrupted in sleep.");  
            return MonkeyEvent.INJECT_FAIL;  
        }  
          
        return MonkeyEvent.INJECT_SUCCESS;  
    }  

        那么这个iWindowManager的对象究竟是什么呢?这个事系统隐藏的一个接口,通过这个接口可以注入一些操作事件,那么我们以后是不是也可以用这个接口来进行事件的注入呢?答案是no,为什么呢?我们来看看:

        谷歌为了方便Monkey能够轻松的完成一些点击、滑动事件,因此在使用了这个系统隐藏的接口,Monkey这个应用拥有这个两个独特的权限:第一个是SET_ACTIVITY_WATCHER这个权限,它允许monkey对activity的生命周期进行全权控制。第二个就是INJECT_EVENTS这个权限它允许monkey去模拟触摸和按键事件。为了防止这个系统隐藏接口暴露出的漏洞,普通的App是不能请求到这些权限的,只有android系统同意的应用才会得到允许获得这些权限。为了防止坏人使用Monkey来进行这个事件的注入,Monkey也只被允许root运行或者是shell这个组的成员运行。

  • 相关阅读:
    scrapy框架之comand line tool
    CSS选择器与XPath语言
    Selenium之Web页面滚动条滚操作
    Selenium+Chrome+PhantomJS 爬取淘宝
    爬取今日头条中的图片
    django 和 mongdb 写一个简陋的网址,以及用django内置的分页功能
    charts 画饼图
    charts 画折线图
    oracle的char和varchar类型
    ORA-02049: 超时: 分布式事务处理等待锁的解决方法
  • 原文地址:https://www.cnblogs.com/by-dream/p/4713466.html
Copyright © 2011-2022 走看看