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



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

  • 相关阅读:
    Android Library projetcts cannot be exported.
    技术阅读记录(一)
    ASP.NET MVC Model绑定(五)
    Linux 高速操作IOport
    This version of the rendering library is more recent than your version of IntelliJ IDEA.
    WPF使用RoutedCommand自己定义命令
    AutoLayout具体解释+手把手实战
    JNI之——Can&#39;t load IA 32-bit .dll on a AMD 64-bit platform错误的解决
    9.Nexus私服安装配置
    Alcatraz:管理Xcode插件
  • 原文地址:https://www.cnblogs.com/mfrbuaa/p/4746087.html
Copyright © 2011-2022 走看看