0


【Android12】Monkey压力测试源码执行流程分析

Monkey压力测试源码执行流程分析

Monkey是Android提供的用于应用程序自动化测试、压力测试的测试工具。
其源码路径(Android12)位于

/development/cmds/monkey/

部署形式为Java Binary

# development/cmds/monkey/Android.bp
// Copyright 2008 The Android Open Source Project
//

package {
    default_applicable_licenses: ["development_cmds_monkey_license"],
}

// See: http://go/android-license-faq
license {
    name: "development_cmds_monkey_license",
    visibility: [":__subpackages__"],
    license_kinds: ["SPDX-license-identifier-Apache-2.0",
    ],
    license_text: ["NOTICE",
    ],
}

//###############################################################
java_binary {
    name: "monkey",
    srcs: ["**/*.java"],
    wrapper: "monkey",
}

通过Monkey,可以模拟用户的Touch(单指、多指、手势)、按键(key)事件等,检测应用程序发生的ANR、Crash事件,并收集相关Debug信息等。
例如测试应用com.package.linduo,

adb shell monkey -p com.package.linduo --pct-touch 10 --pct-motion 2010000# 该命令表示,执行1万次测试事件,其中Touch事件占10%,Motion事件占20%# 或者adb shell进入android终端,直接使用monkey命令

Monkey支持的命令

privatevoidshowUsage(){StringBuffer usage =newStringBuffer();
        usage.append("usage: monkey [-p ALLOWED_PACKAGE [-p ALLOWED_PACKAGE] ...]\n");
        usage.append("              [-c MAIN_CATEGORY [-c MAIN_CATEGORY] ...]\n");
        usage.append("              [--ignore-crashes] [--ignore-timeouts]\n");
        usage.append("              [--ignore-security-exceptions]\n");
        usage.append("              [--monitor-native-crashes] [--ignore-native-crashes]\n");
        usage.append("              [--kill-process-after-error] [--hprof]\n");
        usage.append("              [--match-description TEXT]\n");
        usage.append("              [--pct-touch PERCENT] [--pct-motion PERCENT]\n");
        usage.append("              [--pct-trackball PERCENT] [--pct-syskeys PERCENT]\n");
        usage.append("              [--pct-nav PERCENT] [--pct-majornav PERCENT]\n");
        usage.append("              [--pct-appswitch PERCENT] [--pct-flip PERCENT]\n");
        usage.append("              [--pct-anyevent PERCENT] [--pct-pinchzoom PERCENT]\n");
        usage.append("              [--pct-permission PERCENT]\n");
        usage.append("              [--pkg-blacklist-file PACKAGE_BLACKLIST_FILE]\n");
        usage.append("              [--pkg-whitelist-file PACKAGE_WHITELIST_FILE]\n");
        usage.append("              [--wait-dbg] [--dbg-no-events]\n");
        usage.append("              [--setup scriptfile] [-f scriptfile [-f scriptfile] ...]\n");
        usage.append("              [--port port]\n");
        usage.append("              [-s SEED] [-v [-v] ...]\n");
        usage.append("              [--throttle MILLISEC] [--randomize-throttle]\n");
        usage.append("              [--profile-wait MILLISEC]\n");
        usage.append("              [--device-sleep-time MILLISEC]\n");
        usage.append("              [--randomize-script]\n");
        usage.append("              [--script-log]\n");
        usage.append("              [--bugreport]\n");
        usage.append("              [--periodic-bugreport]\n");
        usage.append("              [--permission-target-system]\n");
        usage.append("              COUNT\n");Logger.err.println(usage.toString());}

Monkey执行测试的源码分析

这里主要关注模式事件的执行流程

  • Monkey启动
  • Monkey生成模拟事件
  • Monkey向系统发送模拟事件在这里插入图片描述
Monkey启动

Monkey.java中定义了程序入口函数main,该函数中启动了Monkey程序。

