中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Robotium源碼分析之Instrumentation進階

發布時間:2020-07-23 19:44:16 來源:網絡 閱讀:672 作者:zhukev 欄目:移動開發

在分析Robotium的運行原理之前,我們有必要先搞清楚Instrumentation的一些相關知識點,因為Robotium就是基于Instrumentation而開發出來的一套自動化測試框架。鑒于之前本人已經轉載和編寫了Instrumentation的一些文章,所以建議大家如果沒有看過的還是翻看下先對Instrumentation有個基本的理解。然后帶著疑問再來看這篇文章看是否能幫上自己。

既然是分析Instrumentation,那么我們必須要先看下Instrumentation 這個類的類圖,直接網上截獲,就不花時間另外去畫了,但請注意網上該圖是比較老的,一些新的注入事件的方法是沒有加進去的,注意紅色部分:

Robotium源碼分析之Instrumentation進階

開始分析之前我們要搞清楚Instrumentation的幾點

1. Instrumentation測試腳本和目標app在同一個進程中運行

如官方描述的“instrumentation can load both a test package and the application under test into the same process. Since the application components and their tests are in the same process, the tests can invoke methods in the components, and modify and examine fields in the components.“翻譯過來就是“Instrumentation可以把測試包和目標測試應用加載到同一個進程中運行。既然各個控件和測試代碼都運行在同一個進程中了,測試代碼當然就可以調用這些控件的方法了,同時修改和驗證這些控件的一些項也不在話下了。
這個從ddms的線程輸出可以證明,通過"am instrumentation -w com.example.android.notepad/android.test.InstrumentationTestRunner" 運行的輸出如下:
Robotium源碼分析之Instrumentation進階

