如何解决每秒重绘视图引发的Espresso AppNotIdleException问题?
解决Espresso因持续重绘导致的AppNotIdleException问题
首先得纠正你对noActivity()的误解:这个方法根本不是这么用的!它是ActivityScenario的API,用来获取一个无关联Activity的场景实例,和跳过空闲等待完全没关系,你之前的写法本身就有问题,这也是测试跳过点击停止按钮直接执行tearDown()的原因。
回到核心问题:Espresso默认会等待主线程空闲+所有注册的IdleResource空闲才会执行下一步操作,而你的仪表每秒重绘一次,相当于主线程一直在处理动画/绘制任务,Espresso会一直认为应用处于忙碌状态,最终超时抛出AppNotIdleException。下面给你几个可行的解决方案,按从简单到精准的顺序排列:
方案1:强制跳过空闲等待(快速解决单个测试)
如果你只是想让测试直接点击停止按钮,不管应用是否空闲,可以自定义一个ViewAction,不调用Espresso默认的等待空闲逻辑:
Kotlin代码
@Test fun testGaugeStartStop() { // 点击启动按钮 onView(withId(R.id.start_button)).perform(click()) // 自定义点击动作,不等待主线程空闲 onView(withId(R.id.stop_button)).perform(object : ViewAction { override fun getConstraints() = isDisplayed() override fun getDescription() = "Click stop button without waiting for idle" override fun perform(uiController: UiController, view: View) { // 直接触发点击,跳过loopMainThreadUntilIdle() view.performClick() } }) }
Java代码
@Test public void testGaugeStartStop() { onView(withId(R.id.start_button)).perform(click()); onView(withId(R.id.stop_button)).perform(new ViewAction() { @Override public Matcher<View> getConstraints() { return isDisplayed(); } @Override public String getDescription() { return "Click stop button without waiting for idle"; } @Override public void perform(UiController uiController, View view) { view.performClick(); } }); }
方案2:注册自定义IdleResource(精准控制空闲条件)
如果想让Espresso在停止按钮可见时就判定应用空闲(符合你业务逻辑的等待条件),可以编写一个自定义IdleResource,让它检查停止按钮的可见状态:
自定义IdleResource(Kotlin)
class StopButtonVisibleIdlingResource(private val activity: Activity) : IdlingResource { private var callback: ResourceCallback? = null override fun getName() = "StopButtonVisibleIdlingResource" override fun isIdleNow(): Boolean { val stopButton = activity.findViewById<Button>(R.id.stop_button) val isButtonVisible = stopButton != null && stopButton.isShown // 如果按钮可见,通知Espresso进入空闲状态 if (isButtonVisible) { callback?.onTransitionToIdle() } return isButtonVisible } override fun registerIdleTransitionCallback(callback: ResourceCallback?) { this.callback = callback } }
测试中使用(Kotlin)
@get:Rule val activityRule = ActivityScenarioRule(MainActivity::class.java) @Test fun testGaugeStartStop() { lateinit var idlingResource: StopButtonVisibleIdlingResource activityRule.scenario.onActivity { activity -> // 初始化并注册空闲资源 idlingResource = StopButtonVisibleIdlingResource(activity) IdlingRegistry.getInstance().register(idlingResource) } // 点击启动按钮 onView(withId(R.id.start_button)).perform(click()) // Espresso会自动等待直到空闲资源返回true(停止按钮可见),再执行点击 onView(withId(R.id.stop_button)).perform(click()) // 测试结束后注销资源,避免影响其他测试 IdlingRegistry.getInstance().unregister(idlingResource) }
Java版本的自定义IdleResource和测试代码
public class StopButtonVisibleIdlingResource implements IdlingResource { private final Activity activity; private ResourceCallback callback; public StopButtonVisibleIdlingResource(Activity activity) { this.activity = activity; } @Override public String getName() { return "StopButtonVisibleIdlingResource"; } @Override public boolean isIdleNow() { Button stopButton = activity.findViewById(R.id.stop_button); boolean isButtonVisible = stopButton != null && stopButton.isShown(); if (isButtonVisible) { if (callback != null) { callback.onTransitionToIdle(); } } return isButtonVisible; } @Override public void registerIdleTransitionCallback(ResourceCallback callback) { this.callback = callback; } }
测试代码:
@Rule public ActivityScenarioRule<MainActivity> activityRule = new ActivityScenarioRule<>(MainActivity.class); @Test public void testGaugeStartStop() { StopButtonVisibleIdlingResource idlingResource = null; activityRule.getScenario().onActivity(activity -> { idlingResource = new StopButtonVisibleIdlingResource(activity); IdlingRegistry.getInstance().register(idlingResource); }); onView(withId(R.id.start_button)).perform(click()); onView(withId(R.id.stop_button)).perform(click()); if (idlingResource != null) { IdlingRegistry.getInstance().unregister(idlingResource); } }
方案3:全局禁用自动空闲等待(不推荐,适合特殊场景)
如果你有很多测试都需要跳过空闲等待,可以在测试类的@Before方法中注册一个始终返回空闲的IdleResource,让Espresso认为应用一直处于空闲状态:
Kotlin代码
@Before fun setup() { val alwaysIdleResource = object : IdlingResource { override fun getName() = "AlwaysIdleResource" override fun isIdleNow() = true override fun registerIdleTransitionCallback(callback: ResourceCallback?) {} } IdlingRegistry.getInstance().register(alwaysIdleResource) } @After fun teardown() { IdlingRegistry.getInstance().clear() }
这种方法不推荐,因为它会让Espresso失去等待异步任务的能力,可能导致其他测试出现不稳定的情况,只适合所有测试都不需要等待空闲的场景。
内容的提问来源于stack exchange,提问作者elock
相关产品推荐
相关产品推荐

