zoukankan      html  css  js  c++  java
  • Robotium调用getActivity()导致程序挂起的方法

    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"); } }


    但在工作的測试目标平台launcher中使用相同的方法去setup并执行简单的測试时碰到问题:測试程序一直挂起没有返回,程序挂起在下面getaActivity()方法(因是公司代码。故以notepad測试代码代替之):
    	@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.问题分析

    既然是getActvity()方法出现故障。而该方法原有的bug也已经在最新的版本号fixed。在google无所获的情况下也仅仅能剩下分析源代码这条路了。由于是自己刚在backbook上搭建的自己主动化研究平台。为了节省时间。当时没有下载android的对应源代码,仅仅有sdk。所以第一步必须是先在项目中配置使用上android的源代码,其理与配置javadoc相近,请查看本人之前的一篇文章《How to Configure Javadoc for Robotium Library》,这里不做累术。

    增加源代码后调试分析。终于程序挂起在android.test.InstrumentationTestCase中的launchActivityWithIntent方法中,下面是eclipse中的调试截图演示样例:

    下面是该方法的完整代码片段:
    /**
         * 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状态的时候返回
    看到这里我幡然领悟。在目标平台上面我们有一个天气预报的功能。在不停的发送事件给application(也就是launcher)来更新当前的天气情况。所以一直没有达到idle的状态。这样这个函数也就一直没有返回而挂起了。而在本人的測试手机上測试的notepad这个apk,一进去的launchable activity就是idle的。所以不会碰到这个问题。

    带着这个思路在调整googlekeyword在stackoverflow中找到了国外同行碰到的一个类似的问题:http://stackoverflow.com/questions/20860832/why-does-getactivity-block-during-junit-test-when-custom-imageview-calls-start
    这里总结下本人研究过程中了解到的robotium初始化solo的时候new Solo(getInstrumentation(),getActivity())中getActivity所做的事情:
    • 假设目标activity没有起来。那么启动该activity并放在前台
    • 假设目标activity已经起来,那么直接放在前台等待被測试
    • 假设该该activity所属application在自己主动不停的接受事件,直接调用getActivity会由于一直等待application变成idle状态而挂起

    3. 解决方法

    本人依照项目中的目标測试launcher的实际情况想到的解决方法是在初始化solo的时候不去调用getActivity()这个InstrumentationTestCase2的方法:
    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给启动起来,那么睡眠时间过后測试会继续正常执行。

    刚才stackoverflow上提到的另外一个方法是重写getActivity()这个IntrumentationTestCase2的方法(注意我们全部的robotium測试类都是继承于该class的):
    @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

    天地会珠海分舵

    http://techgogogo.com


    服务号:TechGoGoGo

    扫描码:

    http://blog.csdn.net/zhubaitian



    版权声明:本文博客原创文章,博客,未经同意,不得转载。

  • 相关阅读:
    173. Binary Search Tree Iterator
    199. Binary Tree Right Side View
    230. Kth Smallest Element in a BST
    236. Lowest Common Ancestor of a Binary Tree
    337. House Robber III
    449. Serialize and Deserialize BST
    508. Most Frequent Subtree Sum
    513. Find Bottom Left Tree Value
    129. Sum Root to Leaf Numbers
    652. Find Duplicate Subtrees
  • 原文地址:https://www.cnblogs.com/mfrbuaa/p/4746087.html
Copyright © 2011-2022 走看看