// development/cmds/monkey/src/com/android/commands/monkey/Monkey.javapublicstaticvoidmain(String[] args){// Set the process name showing in "ps" or "top"Process.setArgV0("com.android.commands.monkey");Logger.err.println("args: "+Arrays.toString(args));int resultCode =(newMonkey()).run(args);System.exit(resultCode);}
// development/cmds/monkey/src/com/android/commands/monkey/Monkey.java/**
 * Run the command!
 *
 * @param args The command-line arguments
 * @return Returns a posix-style result code. 0 for no error.
 */privateintrun(String[] args){// Default values for some command-line options
    mVerbose =0;// 默认的测试次数
    mCount =1000;// 生成radom的seed
    mSeed =0;// 记录事件之间的延迟,就是每个事件执行的间隔
    mThrottle =0;// prepare for command-line processing
    mArgs = args;// 解析参数if(!processOptions()){return-1;}// 确定待测试的Packageif(!loadPackageLists()){return-1;}// now set up additional data in preparation for launchif(mMainCategories.size()==0){
        mMainCategories.add(Intent.CATEGORY_LAUNCHER);
        mMainCategories.add(Intent.CATEGORY_MONKEY);}if(mSeed ==0){
        mSeed =System.currentTimeMillis()+System.identityHashCode(this);}// 获取系统服务接口(AMS、PMS、WMS)if(!getSystemInterfaces()){return-3;}// 获取用于启动应用的Activityif(!getMainApps()){return-4;}if(mScriptFileNames !=null&& mScriptFileNames.size()==1){// script mode, ignore other options}elseif(mScriptFileNames !=null&& mScriptFileNames.size()>1){}elseif(mServerPort !=-1){}else{// 创建用于产生模拟器事件的Source对象
        mEventSource =newMonkeySourceRandom(mRandom, mMainApps,
                mThrottle, mRandomizeThrottle, mPermissionTargetSystem);
        mEventSource.setVerbose(mVerbose);// 设置各测试类型的测试比例// set any of the factors that has been setfor(int i =0; i <MonkeySourceRandom.FACTORZ_COUNT; i++){if(mFactors[i]<=0.0f){((MonkeySourceRandom) mEventSource).setFactors(i, mFactors[i]);}}// 产生activity事件,该事件用来启动应用// in random mode, we start with a random activity((MonkeySourceRandom) mEventSource).generateActivity();}try{// 执行模拟测试事件
        crashedAtCycle =runMonkeyCycles();}finally{// Release the rotation lock if it's still held and restore the// original orientation.newMonkeyRotationEvent(Surface.ROTATION_0,false).injectEvent(
            mWm, mAm, mVerbose);}}
Monkey解析输入参数

processOptions函数解析输入参数(就是monkey命令后跟着的参数信息),根据入参设置Monkey类中相关成员变量。

