【Android M】Monkey命令源码及是否处于monkey测试的判断方法
来源:互联网 发布:csolfps优化大全 编辑:程序博客网 时间:2024/05/16 14:43
development/cmds/monkey/src/com/android/commands/monkey/Monkey.java
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
Monkey命令源码
1. Monkey.java
/* * Copyright 2007, The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package com.android.commands.monkey;import android.app.ActivityManagerNative;import android.app.IActivityController;import android.app.IActivityManager;import android.content.ComponentName;import android.content.Intent;import android.content.pm.IPackageManager;import android.content.pm.ResolveInfo;import android.os.Build;import android.os.Debug;import android.os.Environment;import android.os.Process;import android.os.RemoteException;import android.os.ServiceManager;import android.os.StrictMode;import android.os.SystemClock;import android.os.UserHandle;import android.view.IWindowManager;import android.view.Surface;import java.io.BufferedReader;import java.io.BufferedWriter;import java.io.File;import java.io.FileReader;import java.io.FileWriter;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.io.Writer;import java.util.ArrayList;import java.util.HashSet;import java.util.Iterator;import java.util.List;import java.util.Random;import java.util.Set;/** * Application that injects random key events and other actions into the system. */public class Monkey { /** * Monkey Debugging/Dev Support * <p> * All values should be zero when checking in. */ private final static int DEBUG_ALLOW_ANY_STARTS = 0; private final static int DEBUG_ALLOW_ANY_RESTARTS = 0; private IActivityManager mAm; private IWindowManager mWm; private IPackageManager mPm; /** Command line arguments */ private String[] mArgs; /** Current argument being parsed */ private int mNextArg; /** Data of current argument */ private String mCurArgData; /** Running in verbose output mode? 1= verbose, 2=very verbose */ private int mVerbose; /** Ignore any application crashes while running? */ private boolean mIgnoreCrashes; /** Ignore any not responding timeouts while running? */ private boolean mIgnoreTimeouts; /** Ignore security exceptions when launching activities */ /** (The activity launch still fails, but we keep pluggin' away) */ private boolean mIgnoreSecurityExceptions; /** Monitor /data/tombstones and stop the monkey if new files appear. */ private boolean mMonitorNativeCrashes; /** Ignore any native crashes while running? */ private boolean mIgnoreNativeCrashes; /** Send no events. Use with long throttle-time to watch user operations */ private boolean mSendNoEvents; /** This is set when we would like to abort the running of the monkey. */ private boolean mAbort; /** * Count each event as a cycle. Set to false for scripts so that each time * through the script increments the count. */ private boolean mCountEvents = true; /** * This is set by the ActivityController thread to request collection of ANR * trace files */ private boolean mRequestAnrTraces = false; /** * This is set by the ActivityController thread to request a * "dumpsys meminfo" */ private boolean mRequestDumpsysMemInfo = false; /** * This is set by the ActivityController thread to request a * bugreport after ANR */ private boolean mRequestAnrBugreport = false; /** * This is set by the ActivityController thread to request a * bugreport after a system watchdog report */ private boolean mRequestWatchdogBugreport = false; /** * Synchronization for the ActivityController callback to block * until we are done handling the reporting of the watchdog error. */ private boolean mWatchdogWaiting = false; /** * This is set by the ActivityController thread to request a * bugreport after java application crash */ private boolean mRequestAppCrashBugreport = false; /**Request the bugreport based on the mBugreportFrequency. */ private boolean mGetPeriodicBugreport = false; /** * Request the bugreport based on the mBugreportFrequency. */ private boolean mRequestPeriodicBugreport = false; /** Bugreport frequency. */ private long mBugreportFrequency = 10; /** Failure process name */ private String mReportProcessName; /** * This is set by the ActivityController thread to request a "procrank" */ private boolean mRequestProcRank = false; /** Kill the process after a timeout or crash. */ private boolean mKillProcessAfterError; /** Generate hprof reports before/after monkey runs */ private boolean mGenerateHprof; /** Package blacklist file. */ private String mPkgBlacklistFile; /** Package whitelist file. */ private String mPkgWhitelistFile; /** Categories we are allowed to launch **/ private ArrayList<String> mMainCategories = new ArrayList<String>(); /** Applications we can switch to. */ private ArrayList<ComponentName> mMainApps = new ArrayList<ComponentName>(); /** The delay between event inputs **/ long mThrottle = 0; /** Whether to randomize each throttle (0-mThrottle ms) inserted between events. */ boolean mRandomizeThrottle = false; /** The number of iterations **/ int mCount = 1000; /** The random number seed **/ long mSeed = 0; /** The random number generator **/ Random mRandom = null; /** Dropped-event statistics **/ long mDroppedKeyEvents = 0; long mDroppedPointerEvents = 0; long mDroppedTrackballEvents = 0; long mDroppedFlipEvents = 0; long mDroppedRotationEvents = 0; /** The delay between user actions. This is for the scripted monkey. **/ long mProfileWaitTime = 5000; /** Device idle time. This is for the scripted monkey. **/ long mDeviceSleepTime = 30000; boolean mRandomizeScript = false; boolean mScriptLog = false; /** Capture bugreprot whenever there is a crash. **/ private boolean mRequestBugreport = false; /** a filename to the setup script (if any) */ private String mSetupFileName = null; /** filenames of the script (if any) */ private ArrayList<String> mScriptFileNames = new ArrayList<String>(); /** a TCP port to listen on for remote commands. */ private int mServerPort = -1; private static final File TOMBSTONES_PATH = new File("/data/tombstones"); private HashSet<String> mTombstones = null; float[] mFactors = new float[MonkeySourceRandom.FACTORZ_COUNT]; MonkeyEventSource mEventSource; private MonkeyNetworkMonitor mNetworkMonitor = new MonkeyNetworkMonitor(); private boolean mPermissionTargetSystem = false; // information on the current activity. public static Intent currentIntent; public static String currentPackage; /** * Monitor operations happening in the system. */ private class ActivityController extends IActivityController.Stub { public boolean activityStarting(Intent intent, String pkg) { boolean allow = MonkeyUtils.getPackageFilter().checkEnteringPackage(pkg) || (DEBUG_ALLOW_ANY_STARTS != 0); if (mVerbose > 0) { // StrictMode's disk checks end up catching this on // userdebug/eng builds due to PrintStream going to a // FileOutputStream in the end (perhaps only when // redirected to a file?) So we allow disk writes // around this region for the monkey to minimize // harmless dropbox uploads from monkeys. StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites(); System.out.println(" // " + (allow ? "Allowing" : "Rejecting") + " start of " + intent + " in package " + pkg); StrictMode.setThreadPolicy(savedPolicy); } currentPackage = pkg; currentIntent = intent; return allow; } public boolean activityResuming(String pkg) { StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites(); System.out.println(" // activityResuming(" + pkg + ")"); boolean allow = MonkeyUtils.getPackageFilter().checkEnteringPackage(pkg) || (DEBUG_ALLOW_ANY_RESTARTS != 0); if (!allow) { if (mVerbose > 0) { System.out.println(" // " + (allow ? "Allowing" : "Rejecting") + " resume of package " + pkg); } } currentPackage = pkg; StrictMode.setThreadPolicy(savedPolicy); return allow; } public boolean appCrashed(String processName, int pid, String shortMsg, String longMsg, long timeMillis, String stackTrace) { StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites(); System.err.println("// CRASH: " + processName + " (pid " + pid + ")"); System.err.println("// Short Msg: " + shortMsg); System.err.println("// Long Msg: " + longMsg); System.err.println("// Build Label: " + Build.FINGERPRINT); System.err.println("// Build Changelist: " + Build.VERSION.INCREMENTAL); System.err.println("// Build Time: " + Build.TIME); System.err.println("// " + stackTrace.replace("\n", "\n// ")); StrictMode.setThreadPolicy(savedPolicy); if (!mIgnoreCrashes || mRequestBugreport) { synchronized (Monkey.this) { if (!mIgnoreCrashes) { mAbort = true; } if (mRequestBugreport){ mRequestAppCrashBugreport = true; mReportProcessName = processName; } } return !mKillProcessAfterError; } return false; } public int appEarlyNotResponding(String processName, int pid, String annotation) { return 0; } public int appNotResponding(String processName, int pid, String processStats) { StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites(); System.err.println("// NOT RESPONDING: " + processName + " (pid " + pid + ")"); System.err.println(processStats); StrictMode.setThreadPolicy(savedPolicy); synchronized (Monkey.this) { mRequestAnrTraces = true; mRequestDumpsysMemInfo = true; mRequestProcRank = true; if (mRequestBugreport){ mRequestAnrBugreport = true; mReportProcessName = processName; } } if (!mIgnoreTimeouts) { synchronized (Monkey.this) { mAbort = true; } } return (mKillProcessAfterError) ? -1 : 1; } public int systemNotResponding(String message) { StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites(); System.err.println("// WATCHDOG: " + message); StrictMode.setThreadPolicy(savedPolicy); synchronized (Monkey.this) { if (!mIgnoreCrashes) { mAbort = true; } if (mRequestBugreport) { mRequestWatchdogBugreport = true; } mWatchdogWaiting = true; } synchronized (Monkey.this) { while (mWatchdogWaiting) { try { Monkey.this.wait(); } catch (InterruptedException e) { } } } return (mKillProcessAfterError) ? -1 : 1; } } /** * Run the procrank tool to insert system status information into the debug * report. */ private void reportProcRank() { commandLineReport("procrank", "procrank"); } /** * Run "cat /data/anr/traces.txt". Wait about 5 seconds first, to let the * asynchronous report writing complete. */ private void reportAnrTraces() { try { Thread.sleep(5 * 1000); } catch (InterruptedException e) { } commandLineReport("anr traces", "cat /data/anr/traces.txt"); } /** * 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"); } /** * 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) { System.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) { if (mRequestBugreport) { logOutput.write(s); logOutput.write("\n"); } else { System.err.println(s); } } int status = p.waitFor(); System.err.println("// " + reportName + " status was " + status); if (logOutput != null) { logOutput.close(); } } catch (Exception e) { System.err.println("// Exception from " + reportName + ":"); System.err.println(e.toString()); } } // Write the numbe of iteration to the log private void writeScriptLog(int count) { // TO DO: Add the script file name to the log. try { Writer output = new BufferedWriter(new FileWriter(new File( Environment.getLegacyExternalStorageDirectory(), "scriptlog.txt"), true)); output.write("iteration: " + count + " time: " + MonkeyUtils.toCalendarTime(System.currentTimeMillis()) + "\n"); output.close(); } catch (IOException e) { System.err.println(e.toString()); } } // Write the bugreport to the sdcard. private void getBugreport(String reportName) { reportName += MonkeyUtils.toCalendarTime(System.currentTimeMillis()); String bugreportName = reportName.replaceAll("[ ,:]", "_"); commandLineReport(bugreportName + ".txt", "bugreport"); } /** * 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); } /** * 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 (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; } 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) { 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(); } // 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){ System.out.println("Print the anr report"); getBugreport("anr_" + mReportProcessName + "_"); mRequestAnrBugreport = false; } if (mRequestWatchdogBugreport) { System.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) { 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.print(mDroppedFlipEvents); System.out.print(" rotations="); System.out.println(mDroppedRotationEvents); } // 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; } } /** * Process the command-line options * * @return Returns true if options were parsed with no apparent errors. */ private boolean processOptions() { // quick (throwaway) check for unadorned command if (mArgs.length < 1) { showUsage(); return false; } 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"); } else if (opt.equals("--pct-motion")) { int i = MonkeySourceRandom.FACTOR_MOTION; mFactors[i] = -nextOptionLong("motion events percentage"); } else if (opt.equals("--pct-trackball")) { int i = MonkeySourceRandom.FACTOR_TRACKBALL; mFactors[i] = -nextOptionLong("trackball events percentage"); } else if (opt.equals("--pct-rotation")) { int i = MonkeySourceRandom.FACTOR_ROTATION; mFactors[i] = -nextOptionLong("screen rotation events percentage"); } else if (opt.equals("--pct-syskeys")) { int i = MonkeySourceRandom.FACTOR_SYSOPS; mFactors[i] = -nextOptionLong("system (key) operations percentage"); } else if (opt.equals("--pct-nav")) { int i = MonkeySourceRandom.FACTOR_NAV; mFactors[i] = -nextOptionLong("nav events percentage"); } else if (opt.equals("--pct-majornav")) { int i = MonkeySourceRandom.FACTOR_MAJORNAV; mFactors[i] = -nextOptionLong("major nav events percentage"); } else if (opt.equals("--pct-appswitch")) { int i = MonkeySourceRandom.FACTOR_APPSWITCH; mFactors[i] = -nextOptionLong("app switch events percentage"); } else if (opt.equals("--pct-flip")) { int i = MonkeySourceRandom.FACTOR_FLIP; mFactors[i] = -nextOptionLong("keyboard flip percentage"); } else if (opt.equals("--pct-anyevent")) { int i = MonkeySourceRandom.FACTOR_ANYTHING; mFactors[i] = -nextOptionLong("any events percentage"); } else if (opt.equals("--pct-pinchzoom")) { int i = MonkeySourceRandom.FACTOR_PINCHZOOM; mFactors[i] = -nextOptionLong("pinch zoom events percentage"); } else if (opt.equals("--pct-permission")) { int i = MonkeySourceRandom.FACTOR_PERMISSION; mFactors[i] = -nextOptionLong("runtime permission toggle events percentage"); } else if (opt.equals("--pkg-blacklist-file")) { mPkgBlacklistFile = nextOptionData(); } else if (opt.equals("--pkg-whitelist-file")) { mPkgWhitelistFile = nextOptionData(); } else if (opt.equals("--throttle")) { mThrottle = nextOptionLong("delay (in milliseconds) to wait between events"); } else if (opt.equals("--randomize-throttle")) { mRandomizeThrottle = true; } else if (opt.equals("--wait-dbg")) { // do nothing - it's caught at the very start of run() } else if (opt.equals("--dbg-no-events")) { mSendNoEvents = true; } else if (opt.equals("--port")) { mServerPort = (int) nextOptionLong("Server port to listen on for commands"); } else if (opt.equals("--setup")) { mSetupFileName = nextOptionData(); } else if (opt.equals("-f")) { mScriptFileNames.add(nextOptionData()); } else if (opt.equals("--profile-wait")) { mProfileWaitTime = nextOptionLong("Profile delay" + " (in milliseconds) to wait between user action"); } else if (opt.equals("--device-sleep-time")) { mDeviceSleepTime = nextOptionLong("Device sleep time" + "(in milliseconds)"); } else if (opt.equals("--randomize-script")) { mRandomizeScript = true; } else if (opt.equals("--script-log")) { mScriptLog = true; } else if (opt.equals("--bugreport")) { mRequestBugreport = true; } else if (opt.equals("--periodic-bugreport")){ mGetPeriodicBugreport = true; mBugreportFrequency = nextOptionLong("Number of iterations"); } else if (opt.equals("--permission-target-system")){ mPermissionTargetSystem = true; } else if (opt.equals("-h")) { showUsage(); return false; } else { System.err.println("** Error: Unknown option: " + opt); showUsage(); return false; } } MonkeyUtils.getPackageFilter().addValidPackages(validPackages); } catch (RuntimeException ex) { System.err.println("** Error: " + ex.toString()); showUsage(); return false; } // If a server port hasn't been specified, we need to specify // a count if (mServerPort == -1) { String countStr = nextArg(); if (countStr == null) { System.err.println("** Error: Count not specified"); showUsage(); return false; } try { mCount = Integer.parseInt(countStr); } catch (NumberFormatException e) { System.err.println("** Error: Count is not a number"); showUsage(); return false; } } return true; } /** * Load a list of package names from a file. * * @param fileName The file name, with package names separated by new line. * @param list The destination list. * @return Returns false if any error occurs. */ private static boolean loadPackageListFromFile(String fileName, Set<String> list) { BufferedReader reader = null; try { reader = new BufferedReader(new FileReader(fileName)); String s; while ((s = reader.readLine()) != null) { s = s.trim(); if ((s.length() > 0) && (!s.startsWith("#"))) { list.add(s); } } } catch (IOException ioe) { System.err.println(ioe); return false; } finally { if (reader != null) { try { reader.close(); } catch (IOException ioe) { System.err.println(ioe); } } } return true; } /** * Load package blacklist or whitelist (if specified). * * @return Returns false if any error occurs. */ private boolean loadPackageLists() { if (((mPkgWhitelistFile != null) || (MonkeyUtils.getPackageFilter().hasValidPackages())) && (mPkgBlacklistFile != null)) { System.err.println("** Error: you can not specify a package blacklist " + "together with a whitelist or individual packages (via -p)."); return false; } Set<String> validPackages = new HashSet<>(); if ((mPkgWhitelistFile != null) && (!loadPackageListFromFile(mPkgWhitelistFile, validPackages))) { return false; } MonkeyUtils.getPackageFilter().addValidPackages(validPackages); Set<String> invalidPackages = new HashSet<>(); if ((mPkgBlacklistFile != null) && (!loadPackageListFromFile(mPkgBlacklistFile, invalidPackages))) { return false; } MonkeyUtils.getPackageFilter().addInvalidPackages(invalidPackages); return true; } /** * Check for any internal configuration (primarily build-time) errors. * * @return Returns true if ready to rock. */ private boolean checkInternalConfiguration() { return true; } /** * Attach to the required system interfaces. * * @return Returns true if all system interfaces were available. */ 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; } /** * 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 */ private boolean getMainApps() { try { final int N = mMainCategories.size(); for (int i = 0; i < N; i++) { Intent intent = new Intent(Intent.ACTION_MAIN); String category = mMainCategories.get(i); if (category.length() > 0) { intent.addCategory(category); } List<ResolveInfo> mainApps = mPm.queryIntentActivities(intent, null, 0, UserHandle.myUserId()); if (mainApps == null || mainApps.size() == 0) { System.err.println("// Warning: no activities found for category " + category); continue; } if (mVerbose >= 2) { // very verbose System.out.println("// Selecting main activities from category " + category); } final int NA = 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)) { if (mVerbose >= 2) { // very verbose System.out.println("// + Using main activity " + r.activityInfo.name + " (from package " + packageName + ")"); } mMainApps.add(new ComponentName(packageName, r.activityInfo.name)); } else { if (mVerbose >= 3) { // very very verbose System.out.println("// - NOT USING main activity " + r.activityInfo.name + " (from package " + packageName + ")"); } } } } } catch (RemoteException e) { System.err.println("** Failed talking with package manager!"); return false; } if (mMainApps.size() == 0) { System.out.println("** No activities found to run, monkey aborted."); return false; } return true; } /** * 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) { synchronized (this) { if (mRequestProcRank) { reportProcRank(); mRequestProcRank = false; } if (mRequestAnrTraces) { mRequestAnrTraces = false; shouldReportAnrTraces = true; } if (mRequestAnrBugreport){ getBugreport("anr_" + mReportProcessName + "_"); mRequestAnrBugreport = false; } if (mRequestWatchdogBugreport) { System.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) if (checkNativeCrashes() && (eventCounter > 0)) { System.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 if (shouldReportAnrTraces) { shouldReportAnrTraces = false; reportAnrTraces(); } if (shouldReportDumpsysMemInfo) { shouldReportDumpsysMemInfo = false; reportDumpsysMemInfo(); } if (shouldAbort) { shouldAbort = false; System.out.println("** Monkey aborted due to error."); System.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(); System.out.println(" //[calendar_time:" + calendarTime + " system_uptime:" + systemUpTime + "]"); System.out.println(" // Sending event #" + eventCounter); } MonkeyEvent ev = mEventSource.getNextEvent(); if (ev != null) { int injectCode = ev.injectEvent(mWm, mAm, mVerbose); if (injectCode == MonkeyEvent.INJECT_FAIL) { System.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; System.err.println("** Error: RemoteException while injecting event."); } else if (injectCode == MonkeyEvent.INJECT_ERROR_SECURITY_EXCEPTION) { systemCrashed = !mIgnoreSecurityExceptions; if (systemCrashed) { System.err.println("** Error: SecurityException while injecting event."); } } // Don't count throttling as an event. if (!(ev instanceof MonkeyThrottleEvent)) { eventCounter++; if (mCountEvents) { cycleCounter++; } } } else { 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; } } } System.out.println("Events injected: " + eventCounter); return eventCounter; } /** * Send SIGNAL_USR1 to all processes. This will generate large (5mb) * profiling reports in data/misc, so use with care. */ private void signalPersistentProcesses() { try { mAm.signalPersistentProcesses(Process.SIGNAL_USR1); synchronized (this) { wait(2000); } } catch (RemoteException e) { System.err.println("** Failed talking with activity manager!"); } catch (InterruptedException e) { } } /** * Watch for appearance of new tombstone files, which indicate native * crashes. * * @return Returns true if new files have appeared in the list */ private boolean checkNativeCrashes() { String[] tombstones = TOMBSTONES_PATH.list(); // shortcut path for usually empty directory, so we don't waste even // more objects if ((tombstones == null) || (tombstones.length == 0)) { mTombstones = null; return false; } // use set logic to look for new files HashSet<String> newStones = new HashSet<String>(); for (String x : tombstones) { newStones.add(x); } boolean result = (mTombstones == null) || !mTombstones.containsAll(newStones); // keep the new list for the next time mTombstones = newStones; return result; } /** * Return the next command line option. This has a number of special cases * which closely, but not exactly, follow the POSIX command line options * patterns: * * <pre> * -- means to stop processing additional options * -z means option z * -z ARGS means option z with (non-optional) arguments ARGS * -zARGS means option z with (optional) arguments ARGS * --zz means option zz * --zz ARGS means option zz with (non-optional) arguments ARGS * </pre> * * Note that you cannot combine single letter options; -abc != -a -b -c * * @return Returns the option string, or null if there are no more options. */ 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; } /** * Return the next data associated with the current option. * * @return Returns the data string, or null of there are no more arguments. */ private String nextOptionData() { if (mCurArgData != null) { return mCurArgData; } if (mNextArg >= mArgs.length) { return null; } String data = mArgs[mNextArg]; mNextArg++; return data; } /** * Returns a long converted from the next data argument, with error handling * if not available. * * @param opt The name of the option. * @return Returns a long converted from the argument. */ private long nextOptionLong(final String opt) { long result; try { result = Long.parseLong(nextOptionData()); } catch (NumberFormatException e) { System.err.println("** Error: " + opt + " is not a number"); throw e; } return result; } /** * Return the next argument on the command line. * * @return Returns the argument string, or null if we have reached the end. */ private String nextArg() { if (mNextArg >= mArgs.length) { return null; } String arg = mArgs[mNextArg]; mNextArg++; return arg; } /** * Print how to use this command. */ private void showUsage() { StringBuffer usage = new StringBuffer(); 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(" [--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"); System.err.println(usage.toString()); }}
源码部分说明
A.从run方法开始正式运行
private int run(String[] args) {
......
//mMainCategories列表装载
// now set up additional data in preparation for launch
if (mMainCategories.size() == 0) { // ArrayList<String> mMainCategories
mMainCategories.add(Intent.CATEGORY_LAUNCHER);
mMainCategories.add(Intent.CATEGORY_MONKEY);
}
......
//打印mMainCategories列表
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());
}
}
}
......
}
B.获取系统接口
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;
}
C. 获取待测试的Activity列表
private boolean getMainApps() {
try {
final int N = mMainCategories.size();
for (int i = 0; i < N; i++) {
Intent intent = new Intent(Intent.ACTION_MAIN);
String category = mMainCategories.get(i);
if (category.length() > 0) {
intent.addCategory(category);
}
List<ResolveInfo> mainApps = mPm.queryIntentActivities(intent, null, 0,
UserHandle.myUserId());
if (mainApps == null || mainApps.size() == 0) {
System.err.println("// Warning: no activities found for category " + category);
continue;
}
if (mVerbose >= 2) { // very verbose
System.out.println("// Selecting main activities from category " + category);
}
final int NA = 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)) {
if (mVerbose >= 2) { // very verbose
System.out.println("// + Using main activity " + r.activityInfo.name
+ " (from package " + packageName + ")");
}
mMainApps.add(new ComponentName(packageName, r.activityInfo.name));
} else {
if (mVerbose >= 3) { // very very verbose
System.out.println("// - NOT USING main activity "
+ r.activityInfo.name + " (from package " + packageName + ")");
}
}
}
}
} catch (RemoteException e) {
System.err.println("** Failed talking with package manager!");
return false;
}
if (mMainApps.size() == 0) {
System.out.println("** No activities found to run, monkey aborted.");
return false;
}
return true;
}
2. ActivityManagerService.java
@Override
public void setUserIsMonkey(boolean userIsMonkey) {
synchronized (this) {
synchronized (mPidsSelfLocked) {
final int callingPid = Binder.getCallingPid();
ProcessRecord precessRecord = mPidsSelfLocked.get(callingPid);
if (precessRecord == null) {
throw new SecurityException("Unknown process: " + callingPid);
}
if (precessRecord.instrumentationUiAutomationConnection == null) {
throw new SecurityException("Only an instrumentation process "
+ "with a UiAutomation can call setUserIsMonkey");
}
}
mUserIsMonkey = userIsMonkey;
}
}
@Override
public boolean isUserAMonkey() {
synchronized (this) {
// If there is a controller also implies the user is a monkey.
return (mUserIsMonkey || mController != null);
}
}
判断当前是否处于monkey测试
使用这个方法:ActivityManager.isUserAMonkey()
运行monkey测试
adb shell回车,monkey --ignore-crashes --ignore-timeouts --ignore-native-crashes --monitor-native-crashes --throttle 1000 -v -v -v -s 800 380000 2>/sdcard/error.txt 1>/sdcard/info.txt
Log
:Monkey: seed=800 count=380000:IncludeCategory: android.intent.category.LAUNCHER:IncludeCategory: android.intent.category.MONKEY// Selecting main activities from category android.intent.category.LAUNCHER// + Using main activity com.android.browser.BrowserActivity (from package com.android.browser)// + Using main activity com.android.calendar.AllInOneActivity (from package com.android.calendar)// + Using main activity com.android.camera.CameraLauncher (from package com.android.camera3)// + Using main activity com.android.contacts.activities.PeopleActivity (from package com.android.contacts)// + Using main activity com.android.dialer.DialtactsActivity (from package com.android.contacts)// + Using main activity com.android.deskclock.DeskClock (from package com.android.deskclock)// + Using main activity com.android.email.activity.Welcome (from package com.android.email)// + Using main activity com.android.mms.ui.ConversationList (from package com.android.mms)// + Using main activity com.android.music.MusicBrowserActivity (from package com.android.music)// + Using main activity com.android.music.VideoBrowserActivity (from package com.android.music)// + Using main activity com.android.settings.Settings (from package com.android.settings)// + Using main activity com.cmdc.phonekeeper.PhoneKeeperMainActivity (from package com.cmdc.phonekeeper)// + Using main activity com.cyanogenmod.filemanager.activities.NavigationActivity (from package com.cyanogenmod.filemanager)// + Using main activity com.android.gallery3d.app.GalleryActivity (from package org.codeaurora.gallery)// + Using main activity com.android.calculator2.Calculator (from package com.android.calculator2)// + Using main activity com.android.documentsui.LauncherActivity (from package com.android.documentsui)// + Using main activity com.android.mplayer.VideoAlbumGrid (from package com.android.mplayer)// + Using main activity com.android.quicksearchbox.SearchActivity (from package com.android.quicksearchbox)// + Using main activity com.caf.fmradio.FMRadio (from package com.caf.fmradio)// + Using main activity com.hmct.FileManager.Activity.ClassifyFileManager (from package com.hmct.FileManager.Activity)// + Using main activity com.qti.csm.EnterSet (from package com.qti.csm)// + Using main activity com.qualcomm.qti.carrierconfigure.ConfigurationActivity (from package com.qualcomm.qti.carrierconfigure)// + Using main activity com.qualcomm.qti.logkit.cActivity (from package com.qualcomm.qti.logkit)// + Using main activity com.qualcomm.qti.securemsm.mdtp.MdtpDemo.MainUserActivity (from package com.qualcomm.qti.securemsm.mdtp.MdtpDemo)// + Using main activity com.qualcomm.qti.sensors.ui.qsensortest.TabControl (from package com.qualcomm.qti.sensors.qsensortest)// + Using main activity com.quicinc.cne.settings.CneSettings (from package com.quicinc.cne.settings)// + Using main activity com.quicinc.wipoweragent.WipowerAgentActivity (from package com.quicinc.wipoweragent)// + Using main activity com.wingtech.countrycodeswitch.CountryCodeSwitchActivity (from package com.wingtech.countrycodeswitch)// + Using main activity com.wingtech.note.list.NotesListActivity (from package com.wingtech.note)// + Using main activity com.xxx.userservice.MainActivity (from package com.xxx.userservice)// + Using main activity org.codeaurora.bluetooth.hidtestapp.HidTestApp (from package org.codeaurora.bluetooth.hidtestapp)// + Using main activity com.android.anim.RecentsActivity (from package com.android.anim)// + Using main activity com.example.android.multiwindow.LaunchingAdjacentActivity (from package com.example.android.multiwindow)// + Using main activity com.example.navigationdrawer.MainActivity (from package com.example.navigationdrawer)// Selecting main activities from category android.intent.category.MONKEY// + Using main activity com.android.launcher3.Launcher (from package com.android.launcher3)// + Using main activity com.android.settings.Settings$RunningServicesActivity (from package com.android.settings)// + Using main activity com.android.settings.Settings$StorageUseActivity (from package com.android.settings)// Seeded: 800// Event percentages:// 0: 15.0%// 1: 10.0%// 2: 2.0%// 3: 15.0%// 4: -0.0%// 5: -0.0%// 6: 25.0%// 7: 15.0%// 8: 2.0%// 9: 2.0%// 10: 1.0%// 11: 13.0%:Switch: #Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;launchFlags=0x10200000;component=com.android.calendar/.AllInOneActivity;end // Allowing start of Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.android.calendar/.AllInOneActivity } in package com.android.calendarSleeping for 1000 milliseconds:Sending Trackball (ACTION_MOVE): 0:(3.0,2.0):Sending Trackball (ACTION_MOVE): 0:(0.0,-3.0):Sending Trackball (ACTION_MOVE): 0:(1.0,3.0):Sending Trackball (ACTION_MOVE): 0:(0.0,-3.0):Sending Trackball (ACTION_MOVE): 0:(-2.0,-5.0):Sending Trackball (ACTION_MOVE): 0:(-4.0,-3.0):Sending Trackball (ACTION_MOVE): 0:(-1.0,2.0):Sending Trackball (ACTION_MOVE): 0:(-5.0,-4.0):Sending Trackball (ACTION_MOVE): 0:(-4.0,-4.0):Sending Trackball (ACTION_MOVE): 0:(1.0,-1.0):Sending Trackball (ACTION_MOVE): 0:(-1.0,-5.0):Sending Trackball (ACTION_MOVE): 0:(-1.0,0.0):Sending Trackball (ACTION_MOVE): 0:(-3.0,-3.0):Sending Trackball (ACTION_MOVE): 0:(1.0,-4.0):Sending Trackball (ACTION_MOVE): 0:(0.0,-3.0):Sending Trackball (ACTION_MOVE): 0:(-5.0,1.0):Sending Trackball (ACTION_MOVE): 0:(-3.0,0.0):Sending Trackball (ACTION_MOVE): 0:(-2.0,4.0):Sending Trackball (ACTION_MOVE): 0:(3.0,-4.0):Sending Trackball (ACTION_MOVE): 0:(0.0,2.0):Sending Key (ACTION_DOWN): 25 // KEYCODE_VOLUME_DOWN:Sending Key (ACTION_UP): 25 // KEYCODE_VOLUME_DOWNSleeping for 1000 milliseconds:Sending Touch (ACTION_DOWN): 0:(345.0,830.0):Sending Touch (ACTION_UP): 0:(334.6787,838.3302)Sleeping for 1000 milliseconds:Sending Key (ACTION_DOWN): 82 // KEYCODE_MENU:Sending Key (ACTION_UP): 82 // KEYCODE_MENUSleeping for 1000 milliseconds:Sending Key (ACTION_DOWN): 82 // KEYCODE_MENU:Sending Key (ACTION_UP): 82 // KEYCODE_MENUSleeping for 1000 milliseconds:Sending Touch (ACTION_DOWN): 0:(41.0,133.0):Sending Touch (ACTION_MOVE): 0:(49.90042,140.75574):Sending Touch (ACTION_MOVE): 0:(52.748535,158.99876):Sending Touch (ACTION_MOVE): 0:(56.985256,172.18214):Sending Touch (ACTION_UP): 0:(60.56301,173.46811)Sleeping for 1000 milliseconds
停止monke测试
$adb shell kill -9 `adb shell ps | grep "com.android.commands.monkey" | awk '{print $2}'`
原文地址:http://blog.csdn.net/yelangjueqi/article/details/58594383
- 【Android M】Monkey命令源码及是否处于monkey测试的判断方法
- Android命令monkey测试
- Android monkey 测试命令
- Android命令Monkey测试
- Android的monkey测试
- Android的monkey测试
- Android的monkey测试
- Android的monkey测试
- Android的monkey测试
- Android的monkey测试
- Android的monkey测试
- android压力测试命令monkey
- android压力测试命令Monkey
- Android自动测试命令Monkey
- Android命令Monkey压力测试
- android monkey压力测试 命令
- android压力测试命令monkey
- Monkey测试命令及其说明附中止monkey测试方法
- Hibernate (二) 分页查询
- app接口设计之signature签名的php实现
- 自定义播放操作(快进,快退,声音,亮度调节)
- 数据库系统概论第五版学习笔记
- iOS集成支付宝遇到的问题一:Cannot find interface declarationfor'NSObject', superclass of'Base64'
- 【Android M】Monkey命令源码及是否处于monkey测试的判断方法
- 缓存穿透,缓存击穿,缓存雪崩解决方案分析
- test
- cmd命令,最新的装逼利器
- UVA-11475-Extend to Palindrome((扩展)kmp)
- Centos7下配置系统日志备份策略
- SUSE 11 操作系统安装
- nyoj 49 开心的小明(01背包)
- $.extend(true,o,d)与$.extend(o,d) 的区别