Robotium源码分析之Instrumentation进阶-attach
在分析Robotium的运行原理之前,我们有必要先搞清楚Instrumentation的一些相关知识点,因为Robotium就是基于Instrumentation而开发出来的一套自动化测试框架。鉴于之前本人已经转载和编写了Instrumentation的一些文章,所以建议大家如果没有看过的还是翻看下先对Instrumentation有个基本的理解。然后带着疑问再来看这篇文章看是否能帮上自己。
既然是分析Instrumentation,那么我们必须要先看下Instrumentation 这个类的类图,直接网上截获,就不花时间另外去画了,但请注意网上该图是比较老的,一些新的注入事件的方法是没有加进去的,注意红色部分:
开始分析之前我们要搞清楚Instrumentation的几点
1. Instrumentation测试脚本和目标app在同一个进程中运行
in the components.“翻译过来就是“Instrumentation可以把测试包和目标测试应用加载到同一个进程中运行。既然各个控件和测试代码都运行在同一个进程中了,测试代码当然就可以调用这些控件的方法了,同时修改和验证这些控件的一些项也不在话下了。“
/* */ public class InstrumentationTestRunner
/* */ extends Instrumentation
/* */ implements TestSuiteProvider
/* */ {
...
}
从它的类定义我们可以看到它是从我们的Instrumentation类继承下来的。其实从它的名字我们就大概可以想像到它是扮演什么角色的,参照我们之前对UiAutomator的源码分析《UIAutomator源码分析之启动和运行》,InstrumentationTestRunner扮演的角色类似于当中的UiAutomatorTestRunner类,都是通过解析获取和建立目标测试用例和测试集然后知道测试的运行。
/* */ 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()方法获得的就是子类的名字。
/* */ 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();
/* */ }
/* */ }
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框架是怎么回事了。
2. runOnUiThread和runOnMainSync的区别
- 子线程是可以直接获取主线程UiThread的控件以及内容的
- 子线程是不能直接操作主线程UiThread的控件以及内容的
- 1、handler
- 2、Activity.runOnUIThread(Runnable)
- 3、View.Post(Runnable)
- 4、View.PostDelayed(Runnabe,long)
- 5、AsyncTask
/**
* 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队列排队,直接调用执行
/* */ 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这个内部类实现同步的了。
/* */ 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
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注入事件的三种方法比较》。
/**
* 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. 文本输入的两种方式
- 通过runOnMainSync调用直接把文本修改的动作运行在UiThread这个主线程中
- 通过注入事件模拟用户通过按键输入字符
- runOnMainSync: 直接在主线程中修改控件的文本,所以不需要通过键盘驱动,也就是说不需要调出任何的键盘。这样的好处是效率以及不需要担心中英文输入的问题
- 事件注入方式:模拟用户的输入,所以肯定会调出键盘,这样在中文等非默认英文输入的情况下容易碰到问题,毕竟中文字串也是通过拼音组合而成,那么拼音出来后选择哪个出来的组合就成问题了。比如输入"changan"可能出来的是"长安“,”长按“等组合,那么哪个是我们想要的呢?
5. 跨进程和安全问题
众所周知Instrumentation和基于Instrumentation的Robotium对跨进程跨应用的支持是不支持的(其实Robotium从android 4.3之后开始支持UiAutomation框架,理应可以支持跨应用的,这个往后文章我们会进行分析).
- 首先,一个应用要使用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.所谓钩子
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出去给外部使用的.
7. Instrumentation跨应用的考虑
/**
* 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和你临时抱佛脚去凭空组建个弓弩从效率上又怎么能比呢。
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);
}
}
}
Item
|
Description
|
Warning
|
Author
|
天地会珠海分舵
|
转载请注明出处!
更多精彩文章请查看本人博客!
|
Blog Address
|
http://blog.csdn.net/zhubaitian
|
Robotium源码分析之Instrumentation进阶-attach的更多相关文章
- Robotium源码分析之Instrumentation进阶
在分析Robotium的运行原理之前,我们有必要先搞清楚Instrumentation的一些相关知识点,因为Robotium就是基于Instrumentation而开发出来的一套自动化测试框架.鉴于之 ...
- Robotium源码分析之运行原理
从上一章<Robotium源码分析之Instrumentation进阶>中我们了解到了Robotium所基于的Instrumentation的一些进阶基础,比如它注入事件的原理等,但Rob ...
- MonkeyRunner源码分析之工作原理图-attach
花了点时间整理了下MonkeyRunner的工作原理图: Item Description Warning Author 天地会珠海分舵 转载请注明出处! Blog Address http://bl ...
- Android进阶——多线程系列之异步任务AsyncTask的使用与源码分析
AsyncTask是一种轻量级的异步任务类,它可以在线程池中执行后台任务,然后把执行的进度和最终结果传递给主线程并主线程中更新UI,通过AsyncTask可以更加方便执行后台任务以及在主线程中访问UI ...
- Python 进阶之源码分析:如何将一个类方法变为多个方法?
前一篇文章<Python 中如何实现参数化测试?>中,我提到了在 Python 中实现参数化测试的几个库,并留下一个问题: 它们是如何做到把一个方法变成多个方法,并且将每个方法与相应的参数 ...
- Android源码分析-全面理解Context
前言 Context在android中的作用不言而喻,当我们访问当前应用的资源,启动一个新的activity的时候都需要提供Context,而这个Context到底是什么呢,这个问题好像很好回答又好像 ...
- Android事件传递机制详解及最新源码分析——Activity篇
版权声明:本文出自汪磊的博客,转载请务必注明出处. 在前两篇我们共同探讨了事件传递机制<View篇>与<ViewGroup篇>,我们知道View触摸事件是ViewGroup传递 ...
- Android查缺补漏(View篇)--事件分发机制源码分析
在上一篇博文中分析了事件分发的流程及规则,本篇会从源码的角度更进一步理解事件分发机制的原理,如果对事件分发规则还不太清楚的童鞋,建议先看一下上一篇博文 <Android查缺补漏(View篇)-- ...
- 关于ContextImp的源码分析
关于ContextImp的源码分析 来源: http://blog.csdn.net/qinjuning/article/details/7310620 Context概述: Android ...
随机推荐
- Cocos2d-x 3.1.1 学习日志16--A星算法(A*搜索算法)学问
A *搜索算法称为A星算法.这是一个在图形平面,路径.求出最低通过成本的算法. 经常使用于游戏中的NPC的移动计算,或线上游戏的BOT的移动计算上. 首先:1.在Map地图中任取2个点,開始点和结束点 ...
- Mybatis如何SQL声明表名称参数
insert into prefix_${table_name} (a, b, c) values (#{a}, #{b}, #{c}) ${} 它代表了直接使用字面(literal value) # ...
- ASP.NET MVC常见问题解决方法
1.页面报错: The following errors occurred while attempting to load the app. - No assembly found containi ...
- C#+Mapxtreme 实现一些GIS系统基本的功能
此程序包括了mapxtreme地图相关基本功能的演示其中包括 鹰眼地图,图层控制,发达,缩小,平移地图,地图模糊查询,中点工具,距离测量工具,面积测量工具,图元信息查看工具.适合于企业级开发,可以为您 ...
- 用Ghostscript API将PDF格式转换为图像格式(C#)
原文:用Ghostscript API将PDF格式转换为图像格式(C#) 由于项目需要在.net下将pdf转换为普通图像格式,在网上搜了好久终于找到一个解决方案,于是采用拿来主义直接用.来源见代码中注 ...
- Scan IP relocate/failover其它段后不能ping通过
或手动集群重启单个节点srvctl relocate scan_listener后.群集网络段ping IP,VIP.SCAN IP正常.其他段ping SCAN IP 不通.其原因是,该路由ARP表 ...
- JAVA学习第五十九课 — 网络编程概述
网络模型 OSI(Open System Interconnection)开放系统互连:參考模型 TCP/IP 网络通讯要素 IP地址 port号 传输协议 网络參考模型 七层OSI模型的基本概念要了 ...
- Ubuntu中全然卸载Nginx
Nginx尽管好用,可是一旦关键配置文件被改动,想要卸载重装却是相当困难.本人由于採用apt-get方式安装后又源代码安装了Nginx,结果出现冲突,卸载不了,安装不上,非常是蛋疼.基本的问题还是Ng ...
- 【SSH 基金会】SSH框架--struts进一步的详细解释(两)
继上篇博客 既然我们知道了不使用struts给我们带来这么多弊端,那么以下我们来看看struts是怎样封装的.怎么解决我们出现的问题的? 先来说一下struts的基本流程,帮助大家理解以下的代码: S ...
- OUC_OptKernel_oshixiaoxiliu_好题推荐
poj1112 Team Them Up! 补图二分图+dp记录路径codeforces 256A Almost Arithmetical Progression dp或暴力 dp[i][j] = d ...