// development/cmds/monkey/src/com/android/commands/monkey/Monkey.javaprivatebooleanprocessOptions(){// quick (throwaway) check for unadorned commandif(mArgs.length <1){showUsage();returnfalse;}try{String opt;Set<String> validPackages =newHashSet<>();while((opt =nextOption())!=null){if(opt.equals("-s")){
                mSeed =nextOptionLong("Seed");}elseif(opt.equals("-p")){
                validPackages.add(nextOptionData());}elseif(opt.equals("-c")){// 省略}else{Logger.err.println("** Error: Unknown option: "+ opt);showUsage();returnfalse;}}// 根据输入参数,设置待测试的应用MonkeyUtils.getPackageFilter().addValidPackages(validPackages);}catch(RuntimeException ex){Logger.err.println("** Error: "+ ex.toString());showUsage();returnfalse;}// If a server port hasn't been specified, we need to specify// a countif(mServerPort ==-1){// 省略}returntrue;}
Monkey获取系统服务

getSystemInterfaces函数用于获取Android系统服务,包括AMS、PMS、WMS服务。调用AMS服务的setActivityController接口,通过该接口向AMS设置IActivityController.Stub对象,通过该对象监听应用(Activity)的ANR和Crash事件。

/**
 * Attach to the required system interfaces.
 *
 * @return Returns true if all system interfaces were available.
 */privatebooleangetSystemInterfaces(){
    mAm =ActivityManager.getService();if(mAm ==null){Logger.err.println("** Error: Unable to connect to activity manager; is the system "+"running?");returnfalse;}

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

    mPm =IPackageManager.Stub.asInterface(ServiceManager.getService("package"));if(mPm ==null){Logger.err.println("** Error: Unable to connect to package manager; is the system "+"running?");returnfalse;}try{
        mAm.setActivityController(newActivityController(),true);
        mNetworkMonitor.register(mAm);}catch(RemoteException e){Logger.err.println("** Failed talking with activity manager!");returnfalse;}returntrue;}/**
 * Monitor operations happening in the system.
 */privateclassActivityControllerextendsIActivityController.Stub{publicbooleanactivityStarting(Intent intent,String pkg){// 省略}privatebooleanisActivityStartingAllowed(Intent intent,String pkg){// 省略}publicbooleanactivityResuming(String pkg){// 省略}publicbooleanappCrashed(String processName,int pid,String shortMsg,String longMsg,long timeMillis,String stackTrace){// 省略}publicintappEarlyNotResponding(String processName,int pid,String annotation){return0;}publicintappNotResponding(String processName,int pid,String processStats){// 省略}publicintsystemNotResponding(String message){// 省略}}
Monkey获取待测试应用的Activity

monkey通过PackageManager的queryIntentActivities接口,查询带有 Intent.CATEGORY_LAUNCHERIntent.CATEGORY_MONKEY信息的Activity,并判断Activity是否属于待测试应用。将待测试应用的Activity添加到mMainApps变量中。

// development/cmds/monkey/src/com/android/commands/monkey/Monkey.java/**
 * Using the restrictions provided (categories & packages), generate a list
 * of activities that we can actually switch to.
 *
 * @return Returns true if it could successfully build a list of target
 *         activities
 */privatebooleangetMainApps(){try{finalintN= mMainCategories.size();for(int i =0; i <N; i++){Intent intent =newIntent(Intent.ACTION_MAIN);String category = mMainCategories.get(i);if(category.length()>0){
                intent.addCategory(category);}// 查找带有 Intent.CATEGORY_LAUNCHER、Intent.CATEGORY_MONKEY的ActivityList<ResolveInfo> mainApps = mPm.queryIntentActivities(intent,null,0,ActivityManager.getCurrentUser()).getList();finalintNA= mainApps.size();for(int a =0; a <NA; a++){ResolveInfo r = mainApps.get(a);String packageName = r.activityInfo.applicationInfo.packageName;if(MonkeyUtils.getPackageFilter().checkEnteringPackage(packageName)){// 如果Activity属于待测试Package,将其添加到mMainApps中。
                    mMainApps.add(newComponentName(packageName, r.activityInfo.name));}else{}}}}catch(RemoteException e){Logger.err.println("** Failed talking with package manager!");returnfalse;}if(mMainApps.size()==0){Logger.out.println("** No activities found to run, monkey aborted.");returnfalse;}returntrue;}
Monkey生成模拟测试事件,并执行
// development/cmds/monkey/src/com/android/commands/monkey/Monkey.javaprivateintrun(String[] args){// 创建该对象,用于产生测试事件
    mEventSource =newMonkeySourceRandom(mRandom, mMainApps,
            mThrottle, mRandomizeThrottle, mPermissionTargetSystem);for(int i =0; i <MonkeySourceRandom.FACTORZ_COUNT; i++){if(mFactors[i]<=0.0f){((MonkeySourceRandom) mEventSource).setFactors(i, mFactors[i]);}}try{// 执行Monkey测试
        crashedAtCycle =runMonkeyCycles();}finally{// Release the rotation lock if it's still held and restore the// original orientation.newMonkeyRotationEvent(Surface.ROTATION_0,false).injectEvent(
            mWm, mAm, mVerbose);}}

