getActivity() 调用导致 RuntimeException:无法启动意图 Intent act=android.intent.action.MAIN

Posted

技术标签:

【中文标题】getActivity() 调用导致 RuntimeException:无法启动意图 Intent act=android.intent.action.MAIN【英文标题】:getActivity() call causes RuntimeException: Could not launch intent Intent act=android.intent.action.MAIN 【发布时间】:2014-04-30 18:48:20 【问题描述】:

更新 #1:更多信息添加到这篇文章的末尾

我是 android 开发和测试新手。

我有三个 Espresso 测试。第一个测试通过,但第二个测试不会运行,因为在第二个测试之前调用 setUp() 方法中的 getActivity() 总是失败:

块引用 java.lang.RuntimeException:无法在 45 秒内启动 Intent Intent act=android.intent.action.MAIN flg=0x10000000 cmp=my.packagename/.ActivityMain 。 ...

第三次测试通过。

我在创建时没有任何长时间运行的操作、动画或网络调用。我可以单击应用程序中的所有菜单项手动重复测试流程而不会出现问题。

由于某种原因,第一个测试在第二个测试之前“中断”了 setUp() 中的下一个 getActivity() 调用。以下所有测试(第二次测试之后)都将运行正常。

我找到了similar 的问题,但它似乎没有解决,而且问题有点不同。

测试代码:

import static com.google.android.apps.common.testing.ui.espresso.Espresso.onData;
import static com.google.android.apps.common.testing.ui.espresso.Espresso.onView;
import static com.google.android.apps.common.testing.ui.espresso.Espresso.openActionBarOverflowOrOptionsMenu;
import static com.google.android.apps.common.testing.ui.espresso.action.ViewActions.click;
import static com.google.android.apps.common.testing.ui.espresso.assertion.ViewAssertions.matches;
import static com.google.android.apps.common.testing.ui.espresso.matcher.ViewMatchers.isDisplayed;
import static com.google.android.apps.common.testing.ui.espresso.matcher.ViewMatchers.withId;
import static com.google.android.apps.common.testing.ui.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import net.humblegames.bodylasticscalculator.ActivityMain;
import net.humblegames.bodylasticscalculator.R;
import net.humblegames.bodylasticscalculator.applogic.BandSystem;

import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.junit.After;
import org.junit.Before;

import android.app.Activity;
import android.test.ActivityInstrumentationTestCase2;
import android.util.Log;

public class MenuNavigationTestExperiment extends
        ActivityInstrumentationTestCase2<ActivityMain> 

    private String TAG = getClass().getSimpleName();
    private Activity activity;

    public MenuNavigationTestExperiment() 
        super(ActivityMain.class);
    


    @Before
    public void setUp() throws Exception 
        Log.d(TAG, "SETUP");
        activity = getActivity();

        super.setUp();
    

    @After
    public void tearDown() throws Exception 
        Log.d(TAG, "TEARDOWN");

        super.tearDown();
    


    public void testFirst() 
        Log.d(TAG, "testFirst");

        clickMenuItem("Select band system");
        onData(allOf(is(instanceOf(BandSystem.class)), hasName("MMA Training")))
        .onChildView(withId(R.id.label)).perform(click());

        clickMenuItem("About");
        onView(withId(R.id.about_webview)).check(matches(isDisplayed()));
    

    public void testSecond() 
        Log.d(TAG, "testSecond");
        // this test will not run
    


    public void testThird() 
        Log.d(TAG, "testThird");
        // this test will pass
    

    // ------------------ HELPER METHODS ---------------------------------------

    private void clickMenuItem(String menuItem) 
        Log.d(TAG, "clickMenuItem");

        openActionBarOverflowOrOptionsMenu(getInstrumentation()
                .getTargetContext());

        onView(withText(menuItem)).perform(click());
    

    private Matcher<BandSystem> hasName(final String name) 
        Log.d(TAG, "hasName");

        return new BaseMatcher<BandSystem>() 
            @Override
            public boolean matches(final Object item) 
                final BandSystem foo = (BandSystem) item;
                return name == foo.getName();
            

            @Override
            public void describeTo(final Description description) 
                description.appendText("getName should return ").appendValue(
                        name);
            
        ;
    


错误跟踪:

java.lang.RuntimeException: Could not launch intent Intent  act=android.intent.action.MAIN flg=0x10000000 cmp=net.humblegames.bodylasticscalculator/.ActivityMain  within 45 seconds. Perhaps the main thread has not gone idle within a reasonable amount of time? There could be an animation or something constantly repainting the screen. Or the activity is doing network calls on creation? See the threaddump logs. For your reference the last time the event queue was idle before your activity launch request was 1395582828351 and and now the last time the queue went idle was: 1395582830169. If these numbers are the same your activity might be hogging the event queue.
at com.google.android.apps.common.testing.testrunner.GoogleInstrumentation.startActivitySync(GoogleInstrumentation.java:277)
at android.test.InstrumentationTestCase.launchActivityWithIntent(InstrumentationTestCase.java:119)
at android.test.InstrumentationTestCase.launchActivity(InstrumentationTestCase.java:97)
at android.test.ActivityInstrumentationTestCase2.getActivity(ActivityInstrumentationTestCase2.java:104)
at net.humblegames.bodylasticscalculator.test.MenuNavigationTestExperiment.setUp(MenuNavigationTestExperiment.java:42)
at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:191)
at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:176)
at android.test.InstrumentationTestRunner.onStart(InstrumentationTestRunner.java:554)
at com.google.android.apps.common.testing.testrunner.GoogleInstrumentationTestRunner.onStart(GoogleInstrumentationTestRunner.java:167)
at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:1701)


更新 #1

我为该项目创建了一个干净的 Eclipse 项目和一个干净的 Espresso 测试。我能够在 Espresso 测试运行期间重现相同的错误(但我仍然不确定此错误的原因是否与我的真实应用程序中的相同):

目标应用有 3 个活动(主要、第二个、第三个)。用户通过单击菜单项在它们之间导航:主要活动开始第二个,第二个活动开始第三个。

我发现,如果我在第一次测试中调用 clickMenuItem()(见下文)和 Thread.sleep(500),这会导致在第二次测试之前 getActivity() 调用中的 setUp() 崩溃。如果睡眠超时小于 0.5 秒,则不会发生崩溃。

同时如果调用 Thread.sleep(10000) 并且不调用 clickMenuItem() 也不会崩溃。它会在第一个测试中成功休眠 10 秒,并且也将通过第二个测试。


测试代码:
public class MainActivityTest extends
        ActivityInstrumentationTestCase2<MainActivity> 

    private String TAG = getClass().getSimpleName();
    private Activity activity;

    public MainActivityTest() 
        super(MainActivity.class);    

    public void setUp() throws Exception 
        Log.d(TAG, "SETUP");
        activity = getActivity();

        super.setUp();       

    public void tearDown() throws Exception 
        Log.d(TAG, "TEARDOWN");

        super.tearDown();    


    public void testFirst() throws InterruptedException 
        Log.d(TAG, "testFirst");

        clickMenuItem("Start second activity");

        clickMenuItem("Start third activity");

        Thread.sleep(500);
    

    public void testSecond() 
        Log.d(TAG, "testSecond");
        // this test will not run     


    private void clickMenuItem(String menuItem) 
        Log.d(TAG, "clickMenuItem");

        openActionBarOverflowOrOptionsMenu(getInstrumentation()
                .getTargetContext());

        onView(withText(menuItem)).perform(click());          


目标代码:

主要活动:

public class MainActivity extends Activity 

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);         

    @Override
    public boolean onCreateOptionsMenu(Menu menu) 
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;        

    @Override
    public boolean onOptionsItemSelected(MenuItem item) 
        // Handle item selection
        switch (item.getItemId()) 
        case R.id.action_start_second_activity:
            Intent intent = new Intent(this, SecondActivity.class);
            startActivity(intent);
            return true;

        default:
            return super.onOptionsItemSelected(item);
                       

第二个活动:

public class SecondActivity extends Activity 

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);       

    @Override
    public boolean onCreateOptionsMenu(Menu menu) 
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.second, menu);
        return true;        

    @Override
    public boolean onOptionsItemSelected(MenuItem item) 
        // Handle item selection
        switch (item.getItemId()) 
        case R.id.action_start_third_activity:
            Intent intent = new Intent(this, ThirdActivity.class);
            startActivity(intent);
            return true;

        default:
            return super.onOptionsItemSelected(item);
                  

第三个活动:

public class ThirdActivity extends Activity 

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_third);    

    @Override
    public boolean onCreateOptionsMenu(Menu menu) 
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.third, menu);
        return true;    

【问题讨论】:

我认为您必须粘贴您的活动代码。您的测试看起来不错。 @StevenMarkFord,感谢您的建议。我在干净的应用程序和测试项目中重现了相同的错误,并在初始问题的末尾发布了源代码(请参阅更新 #1)。 如果你完全取消睡眠,它会起作用吗?为什么首先需要调用 sleep? 是的,它可以在没有睡眠调用的情况下工作(它也适用于超时 【参考方案1】:

尝试以下方法:

@Before
public void setUp() throws Exception 
    super.setUp();
    Log.d(TAG, "SETUP");
    activity = getActivity();

【讨论】:

我试过了。不幸的是,它没有帮助。仍然有同样的错误。【参考方案2】:

我找到了解决方法。我在 tearDown() 方法中添加了多次点击后退按钮的代码,以关闭所有以前打开的活动。不幸的是,我没有找到另一种方法来关闭 Espresso 中所有打开的活动。 Robotium 有一个非常方便的方法来实现这个目的,solo.finishOpenedActivities()。

public void tearDown() throws Exception 
    Log.d(TAG, "TEARDOWN");

    goBackN();

    super.tearDown();


private void goBackN() 
    final int N = 10; // how many times to hit back button
    try 
        for (int i = 0; i < N; i++)
            Espresso.pressBack();
     catch (com.google.android.apps.common.testing.ui.espresso.NoActivityResumedException e) 
        Log.e(TAG, "Closed all activities", e);
    

【讨论】:

【参考方案3】:

pressBack 解决方案对我不起作用,但我找到了另一个解决方案:

@Override
protected void tearDown() throws Exception 
    closeAllActivities(getInstrumentation());
    super.tearDown();


public static void closeAllActivities(Instrumentation instrumentation) throws Exception 
    final int NUMBER_OF_RETRIES = 100;
    int i = 0;
    while (closeActivity(instrumentation)) 
        if (i++ > NUMBER_OF_RETRIES) 
            throw new AssertionError("Limit of retries excesses");
        
        Thread.sleep(200);
    


public static <X> X callOnMainSync(Instrumentation instrumentation, final Callable<X> callable) throws Exception 
    final AtomicReference<X> retAtomic = new AtomicReference<>();
    final AtomicReference<Throwable> exceptionAtomic = new AtomicReference<>();
    instrumentation.runOnMainSync(new Runnable() 
        @Override
        public void run() 
            try 
                retAtomic.set(callable.call());
             catch (Throwable e) 
                exceptionAtomic.set(e);
            
        
    );
    final Throwable exception = exceptionAtomic.get();
    if (exception != null) 
        Throwables.propagateIfInstanceOf(exception, Exception.class);
        Throwables.propagate(exception);
    
    return retAtomic.get();


public static Set<Activity> getActivitiesInStages(Stage... stages) 
    final Set<Activity> activities = Sets.newHashSet();
    final ActivityLifecycleMonitor instance = ActivityLifecycleMonitorRegistry.getInstance();
    for (Stage stage : stages) 
        final Collection<Activity> activitiesInStage = instance.getActivitiesInStage(stage);
        if (activitiesInStage != null) 
            activities.addAll(activitiesInStage);
        
    
    return activities;


private static boolean closeActivity(Instrumentation instrumentation) throws Exception 
    final Boolean activityClosed = callOnMainSync(instrumentation, new Callable<Boolean>() 
        @Override
        public Boolean call() throws Exception 
            final Set<Activity> activities = getActivitiesInStages(Stage.RESUMED,
                    Stage.STARTED, Stage.PAUSED, Stage.STOPPED, Stage.CREATED);
            activities.removeAll(getActivitiesInStages(Stage.DESTROYED));
            if (activities.size() > 0) 
                final Activity activity = activities.iterator().next();
                activity.finish();
                return true;
             else 
                return false;
            
        
    );
    if (activityClosed) 
        instrumentation.waitForIdleSync();
    
    return activityClosed;

【讨论】:

【参考方案4】:

这似乎对我有用,可以关闭堆栈中除第一个之外的所有活动。

   @After
   @Override
   public void tearDown() throws Exception
   
      Intent intent = new Intent(getActivity(), getActivity().getClass());
      intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); // Removes other Activities from stack
      intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
      myActivity.startActivity(intent);
      super.tearDown();

   

【讨论】:

以上是关于getActivity() 调用导致 RuntimeException:无法启动意图 Intent act=android.intent.action.MAIN的主要内容,如果未能解决你的问题,请参考以下文章

从`onCreateView`调用`getActivity()`是不是安全?

为啥我们可以在 onActivityCreated 之前运行的 onCreateView 中调用 getActivity()?

Android Fragment中调用getActivity为null的问题

在片段中调用 getActivity() 以使其不返回 null 的最佳做法是啥? [复制]

尝试在 getActivity() 上调用虚拟方法 'java.lang.String android.content.Context.getPackageName()

在Fragment里面调用getActivity()报null