1. 问题背景的叙述性说明
需要直接用在工作中没有项目的源代码robotium测试目标android平台launcher,该平台的基础上,当前日期的版本号android 4.4.2。之前我用来验证的可行性,同时android4.4.2测试手机htc incredable s针对一个仅仅有apk的notepad应用做过相同的验证,在測试手机上执行全然没有问题。该測试代码例如以下:
package com.example.android.notepad.tryout; import com.robotium.solo.Solo; import android.test.ActivityInstrumentationTestCase2; import android.widget.TextView; import android.app.Activity; @SuppressWarnings("rawtypes") public class NotePadTest extends ActivityInstrumentationTestCase2{ private static Solo solo = null; public Activity activity; private static final int NUMBER_TOTAL_CASES = 2; private static int run = 0; private static Class<?> launchActivityClass; //相应re-sign.jar生成出来的信息框里的两个值 private static String mainActiviy = "com.example.android.notepad.NotesList"; private static String packageName = "com.example.android.notepad"; static { try { launchActivityClass = Class.forName(mainActiviy); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } } @SuppressWarnings("unchecked") public NotePadTest() { super(packageName, launchActivityClass); } @Override public void setUp() throws Exception { //setUp() is run before a test case is started. //This is where the solo object is created. super.setUp(); //The variable solo has to be static, since every time after a case's finished, this class TCCreateNote would be re-instantiated // which would lead to soto to re-instantiated to be null if it's not set as static //TextView title = (TextView)getActivity().findViewById(Ref.id.title); if(solo == null) { NotePadTest.solo = new Solo(getInstrumentation(),getActivity()); } } @Override public void tearDown() throws Exception { //Check whether it's the last case executed. run += countTestCases(); if(run >= NUMBER_TOTAL_CASES) { solo.finishOpenedActivities(); } } public void testAddNoteCNTitle() throws Exception { //Thread.sleep(5000); solo.clickOnMenuItem("Add note"); solo.enterText(0, "中文标签笔记"); solo.clickOnMenuItem("Save"); solo.clickInList(0); solo.clearEditText(0); solo.enterText(0, "Text 1"); solo.clickOnMenuItem("Save"); solo.assertCurrentActivity("Expected NotesList Activity", "NotesList"); solo.clickLongOnText("中文标签笔记"); solo.clickOnText("Delete"); } public void testAddNoteEngTitle() throws Exception { solo.clickOnMenuItem("Add note"); solo.enterText(0, "English Title Note"); solo.clickOnMenuItem("Save"); solo.clickInList(0); solo.clearEditText(0); solo.enterText(0, "Text 1"); solo.clickOnMenuItem("Save"); solo.assertCurrentActivity("Expected NotesList Activity", "NotesList"); solo.clickLongOnText("English Title Note"); solo.clickOnText("Delete"); } }
@Override public void setUp() throws Exception { //setUp() is run before a test case is started. //This is where the solo object is created. super.setUp(); //The variable solo has to be static, since every time after a case's finished, this class TCCreateNote would be re-instantiated // which would lead to soto to re-instantiated to be null if it's not set as static if(solo == null) { NotePadTest.solo = new Solo(getInstrumentation(),getActivity()); } }
当时一直怀疑是否系统launcher的robotium初始化和setup方法跟普通的apk不一样,google上有历史文章描写叙述getActivity()在Android 2.xx.xx上确实有这个问题,但后来的版本号已经解决,而本人使用的时当前最的4.4.2,所以不应该还存在这样的问题。
针对这个思路去尝试找解决的方法终无果。
2.问题分析
/** * Utility method for launching an activity with a specific Intent. * * <p><b>NOTE:</b> The parameter <i>pkg</i> must refer to the package identifier of the * package hosting the activity to be launched, which is specified in the AndroidManifest.xml * file. This is not necessarily the same as the java package name. * * @param pkg The package hosting the activity to be launched. * @param activityCls The activity class to launch. * @param intent The intent to launch with * @return The activity, or null if non launched. */ @SuppressWarnings("unchecked") public final <T extends Activity> T launchActivityWithIntent( String pkg, Class<T> activityCls, Intent intent) { intent.setClassName(pkg, activityCls.getName()); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); T activity = (T) getInstrumentation().startActivitySync(intent); getInstrumentation().waitForIdleSync(); return activity; }
导致挂起的位置是里面的getInstrumentation().waitForIdleSync()方法,到了这里再代码跟踪进去看到的就是android.app.instrumentation这个基类里面:
/** * Synchronously wait for the application to be idle. Can not be called * from the main application thread -- use {@link #start} to execute * instrumentation in its own thread. */ public void waitForIdleSync() { validateNotAppThread(); Idler idler = new Idler(null); mMessageQueue.addIdleHandler(idler); mThread.getHandler().post(new EmptyRunnable()); idler.waitForIdle(); }这里依照本人的理解做的事情大概例如以下:
- 首先确保调用这种方法的来源不是application的主线程
- 然后把当前等待application变成idle的请求放到消息队列中
- 最后等待app在处理全然部事件达到idle状态的时候返回
- 假设目标activity没有起来。那么启动该activity并放在前台
- 假设目标activity已经起来,那么直接放在前台等待被測试
- 假设该该activity所属application在自己主动不停的接受事件,直接调用getActivity会由于一直等待application变成idle状态而挂起
3. 解决方法
solo = new Solo(getInstrumentation());由于我们的launcher在robotium在kill掉原来的launcher进程的时候就会自己主动起来,所以并不须要手动的去getActivity()去启动。这样的方法在不能启动起来的apk如notepad上面就不行,不信你去掉getActivity()的调用,保证notepad不会启动或者放到前台。可是假设你在開始測试前先把notepad手动起来并放到前台,測试还是会正常进行的。比方下面的验证性代码:
package com.example.android.notepad.tryout; import com.robotium.solo.Solo; import android.test.ActivityInstrumentationTestCase2; import android.widget.TextView; import android.app.Activity; @SuppressWarnings("rawtypes") public class NotePadTest extends ActivityInstrumentationTestCase2{ private static Solo solo = null; public Activity activity; private static final int NUMBER_TOTAL_CASES = 2; private static int run = 0; private static Class<?> launchActivityClass; //相应re-sign.jar生成出来的信息框里的两个值 private static String mainActiviy = "com.example.android.notepad.NotesList"; private static String packageName = "com.example.android.notepad"; static { try { launchActivityClass = Class.forName(mainActiviy); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } } @SuppressWarnings("unchecked") public NotePadTest() { super(packageName, launchActivityClass); } @Override public void setUp() throws Exception { //setUp() is run before a test case is started. //This is where the solo object is created. super.setUp(); //The variable solo has to be static, since every time after a case's finished, this class TCCreateNote would be re-instantiated // which would lead to soto to re-instantiated to be null if it's not set as static //TextView title = (TextView)getActivity().findViewById(Ref.id.title); if(solo == null) { NotePadTest.solo = new Solo(getInstrumentation());//, getActivity()); } } @Override public void tearDown() throws Exception { //Check whether it's the last case executed. run += countTestCases(); if(run >= NUMBER_TOTAL_CASES) { solo.finishOpenedActivities(); } } public void testAddNoteCNTitle() throws Exception { //getActivity(); Thread.sleep(5000); solo.clickOnMenuItem("Add note"); solo.enterText(0, "中文标签笔记"); solo.clickOnMenuItem("Save"); solo.clickInList(0); solo.clearEditText(0); solo.enterText(0, "Text 1"); solo.clickOnMenuItem("Save"); solo.assertCurrentActivity("Expected NotesList Activity", "NotesList"); solo.clickLongOnText("中文标签笔记"); solo.clickOnText("Delete"); } }初始化solo的时候和testcase里面都没有去调用getActivity(),可是在testcase開始前先睡眠5秒。假设在这5秒的过程中你手动把notepad给启动起来,那么睡眠时间过后測试会继续正常执行。
@Override public MyActivity getActivity() { if (mActivity == null) { Intent intent = new Intent(getInstrumentation().getTargetContext(), MyActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // register activity that need to be monitored. monitor = getInstrumentation().addMonitor(MyActivity.class.getName(), null, false); getInstrumentation().getTargetContext().startActivity(intent); mActivity = (MyActivity) getInstrumentation().waitForMonitor(monitor); setActivity(mActivity); } return mActivity; }鉴于本人如今仅仅是做前期的可行性研究,够用就好。且周末手头上也没有目标机器在手进行验证,所以有兴趣的朋友就自己去尝试下吧。
作者 |
自主博客 |
微信 |
CSDN |
天地会珠海分舵 |
|
服务号:TechGoGoGo 扫描码: |
http://blog.csdn.net/zhubaitian |
版权声明:本文博客原创文章,博客,未经同意,不得转载。
本文转自mfrbuaa博客园博客,原文链接:http://www.cnblogs.com/mfrbuaa/p/4746087.html,如需转载请自行联系原作者