runMonkeyCycles函数中调用MonkeySourceRandom的getNextEvent函数生成模拟测试事件(MonkeyEvent),调用MonkeyEventinjectEvent执行模拟测试。

privateintrunMonkeyCycles(){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.while(!systemCrashed && cycleCounter < mCount){synchronized(this){// 注意:因为先执行过generateActivity,所以第一次调用会,会获得启动Activity的模拟测试事件MonkeyEvent ev = mEventSource.getNextEvent();if(ev !=null){int injectCode = ev.injectEvent(mWm, mAm, mVerbose);}else{}}}catch(RuntimeException e){Logger.error("** Error: A RuntimeException occurred:", e);}Logger.out.println("Events injected: "+ eventCounter);return eventCounter;}

MonkeySourceRandom的getNextEvent,会在事件队列(存储模拟测试事件)为空时,产生测试对象。生成测试事件时,先生成一个随机数,然后根据测试类型所占比例(比例越大,生成该测试类型的概率越大),生成不同测试类型。

// development/cmds/monkey/src/com/android/commands/monkey/MonkeySourceRandom.java/**
 * generate an activity event
 */publicvoidgenerateActivity(){MonkeyActivityEvent e =newMonkeyActivityEvent(mMainApps.get(
            mRandom.nextInt(mMainApps.size())));
    mQ.addLast(e);}/**
 * if the queue is empty, we generate events first
 * @return the first event in the queue
 */publicMonkeyEventgetNextEvent(){if(mQ.isEmpty()){generateEvents();}
    mEventCount++;MonkeyEvent e = mQ.getFirst();
    mQ.removeFirst();return e;}/**
 * generate a random event based on mFactor
 */privatevoidgenerateEvents(){// 生成随机数float cls = mRandom.nextFloat();int lastKey =0;// 根据Factor,即不同测试类型所占的比例,生成测试事件if(cls < mFactors[FACTOR_TOUCH]){generatePointerEvent(mRandom,GESTURE_TAP);return;}elseif(cls < mFactors[FACTOR_MOTION]){generatePointerEvent(mRandom,GESTURE_DRAG);return;}elseif(cls < mFactors[FACTOR_PINCHZOOM]){generatePointerEvent(mRandom,GESTURE_PINCH_OR_ZOOM);return;}elseif(cls < mFactors[FACTOR_TRACKBALL]){generateTrackballEvent(mRandom);return;}elseif(cls < mFactors[FACTOR_ROTATION]){generateRotationEvent(mRandom);return;}elseif(cls < mFactors[FACTOR_PERMISSION]){
        mQ.add(mPermissionUtil.generateRandomPermissionEvent(mRandom));return;}// The remaining event categories are injected as key eventsfor(;;){if(cls < mFactors[FACTOR_NAV]){
            lastKey =NAV_KEYS[mRandom.nextInt(NAV_KEYS.length)];}elseif(cls < mFactors[FACTOR_MAJORNAV]){
            lastKey =MAJOR_NAV_KEYS[mRandom.nextInt(MAJOR_NAV_KEYS.length)];}elseif(cls < mFactors[FACTOR_SYSOPS]){
            lastKey =SYS_KEYS[mRandom.nextInt(SYS_KEYS.length)];}elseif(cls < mFactors[FACTOR_APPSWITCH]){MonkeyActivityEvent e =newMonkeyActivityEvent(mMainApps.get(
                    mRandom.nextInt(mMainApps.size())));
            mQ.addLast(e);return;}elseif(cls < mFactors[FACTOR_FLIP]){MonkeyFlipEvent e =newMonkeyFlipEvent(mKeyboardOpen);
            mKeyboardOpen =!mKeyboardOpen;
            mQ.addLast(e);return;}else{
            lastKey =1+ mRandom.nextInt(KeyEvent.getMaxKeyCode()-1);}if(lastKey !=KeyEvent.KEYCODE_POWER&& lastKey !=KeyEvent.KEYCODE_ENDCALL&& lastKey !=KeyEvent.KEYCODE_SLEEP&& lastKey !=KeyEvent.KEYCODE_SOFT_SLEEP&&PHYSICAL_KEY_EXISTS[lastKey]){break;}}MonkeyKeyEvent e =newMonkeyKeyEvent(KeyEvent.ACTION_DOWN, lastKey);
    mQ.addLast(e);

    e =newMonkeyKeyEvent(KeyEvent.ACTION_UP, lastKey);
    mQ.addLast(e);}

