◆版權聲明:本文出自胖喵~的博客,轉載必須注明出處。
轉載請注明出處: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中的內容基本就是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這個組的成員運行。