我們對比不是通過"am instrumentation"命令啟動的notepad應用的線程輸出:
Robotium源碼分析之Instrumentation進階
可以看到是沒有“Instr:android.test.InstrumentationTestRunner這個線程的。這個線程和Instrumentation又是什么關系呢?
/*     */ public class InstrumentationTestRunner /*     */   extends Instrumentation /*     */   implements TestSuiteProvider /*     */ {                 ...             }
從它的類定義我們可以看到它是從我們的Instrumentation類繼承下來的。其實從它的名字我們就大概可以想像到它是扮演什么角色的,參照我們之前對UiAutomator的源碼分析《UIAutomator源碼分析之啟動和運行》,InstrumentationTestRunner扮演的角色類似于當中的UiAutomatorTestRunner類,都是通過解析獲取和建立目標測試用例和測試集然后知道測試的運行。
大家先看下它的onCreate方法的官方定義:“Called when the instrumentation is starting, before any application code has been loaded.“。翻譯過來就是”這個方法是在Instrumentation啟動過程中,在目標測試代碼被裝載進內存運行之前進行調用
我們可以看下這個方法的實現,看它是如何啟動剛才的那個InstrumetnationTestRunner線程的:
/*     */   public void onCreate(Bundle arguments) /*     */   { /* 303 */     super.onCreate(arguments);                    ... /* 343 */     TestSuiteBuilder testSuiteBuilder = new TestSuiteBuilder(getClass().getName(), getTargetContext().getClassLoader()); /*     */      /*     */  /* 346 */     if (testSizePredicate != null) { /* 347 */       testSuiteBuilder.addRequirements(new Predicate[] { testSizePredicate }); /*     */     } /* 349 */     if (testAnnotationPredicate != null) { /* 350 */       testSuiteBuilder.addRequirements(new Predicate[] { testAnnotationPredicate }); /*     */     } /* 352 */     if (testNotAnnotationPredicate != null) { /* 353 */       testSuiteBuilder.addRequirements(new Predicate[] { testNotAnnotationPredicate }); /*     */     } /*     */      /* 356 */     if (testClassesArg == null) {                 ... /*     */     } else { /* 370 */       parseTestClasses(testClassesArg, testSuiteBuilder); /*     */     } /*     */      /* 373 */     testSuiteBuilder.addRequirements(getBuilderRequirements()); /*     */      /* 375 */     this.mTestRunner = getAndroidTestRunner(); /* 376 */     this.mTestRunner.setContext(getTargetContext()); /* 377 */     this.mTestRunner.setInstrumentation(this); /* 378 */     this.mTestRunner.setSkipExecution(logOnly); /* 379 */     this.mTestRunner.setTest(testSuiteBuilder.build()); /* 380 */     this.mTestCount = this.mTestRunner.getTestCases().size(); /* 381 */     if (this.mSuiteAssignmentMode) { /* 382 */       this.mTestRunner.addTestListener(new SuiteAssignmentPrinter()); /*     */     } else { /* 384 */       WatcherResultPrinter resultPrinter = new WatcherResultPrinter(this.mTestCount); /* 385 */       this.mTestRunner.addTestListener(new TestPrinter("TestRunner", false)); /* 386 */       this.mTestRunner.addTestListener(resultPrinter); /* 387 */       this.mTestRunner.setPerformanceResultsWriter(resultPrinter); /*     */     } /* 389 */     start(); /*     */   }
從中我們可以看到這個方法開始就是如上面所說的類似UiAutomatorTestRunner一樣去獲取解析對應測試包里面的測試集和測試用例,這個在這個章節不是重點,重點是最后面的start()這個方法的調用。這個方法最終調用的是父類Instrumentation的start()方法,我們看下這個方法的官方解析"Create and start a new thread in which to run instrumentation.“翻譯過來就是”創建一個新的運行Instrumentation(測試用例)的線程":
/*      */   public void start() /*      */   { /*  122 */     if (this.mRunner != null) { /*  123 */       throw new RuntimeException("Instrumentation already started"); /*      */     } /*  125 */     this.mRunner = new InstrumentationThread("Instr: " + getClass().getName()); /*  126 */     this.mRunner.start(); /*      */   }
在第125行我們很明顯知道新的線程名就叫做"Instr:android.test.InstrumentationTestRunner",因為這個方法是從子類android.test.InstrumentationTestRunner中傳進來的,所以getClass().getName()方法獲得的就是子類的名字。
我們繼續看這個線程是如何建立起來的,繼續進入InstrumentationThread這個Instrumentation的內部類:
/*      */   private final class InstrumentationThread /*      */     extends Thread { /* 1689 */     public InstrumentationThread(String name) { super(); } /*      */      /*      */     public void run() { /*      */       try { /* 1693 */         Process.setThreadPriority(-8); /*      */       } catch (RuntimeException e) { /* 1695 */         Log.w("Instrumentation", "Exception setting priority of instrumentation thread " + Process.myTid(), e); /*      */       } /*      */        /* 1698 */       if (Instrumentation.this.mAutomaticPerformanceSnapshots) { /* 1699 */         Instrumentation.this.startPerformanceSnapshot(); /*      */       } /* 1701 */       Instrumentation.this.onStart(); /*      */     } /*      */   }
從最前面的定義可看到該類是繼承與Thread對象的,所以后面提供個重寫的run方法來代表該線程的運行入口完成一個Thread線程的完整實現。
1701行,Instrumentation.this值得就是子類InstrumentationTestRunner的實例,那么它的onStart方法又做了什么事情呢?
    /**      * Initialize the current thread as a looper.      * <p/>      * Exposed for unit testing.      */     void prepareLooper() {         Looper.prepare();     }      @Override     public void onStart() {         prepareLooper();          if (mJustCount) {             mResults.putString(Instrumentation.REPORT_KEY_IDENTIFIER, REPORT_VALUE_ID);             mResults.putInt(REPORT_KEY_NUM_TOTAL, mTestCount);             finish(Activity.RESULT_OK, mResults);         } else {             if (mDebug) {                 Debug.waitForDebugger();             }              ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();             PrintStream writer = new PrintStream(byteArrayOutputStream);             try {                 StringResultPrinter resultPrinter = new StringResultPrinter(writer);                  mTestRunner.addTestListener(resultPrinter);                  long startTime = System.currentTimeMillis();                 mTestRunner.runTest();                 long runTime = System.currentTimeMillis() - startTime;                  resultPrinter.printResult(mTestRunner.getTestResult(), runTime);             } catch (Throwable t) {                 // catch all exceptions so a more verbose error message can be outputted                 writer.println(String.format("Test run aborted due to unexpected exception: %s",                                 t.getMessage()));                 t.printStackTrace(writer);             } finally {                 mResults.putString(Instrumentation.REPORT_KEY_STREAMRESULT,                         String.format("\nTest results for %s=%s",                         mTestRunner.getTestClassName(),                         byteArrayOutputStream.toString()));                  if (mCoverage) {                     generateCoverageReport();                 }                 writer.close();                  finish(Activity.RESULT_OK, mResults);             }         }     }
該方法一開始就為InstrumentationTestRunner線程建立一個looper消息隊列,至于looper是怎么回事,大家如果不清的請查看網絡的解析。Looper是用于給一個線程添加一個消息隊列(MessageQueue),并且循環等待,當有消息時會喚起線程來處理消息的一個工具,直到線程結束為止。通常情況下不會用到Looper,因為對于Activity,Service等系統組件,Frameworks已經為我們初始化好了線程(俗稱的UI線程或主線程),在其內含有一個Looper,和由Looper創建的消息隊列,所以主線程會一直運行,處理用戶事件,直到某些事件(BACK)退出。
如果,我們需要新建一個線程,并且這個線程要能夠循環處理其他線程發來的消息事件,或者需要長期與其他線程進行復雜的交互,這時就需要用到Looper來給線程建立消息隊列。
建立好消息隊列后往下的重點就是調用AndroidTestRunner的runTest方法開始測試用例的執行了:
    public void runTest(TestResult testResult) {         mTestResult = testResult;          for (TestListener testListener : mTestListeners) {             mTestResult.addListener(testListener);         }          Context testContext = mInstrumentation == null ? mContext : mInstrumentation.getContext();         for (TestCase testCase : mTestCases) {             setContextIfAndroidTestCase(testCase, mContext, testContext);             setInstrumentationIfInstrumentationTestCase(testCase, mInstrumentation);             setPerformanceWriterIfPerformanceCollectorTestCase(testCase, mPerfWriter);             testCase.run(mTestResult);         }     }
大概做法就是對所有收集到的測試集進行一個for循環然后取出每個測試用例在junit.Framework.Testcase環境下進行運行了。這里就不往下研究junit框架是怎么回事了。
總結以上的分析,android.test.InstrumentationTestRunner會在目標應用代碼運行之前調用onCreate方法來建立一個新的線程并準備后消息隊列,然后會開始基于Instrumentation的測試集的測試。

2. runOnUiThread和runOnMainSync的區別

既然app的主線程和instrumetnaiton測試用例腳本線程是運行在同一個進程中的,我們腦袋中應該就會立刻閃現以下有關UiThread和子線程的兩點限制:
  • 子線程是可以直接獲取主線程UiThread的控件以及內容的
  • 子線程是不能直接操作主線程UiThread的控件以及內容的
根據網上的文章《Android中UI線程與后臺線程交互設計的5種方法》的描述,android提供了以下幾種方法,用于實現后臺線程與UI線程的交互。
  • 1、handler
  • 2、Activity.runOnUIThread(Runnable)
  • 3、View.Post(Runnable)
  • 4、View.PostDelayed(Runnabe,long)
  • 5、AsyncTask
而Instrumentation類又提供了一個runOnMainSync的方法,這和上面的Activity提供的runOnUiThread方法從名字上比較接近,那么我們這里就對比下這兩種方法有什么區別。
我們先看下Activity的runOnUIThread方法:
    /**      * Runs the specified action on the UI thread. If the current thread is the UI      * thread, then the action is executed immediately. If the current thread is      * not the UI thread, the action is posted to the event queue of the UI thread.      *      * @param action the action to run on the UI thread      */     public final void runOnUiThread(Runnable action) {         if (Thread.currentThread() != mUiThread) {             mHandler.post(action);         } else {             action.run();         }     }
其代碼的功能和對應的描述一致:
  • 如果這個方法不是在運行Activity的主線程UiThread上被調用的,也就是在子線程上調用的,那么把action提交到主線程的Main Looper消息隊列中排隊然后返回
  • 如果這個方法是在運行Activity的主線程UiThread上被調用的,那么不需要進入Main Looper隊列排隊,直接調用執行
你看,這多偏心,正室就是權力大,二房就是差點,特別是二房比較多的時候,你慢慢排吧。不過我們的Main Looper還算好了,在我們現實中更多是反過來了,搞了個二房三房后把正室給休掉了。
這里就不往外扯了,這里我們主要關心子線程的情況,既然是把action提交到Main Looper進行排隊,那么必然是異步的了,否則消息隊列的存在就沒有意思了,所以這里我們要在腦袋總記得runOnUiThread是異步的。
我們再看Instrumentation的runOnMainSync方法:
/*      */   public void runOnMainSync(Runnable runner) /*      */   { /*  344 */     validateNotAppThread(); /*  345 */     SyncRunnable sr = new SyncRunnable(runner); /*  346 */     this.mThread.getHandler().post(sr); /*  347 */     sr.waitForComplete(); /*      */   }
這里也是從再從主線程獲得Main Looper的Handler后往Main Looper消息隊列中提交action,但人家提交完之后還會等待該action線程的執行完畢才會退出這個函數,所以兩個方法的區別就是:Activity的runOnUiThread是異步執行的,Instrumentation的runOnMainSync是同步執行的。runOnMainSync又是怎么實現這一點的呢?這個我們就要看Instrumetnation的內部類SyncRunnable了:
/*      */   private static final class SyncRunnable implements Runnable { /*      */     private final Runnable mTarget; /*      */     private boolean mComplete; /*      */      /* 1715 */     public SyncRunnable(Runnable target) { this.mTarget = target; } /*      */      /*      */     public void run() /*      */     { /* 1719 */       this.mTarget.run(); /* 1720 */       synchronized (this) { /* 1721 */         this.mComplete = true; /* 1722 */         notifyAll(); /*      */       } /*      */     } /*      */      /*      */     public void waitForComplete() { /* 1727 */       synchronized (this) { /* 1728 */         while (!this.mComplete) { /*      */           try { /* 1730 */             wait(); /*      */           } /*      */           catch (InterruptedException e) {} /*      */         } /*      */       } /*      */     } /*      */   }
它也是從runnable線程類繼承下來的。在run方法的1720到1722行我們可以看到,該運行在Main UiThread的方法在跑完后會把Instrumentation實例的mComplete變量設置成true,而runOnMainSync最后調用的運行在子線程中的waitForComplete方法會一直等待這個mComplete變量變成true才會返回,也就是說一直等待主線程的調用完成才會返回,那么到了這里就很清楚runOnMainSync是如何通過SyncRunnable這個內部類實現同步的了。
這里還有必要提一提的是runOnMainSync方法調用的第一個函數validateNotAppThread,其實它做的事情就是去查看下當前線程的Looper是不是主線程的Main Looper,如果是的話就直接拋出異常,代表runOnMainSynce這個方法是不應該在主線程調用的;如果不是的話就什么都不干繼續往下跑。
/*      */   private final void validateNotAppThread() /*      */   { /* 1650 */     if (Looper.myLooper() == Looper.getMainLooper()) { /* 1651 */       throw new RuntimeException("This method can not be called from the main application thread"); /*      */     } /*      */   }

3. Instrumentation注入事件統一方式-- InputManager

從開始的類圖,我們可以看到Instrumentation事件相關的方法如下:

Method

Description

Comment

Key Events

sendKeySync

發送一個鍵盤事件,注意同一時間只有一個action,或者是按下,或者是彈起,所有下面其他key相關的事件注入都是以這個方法為基礎的


sendKeyDownUpSync

基于sendKeySync發送一個按鍵的按下和彈起兩個事件


sendCharacterSync

發送鍵盤上的一個字符,完整的過程包括一個按下和彈起事件


sendStringSync

往應用發送一串字符串


Tackball Event

sendTrackballEventSync

發送軌跡球事件。個人沒有用過,應該是像黑莓的那種軌跡球吧


Pointer Event

sendPointerSync

發送點擊事件


那么我們根據不同的事件類型看下它們注入事件的方式是如何的,我們先看按鍵事件類型,因為其他的按鍵事件都是最終調用sendKeySync,所以我們就看這方法就可以了:
    /**      * Send a key event to the currently focused window/view and wait for it to      * be processed.  Finished at some point after the recipient has returned      * from its event processing, though it may <em>not</em> have completely      * finished reacting from the event -- for example, if it needs to update      * its display as a result, it may still be in the process of doing that.      *       * @param event The event to send to the current focus.      */     public void sendKeySync(KeyEvent event) {         validateNotAppThread();          long downTime = event.getDownTime();         long eventTime = event.getEventTime();         int action = event.getAction();         int code = event.getKeyCode();         int repeatCount = event.getRepeatCount();         int metaState = event.getMetaState();         int deviceId = event.getDeviceId();         int scancode = event.getScanCode();         int source = event.getSource();         int flags = event.getFlags();         if (source == InputDevice.SOURCE_UNKNOWN) {             source = InputDevice.SOURCE_KEYBOARD;         }         if (eventTime == 0) {             eventTime = SystemClock.uptimeMillis();         }         if (downTime == 0) {             downTime = eventTime;         }         KeyEvent newEvent = new KeyEvent(downTime, eventTime, action, code, repeatCount, metaState,                 deviceId, scancode, flags | KeyEvent.FLAG_FROM_SYSTEM, source);         InputManager.getInstance().injectInputEvent(newEvent,                 InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH);     }
這個就很明顯了,用的就是InputManager的事件注入方式,如果大家不清楚的請查看本人之前翻譯的《Monkey源碼分析番外篇之Android注入事件的三種方法比較》。
下一個我們就看下軌跡球相關事件注入,用到的同樣是InputManager的事件注入方式:
    /**      * Dispatch a trackball event. Finished at some point after the recipient has      * returned from its event processing, though it may <em>not</em> have      * completely finished reacting from the event -- for example, if it needs      * to update its display as a result, it may still be in the process of      * doing that.      *       * @param event A motion event describing the trackball action.  (As noted in       * {@link MotionEvent#obtain(long, long, int, float, float, int)}, be sure to use       * {@link SystemClock#uptimeMillis()} as the timebase.      */     public void sendTrackballEventSync(MotionEvent event) {         validateNotAppThread();         if ((event.getSource() & InputDevice.SOURCE_CLASS_TRACKBALL) == 0) {             event.setSource(InputDevice.SOURCE_TRACKBALL);         }         InputManager.getInstance().injectInputEvent(event,                 InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH);     }
最后我們看下點擊事件,同樣,使用的也是無一例外的InputManager的事件注入方式:
    /**      * Dispatch a pointer event. Finished at some point after the recipient has      * returned from its event processing, though it may <em>not</em> have      * completely finished reacting from the event -- for example, if it needs      * to update its display as a result, it may still be in the process of      * doing that.      *       * @param event A motion event describing the pointer action.  (As noted in       * {@link MotionEvent#obtain(long, long, int, float, float, int)}, be sure to use       * {@link SystemClock#uptimeMillis()} as the timebase.      */     public void sendPointerSync(MotionEvent event) {         validateNotAppThread();         if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) == 0) {             event.setSource(InputDevice.SOURCE_TOUCHSCREEN);         }         InputManager.getInstance().injectInputEvent(event,                 InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH);     }

4. 文本輸入的兩種方式

從上面兩節我們可以看到,在Instrumetnaiton框架中,我們操作目標應用控件的方式比如輸入文本有兩種方式,這些都可以從本文最后的實例中看到:
  • 通過runOnMainSync調用直接把文本修改的動作運行在UiThread這個主線程中
  • 通過注入事件模擬用戶通過按鍵輸入字符
那么這兩種方式有什么區別呢:
  • runOnMainSync: 直接在主線程中修改控件的文本,所以不需要通過鍵盤驅動,也就是說不需要調出任何的鍵盤。這樣的好處是效率以及不需要擔心中英文輸入的問題
  • 事件注入方式:模擬用戶的輸入,所以肯定會調出鍵盤,這樣在中文等非默認英文輸入的情況下容易碰到問題,畢竟中文字串也是通過拼音組合而成,那么拼音出來后選擇哪個出來的組合就成問題了。比如輸入"changan"可能出來的是"長安“,”長按“等組合,那么哪個是我們想要的呢?

5. 跨進程和安全問題

眾所周知Instrumentation和基于Instrumentation的Robotium對跨進程跨應用的支持是不支持的(其實Robotium從android 4.3之后開始支持UiAutomation框架,理應可以支持跨應用的,這個往后文章我們會進行分析).
但是從上面第3節的分析我們看到它使用的是InputManager的事件注入方式,大家翻回本人之前的文章:《monkey源碼分析之事件注入方法變化》,MonkeyRunner通過Monkey注入事件使用的也是InputManager方式啊。那么為什么基于Monkey的MonkeyRunner就能跨進程跨應用,基于Instrumentation的Robotium就不能跨應用呢?我個人認為有以下幾點原因:
  • 首先,一個應用要使用Instrumentation進行測試的話首先必須要在其Manifest.xml做相應的配置,那么一個應用真正發布的時候肯定是把這些配置給去掉的,所以Instrumentation或基于Instrumentation的Robotium肯定是不能對其他應用進行操作的,不然它就可以隨意的打開一個流量消耗大戶應用來消耗你的流量了。
  • 其次,既然大家里面都用了InputManager進行事件注入,那么為什么Monkey可以跨應用而Robotium不行呢?你Robotium也可以繞開Instrumentation框架直接調用InputManager來做事情啊!這里就要說到INJECT_EVENTS這個系統權限了,大家請參考《Monkey源碼分析番外篇之Android注入事件的三種方法比較》。人家Monkey是google親生的,獲取個INJECT_EVENTS系統權限還不容易嗎,你Robotium跟我什么關系,我google憑什么給你這些第三方應用開放這個權限呢?鬼知道給你開放這個權限后會不會搞破壞啊!所以你還是待在配置了Mainifest.xml的你的目標測試應用中做事情吧,別到處跑了

6.所謂鉤子

都說Android instrumentation是Android系統里面的一套控制方法或者”鉤子“。這些鉤子可以在正常的生命周期(正常是由操作系統控制的)之外控制Android控件的運行,其實指的就是Instrumentation類提供的各種流程控制方法,下表展示了部分方法的對應關系

Method

Control by User(Instrumentation)

Control by OS

Comment

onCreate

callActivityOnCreate

onCreate


onDestroy

callActivityOnDestroy

onDestroy


onStart

callActivityOnStart

onStarty





默認來說,一個Activity的創建和消亡都是由操作系統來控制調用的,用戶是沒辦法控制的。比如用戶是沒法直接調用onCreate方法來在activity啟動的時候做一些初始化動作。但是Instrumentation提供了對應的callActivityOnCreate方法來允許用戶控制對onCreate方法的調用,所以這里本來屬于操作系統的控制權就移交給用戶了。
    /**      * Perform calling of an activity's {@link Activity#onCreate}      * method.  The default implementation simply calls through to that method.      *       * @param activity The activity being created.      * @param icicle The previously frozen state (or null) to pass through to      *               onCreate().      */     public void callActivityOnCreate(Activity activity, Bundle icicle) {         ...         activity.performCreate(icicle);         ... }
從代碼可以看到它做的事情也就是直接調用Activity類的performCreate方法:
   final void performCreate(Bundle icicle) {         onCreate(icicle);         mVisibleFromClient = !mWindow.getWindowStyle().getBoolean(                 com.android.internal.R.styleable.Window_windowNoDisplay, false);         
而performCreate方法最終調用的就是onCreate方法。注意performCreate這個方法是屬于Internal API,它不是public出去給外部使用的.
所以這里就好比Instrumentation勾住了本應該系統調用的onCreate方法,然后由用戶自己來控制勾住的這個方法什么時候執行。

7. Instrumentation跨應用的考慮

安卓從Android4.3開始引進了UiAutomation框架來支持通過Accessibility API來實現針對用戶界面UI層面的功能測試,Instrumentation也提供了相應的接口來獲得UiAutomation實例:
    /**      * Gets the {@link UiAutomation} instance.      * <p>      * <strong>Note:</strong> The APIs exposed via the returned {@link UiAutomation}      * work across application boundaries while the APIs exposed by the instrumentation      * do not. For example, {@link Instrumentation#sendPointerSync(MotionEvent)} will      * not allow you to inject the event in an app different from the instrumentation      * target, while {@link UiAutomation#injectInputEvent(android.view.InputEvent, boolean)}      * will work regardless of the current application.      * </p>      * <p>      * A typical test case should be using either the {@link UiAutomation} or      * {@link Instrumentation} APIs. Using both APIs at the same time is not      * a mistake by itself but a client has to be aware of the APIs limitations.      * </p>      * @return The UI automation instance.      *      * @see UiAutomation      */     public UiAutomation getUiAutomation() {         if (mUiAutomationConnection != null) {             if (mUiAutomation == null) {                 mUiAutomation = new UiAutomation(getTargetContext().getMainLooper(),                         mUiAutomationConnection);                 mUiAutomation.connect();             }             return mUiAutomation;         }         return null;     }
關于UiAutomation更多的描述請查看本人上一個系列關于UiAutomator源碼分析的文章,這里列出來方便大家瀏覽:
  • 《Android4.3引入的UiAutomation新框架官方簡介》
  • 《UIAutomator源碼分析之啟動和運行》
  • 《UiAutomator源碼分析之UiAutomatorBridge框架》
  • 《UiAutomator源碼分析之注入事件》
  • 《UiAutomator源碼分析之獲取控件信息》
從上面的一系列文章可以看到UiAutomator運用UiAutomation框架進行UI自動化測試是做了很多工作,進行了很多高層的封裝來方便用戶使用的。而Robotium僅僅是引入了獲取UiAutomation的實例這個api來暴露給用戶使用,一個方面,當然沒有高層的封裝提供了很多自由,但是也是這些自由讓你想快速開發腳本無所適從!Robotium現階段(5.2.1)對比UiAutomator或者Appium在使用UiAutomation來測試UI就好比,Robotium相當于一個原始社會的人自由的披著件獸皮兩手空空的在原始森林中自由游獵,碰到猛獸可以自由的選擇工具隨意組裝來進行獵殺,但很有可能工具沒有組裝好怪獸卻先把你給吃了;UiAutomator相當于一個現代的人全副武裝AK47的在原始森林根據GPS定位如履薄冰的向目標獵物靠近來獵殺獵物。兩者都是使用最終由化學元素組成的工具來獵殺獵物,但早已高層封裝好的ak47和你臨時抱佛腳去憑空組建個×××從效率上又怎么能比呢。
所以這里我懷疑Robotium可能就提供這個接口就算了,不會再做上層的封裝,因為UiAutomator已經做了,人家UiAutomator是google自家的,什么時候又改動人家最清楚,你怎么跟得住人家呢?況且它從Instrumentation的不可跨進程到提供了一個跨進程的突破口,也給了確實需要跨進程調用的用戶的一個突破口,不提供太多的封裝還能美其名曰“自由”了。注意,僅僅是我自己的猜想了,如果Robotium往后真對UiAutomation做高成封裝的話就當我發神經得了

8.Instrumentation使用例子

/*  * Copyright (C) 2008 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 come.example.android.notepad.test;  import android.test.ActivityInstrumentationTestCase2;  import com.example.android.notepad.NotesList; import com.example.android.notepad.NoteEditor; import com.example.android.notepad.NotesList; import com.example.android.notepad.R;  import android.app.Activity; import android.app.Instrumentation; import android.app.Instrumentation.ActivityMonitor; import android.content.Intent; import android.os.SystemClock; import android.test.InstrumentationTestCase; import android.view.KeyEvent; import android.widget.TextView;   /**  * Make sure that the main launcher activity opens up properly, which will be  * verified by {@link #testActivityTestCaseSetUpProperly}.  */ public class NotePadTest extends ActivityInstrumentationTestCase2<NotesList> {  	NotesList mActivity = null; 	     /**      * Creates an {@link ActivityInstrumentationTestCase2} for the {@link NotesList} activity.      */     public NotePadTest() {         super(NotesList.class);     } 	//private static Instrumentation instrumentation = new Instrumentation(); 	 	@Override  	protected void setUp() throws Exception { 		super.setUp(); 		 		//Start the NotesList activity by instrument 		Intent intent = new Intent(); 		intent.setClassName("com.example.android.notepad", NotesList.class.getName()); 		intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 		Instrumentation inst = getInstrumentation(); 		mActivity = (NotesList) inst.startActivitySync(intent);  	} 	 	 @Override     protected void tearDown()  {         mActivity.finish();         try {             super.tearDown();         } catch (Exception e) {             e.printStackTrace();         } 	 } 	  	       /**      * Verifies that the activity under test can be launched.      */ 	 /*     public void testActivityTestCaseSetUpProperly() {         assertNotNull("activity should be launched successfully", getActivity());     } 	*/  	 public void testActivity() throws Exception { 	         	 	//Add activity monitor to check whether the NoteEditor activity's ready         ActivityMonitor am = getInstrumentation().addMonitor(NoteEditor.class.getName(), null, false);                  //Evoke the system menu and press on the menu entry "Add note";         getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_MENU);         getInstrumentation().invokeMenuActionSync(mActivity, R.id.menu_add, 0);                  //Direct to the NoteEditor activity         Activity noteEditorActivity = getInstrumentation().waitForMonitorWithTimeout(am, 60000);         assertEquals(NoteEditor.class,noteEditorActivity.getClass());         SystemClock.sleep(3000);         //assertEquals(true, getInstrumentation().checkMonitorHit(am, 1));          TextView noteEditor = (TextView) noteEditorActivity.findViewById(R.id.note);            //Get the text directly, DON'T need to runOnMainSync at all!!!         String text = noteEditor.getText().toString();         assertEquals(text,"");                  //runOnMainSync to change the text         getInstrumentation().runOnMainSync(new PerformSetText(noteEditor,"Note1"));                  //inject events to change the text         getInstrumentation().sendCharacterSync(KeyEvent.KEYCODE_1);         getInstrumentation().sendCharacterSync(KeyEvent.KEYCODE_2);         getInstrumentation().sendCharacterSync(KeyEvent.KEYCODE_P);         getInstrumentation().sendStringSync("gotohell");         //getInstrumentation().callActivityOnPause(noteEditorActivity);         Thread.sleep(5000);         //getInstrumentation().callActivityOnResume(noteEditorActivity);                  //Save the new created note         getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_MENU);         getInstrumentation().invokeMenuActionSync(noteEditorActivity, R.id.menu_save, 0);  	 } 	  	 private class PerformSetText implements Runnable {         TextView tv;         String txt;         public PerformSetText(TextView t,String text) {             tv = t;             txt = text;         }            public void run() {             tv.setText(txt);         }     } }
<table cellspacing="0" cellpadding="0" width="539" class="  " style="margin: 0px 0px 10px; padding: 0px; border-collapse: collapse; width: 668px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important;"><tbody style="margin: 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important;"><tr style="margin: 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important;"><td valign="top" width="112" height="39" style="border-style: solid; border-color: rgb(0, 0, 0); margin: 0px; padding: 4px; word-break: break-all; max-width: 100%; word-wrap: break-word !important; box-sizing: border-box !important;"> </td></tr><tr style="margin: 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important;"><td valign="top" width="111" height="13" style="border-style: solid; border-color: rgb(0, 0, 0); margin: 0px; padding: 4px; word-break: break-all; max-width: 100%; word-wrap: break-word !important; box-sizing: border-box !important; background-color: rgb(190, 192, 191);"><p style="margin-top: 0px; margin-bottom: 0px; padding-top: 0px; padding-bottom: 0px; max-width: 100%; clear: both; min-height: 1em; white-space: pre-wrap; box-sizing: border-box !important; word-wrap: break-word !important;"><span style="margin: 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important;">作者</span></p></td><td valign="top" width="112" height="13" style="border-style: solid; border-color: rgb(0, 0, 0); margin: 0px; padding: 4px; word-break: break-all; max-width: 100%; word-wrap: break-word !important; box-sizing: border-box !important; background-color: rgb(190, 192, 191);"><p style="margin-top: 0px; margin-bottom: 0px; padding-top: 0px; padding-bottom: 0px; max-width: 100%; clear: both; min-height: 1em; white-space: pre-wrap; box-sizing: border-box !important; word-wrap: break-word !important;"><span style="margin: 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important;">自主博客</span></p></td><td valign="top" width="111" height="13" style="border-style: solid; border-color: rgb(0, 0, 0); margin: 0px; padding: 4px; word-break: break-all; max-width: 100%; word-wrap: break-word !important; box-sizing: border-box !important; background-color: rgb(190, 192, 191);"><p style="margin-top: 0px; margin-bottom: 0px; padding-top: 0px; padding-bottom: 0px; max-width: 100%; clear: both; min-height: 1em; white-space: pre-wrap; box-sizing: border-box !important; word-wrap: break-word !important;"><span style="margin: 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important;">微信</span></p></td><td valign="top" width="112" height="13" style="border-style: solid; border-color: rgb(0, 0, 0); margin: 0px; padding: 4px; word-break: break-all; max-width: 100%; word-wrap: break-word !important; box-sizing: border-box !important; background-color: rgb(190, 192, 191);"><p style="margin-top: 0px; margin-bottom: 0px; padding-top: 0px; padding-bottom: 0px; max-width: 100%; clear: both; min-height: 1em; white-space: pre-wrap; box-sizing: border-box !important; word-wrap: break-word !important;"><span style="margin: 0px; padding: 0px; max-width: 100%; font-stretch: normal; font-family: Helvetica; letter-spacing: 0px; box-sizing: border-box !important; word-wrap: break-word !important;">CSDN</span></p></td></tr><tr style="margin: 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important;"><td valign="top" width="111" height="39" style="border-style: solid; border-color: rgb(0, 0, 0); margin: 0px; padding: 4px; word-break: break-all; max-width: 100%; word-wrap: break-word !important; box-sizing: border-box !important; background-color: rgb(227, 228, 228);"><p style="margin-top: 0px; margin-bottom: 0px; padding-top: 0px; padding-bottom: 0px; max-width: 100%; clear: both; min-height: 1em; white-space: pre-wrap; box-sizing: border-box !important; word-wrap: break-word !important;"><span style="margin: 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important;">天地會珠海分舵</span></p></td><td valign="top" width="112" height="39" style="border-style: solid; border-color: rgb(0, 0, 0); margin: 0px; padding: 4px; word-break: break-all; max-width: 100%; word-wrap: break-word !important; box-sizing: border-box !important;"><p style="margin-top: 0px; margin-bottom: 0px; padding-top: 0px; padding-bottom: 0px; max-width: 100%; clear: both; min-height: 1em; white-space: pre-wrap; box-sizing: border-box !important; word-wrap: break-word !important;"><span style="margin: 0px; padding: 0px; max-width: 100%; font-stretch: normal; font-size: 11px; font-family: Helvetica; letter-spacing: 0px; box-sizing: border-box !important; word-wrap: break-word !important;"><a target=_blank href="http://techgogogo.com/">http://techgogogo.com</a></span><span style="margin: 0px; padding: 0px; max-width: 100%; font-family: Helvetica; font-size: 11px; letter-spacing: 0px; box-sizing: border-box !important; word-wrap: break-word !important;"> </span></p><p style="margin-top: 0px; margin-bottom: 0px; padding-top: 0px; padding-bottom: 0px; max-width: 100%; clear: both; min-height: 14px; white-space: pre-wrap; font-stretch: normal; font-family: Helvetica; box-sizing: border-box !important; word-wrap: break-word !important;"><br style="margin: 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important;" /></p></td><td valign="top" width="111" height="39" style="border-style: solid; border-color: rgb(0, 0, 0); margin: 0px; padding: 4px; word-break: break-all; max-width: 100%; word-wrap: break-word !important; box-sizing: border-box !important;"><p style="margin-top: 0px; margin-bottom: 0px; padding-top: 0px; padding-bottom: 0px; max-width: 100%; clear: both; min-height: 1em; white-space: pre-wrap; box-sizing: border-box !important; word-wrap: break-word !important;"><span style="margin: 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important;">服務號</span><span style="margin: 0px; padding: 0px; max-width: 100%; font-stretch: normal; font-size: 10px; font-family: Helvetica; letter-spacing: 0px; box-sizing: border-box !important; word-wrap: break-word !important;">:TechGoGoGo</span></p><p style="margin-top: 0px; margin-bottom: 0px; padding-top: 0px; padding-bottom: 0px; max-width: 100%; clear: both; min-height: 1em; white-space: pre-wrap; box-sizing: border-box !important; word-wrap: break-word !important;"><span style="margin: 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important;">掃描碼</span><span style="margin: 0px; padding: 0px; max-width: 100%; font-stretch: normal; font-size: 10px; font-family: Helvetica; letter-spacing: 0px; box-sizing: border-box !important; word-wrap: break-word !important;">:</span></p><p style="margin-top: 0px; margin-bottom: 0px; padding-top: 0px; padding-bottom: 0px; max-width: 100%; clear: both; min-height: 14px; white-space: pre-wrap; font-stretch: normal; font-family: Helvetica; box-sizing: border-box !important; word-wrap: break-word !important;"><img data-s="300,640" data-type="jpeg" data-src="http://mmbiz.qpic.cn/mmbiz/KYJTqcL56vuJuQArNAk7nsLW8hpxia6kjor2IEvib9RAQTEzzEPa4UngfjpT1GKIIKCnb7ib0IViaWEV7VFFiaAkkjg/0" data-ratio="1" data-w="125" _width="auto" src="http://mmbiz.qpic.cn/mmbiz/KYJTqcL56vuJuQArNAk7nsLW8hpxia6kjor2IEvib9RAQTEzzEPa4UngfjpT1GKIIKCnb7ib0IViaWEV7VFFiaAkkjg/640?tp=webp" style="max-width: 100%; margin: 0px; padding: 0px; height: auto !important; box-sizing: border-box !important; word-wrap: break-word !important; width: auto !important; visibility: visible !important;" alt="" /></p></td><td valign="top" width="112" height="39" style="border-style: solid; border-color: rgb(0, 0, 0); margin: 0px; padding: 4px; word-break: break-all; max-width: 100%; word-wrap: break-word !important; box-sizing: border-box !important;"><p style="margin-top: 0px; margin-bottom: 0px; padding-top: 0px; padding-bottom: 0px; max-width: 100%; clear: both; min-height: 1em; white-space: pre-wrap; color: rgb(62, 62, 62); font-family: 'Helvetica Neue', Helvetica, 'Hiragino Sans GB', 'Microsoft YaHei', ΢ÈíÑźÚ, Arial, sans-serif; font-size: 18px; line-height: 28.799×××370605px; box-sizing: border-box !important; word-wrap: break-word !important;"><span style="margin: 0px; padding: 0px; max-width: 100%; color: rgb(0, 0, 0); font-stretch: normal; font-size: 11px; font-family: Helvetica; letter-spacing: 0px; box-sizing: border-box !important; word-wrap: break-word !important;"><a target=_blank href="http://blog.csdn.net/zhubaitian">http://blog.csdn.net/zhubaitian</a></span><span style="margin: 0px; padding: 0px; max-width: 100%; color: rgb(0, 0, 0); font-family: Helvetica; font-size: 11px; letter-spacing: 0px; line-height: 28.799×××370605px; box-sizing: border-box !important; word-wrap: break-word !important;"> </span></p><div><span style="margin: 0px; padding: 0px; max-width: 100%; color: rgb(0, 0, 0); font-family: Helvetica; font-size: 11px; letter-spacing: 0px; line-height: 28.799×××370605px; box-sizing: border-box !important; word-wrap: break-word !important;">  </span></div></td></tr></tbody></table>  

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

天柱县| 新安县| 白水县| 潍坊市| 临洮县| 永宁县| 克山县| 珲春市| 米易县| 大邑县| 内乡县| 中牟县| 宁武县| 乌鲁木齐市| 成安县| 昌吉市| 云霄县| 乳源| 醴陵市| 徐汇区| 喀什市| 金华市| 轮台县| 鹿泉市| 凤城市| 临高县| 武清区| 泌阳县| 新蔡县| 枣阳市| 许昌市| 黄冈市| 金溪县| 舞钢市| 金寨县| 绥芬河市| 定南县| 广德县| 额济纳旗| 曲沃县| 巴楚县|