以Ttouch事件为例子,调用generatePointerEvent函数。通过DMS获取Display对象(用于得知屏幕大小),生成MonkeyTouchEvent对象。

// development/cmds/monkey/src/com/android/commands/monkey/MonkeySourceRandom.javaprivatevoidgeneratePointerEvent(Random random,int gesture){Display display =DisplayManagerGlobal.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY);PointF p1 =randomPoint(random, display);PointF v1 =randomVector(random);long downAt =SystemClock.uptimeMillis();

    mQ.addLast(newMonkeyTouchEvent(MotionEvent.ACTION_DOWN).setDownTime(downAt).addPointer(0, p1.x, p1.y).setIntermediateNote(false));// 省略randomWalk(random, display, p1, v1);
    mQ.addLast(newMonkeyTouchEvent(MotionEvent.ACTION_UP).setDownTime(downAt).addPointer(0, p1.x, p1.y).setIntermediateNote(false));}

调用MonkeyTouchEvent的injectEvent函数,使用InputManager向系统派发TouchEvent。

// development/cmds/monkey/src/com/android/commands/monkey/MonkeyMotionEvent.java@OverridepublicintinjectEvent(IWindowManager iwm,IActivityManager iam,int verbose){MotionEvent me =getEvent();if((verbose >0&&!mIntermediateNote)|| verbose >1){StringBuilder msg =newStringBuilder(":Sending ");
        msg.append(getTypeLabel()).append(" (");switch(me.getActionMasked()){caseMotionEvent.ACTION_DOWN:
                msg.append("ACTION_DOWN");break;caseMotionEvent.ACTION_MOVE:
                msg.append("ACTION_MOVE");break;caseMotionEvent.ACTION_UP:
                msg.append("ACTION_UP");break;caseMotionEvent.ACTION_CANCEL:
                msg.append("ACTION_CANCEL");break;caseMotionEvent.ACTION_POINTER_DOWN:
                msg.append("ACTION_POINTER_DOWN ").append(me.getPointerId(me.getActionIndex()));break;caseMotionEvent.ACTION_POINTER_UP:
                msg.append("ACTION_POINTER_UP ").append(me.getPointerId(me.getActionIndex()));break;default:
                msg.append(me.getAction());break;}
        msg.append("):");int pointerCount = me.getPointerCount();for(int i =0; i < pointerCount; i++){
            msg.append(" ").append(me.getPointerId(i));
            msg.append(":(").append(me.getX(i)).append(",").append(me.getY(i)).append(")");}Logger.out.println(msg.toString());}try{// 派发TouchEventif(!InputManager.getInstance().injectInputEvent(me,InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT)){returnMonkeyEvent.INJECT_FAIL;}}finally{
        me.recycle();}returnMonkeyEvent.INJECT_SUCCESS;}
Monkey主要相关类图

在这里插入图片描述


本文转载自: https://blog.csdn.net/zxc024000/article/details/136284851
版权归原作者 林多 所有, 如有侵权,请联系我们删除。

“【Android12】Monkey压力测试源码执行流程分析”的评论:

还没有评论