代码之家  ›  专栏  ›  技术社区  ›  bmv2143

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

  •  13
  • bmv2143  · 技术社区  · 10 年前

    更新#1:更多信息添加到此文章末尾

    我是Android开发和测试的新手。

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

    块引用 java.lang.RuntimeException:无法在45秒内启动intent intent{act=android.intent.action.MAIN flg=0x100000000 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;    }
    }
    
    4 回复  |  直到 7 年前
        1
  •  7
  •   bmv2143    10 年前

    我找到了一个解决方法。我在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);
        }
    }
    
        2
  •  2
  •   Testing Singh    9 年前

    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;
    }
    
        3
  •  2
  •   harmanjd    9 年前

    这似乎对我关闭堆栈中除第一个活动之外的所有活动都有效。

       @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();
    
       }
    
        4
  •  1
  •   Shereef Marzouk    10 年前

    尝试以下操作:

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