#android #unmarshalling #onsaveinstancestate #onrestoreinstancestate #instancestate
Вопрос:
Похоже, что это несогласованное исключение всегда возникает, когда действие воссоздается с сохраненным состоянием экземпляра во время тестов эспрессо. Я воспроизвел его с помощью очень простого приложения для Android. Вот шаги:
- Создайте приложение для Android с двумя действиями, каждое из которых имеет одну кнопку. Кнопка на первом действии открывает второе действие. Кнопка на втором действии закрывает текущее действие.
- Добавьте тест эспрессо, который просто открывает первое действие, нажимает кнопку (чтобы открыть второе действие), затем нажимает кнопку второго действия (чтобы завершить второе действие и вернуться к первому действию).
- В вашем эмуляторе обязательно включите опцию «Не сохранять действия» в вашем эмуляторе.
В моем реальном приложении это зависит от активности, класс которой будет «неизвестен», чтобы вызвать отключение. В этом конкретном примере, по-видимому, это панель инструментов. Я обнаружил это, удалив определенные записи («androidx.жизненный цикл.BundlableSavedStateRegistry.key» и «android:viewHierarchyState») из сохраненного экземпляра указывают, что он будет обходить это исключение во время тестов эспрессо, но, конечно, тогда все восстанавливается неправильно. И я еще раз повторю, что это проблема только при выполнении тестов эспрессо. При выполнении тех же самых точных шагов теста вручную все выполняется правильно, и исключений нет.
Изменение версий sdk, похоже, тоже не помогает.
Это оно.
Вот весь кровавый код:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
// // This will cause the exception to not be thrown during espresso tests
// if(savedInstanceState != null) {
// savedInstanceState.remove("androidx.lifecycle.BundlableSavedStateRegistry.key");
// savedInstanceState.remove("android:viewHierarchyState");
// }
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.open_button).setOnClickListener(v -> startActivity(new Intent(MainActivity.this, ChildActivity.class)));
}
}
public class ChildActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_child);
findViewById(R.id.close_button).setOnClickListener(v -> finish());
}
}
@LargeTest
@RunWith(AndroidJUnit4.class)
public class MainActivityTest {
@Rule
public ActivityTestRule<MainActivity> mActivityTestRule = new ActivityTestRule<>(MainActivity.class);
@Test
public void test() {
onView(withId(R.id.open_button)).perform(click());
onView(withId(R.id.close_button)).perform(click());
onView(withId(R.id.open_button)).check(matches(isDisplayed()));
}
}
androidx.test.espresso.PerformException: Error performing 'single click - At Coordinates: 115, 272 and precision: 16, 16' on view 'view.getId() is <2131231192/com.example.myapplication:id/close_button>'.
at androidx.test.espresso.PerformException$Builder.build(PerformException.java:1)
at androidx.test.espresso.base.PerformExceptionHandler.handleSafely(PerformExceptionHandler.java:8)
at androidx.test.espresso.base.PerformExceptionHandler.handleSafely(PerformExceptionHandler.java:9)
at androidx.test.espresso.base.DefaultFailureHandler$TypedFailureHandler.handle(DefaultFailureHandler.java:4)
at androidx.test.espresso.base.DefaultFailureHandler.handle(DefaultFailureHandler.java:5)
at androidx.test.espresso.ViewInteraction.waitForAndHandleInteractionResults(ViewInteraction.java:8)
at androidx.test.espresso.ViewInteraction.desugaredPerform(ViewInteraction.java:11)
at androidx.test.espresso.ViewInteraction.perform(ViewInteraction.java:8)
at com.example.myapplication.MainActivityTest.mainActivityTest(MainActivityTest.java:31)
... 32 trimmed
Caused by: android.os.BadParcelableException: ClassNotFoundException when unmarshalling: androidx.appcompat.widget.Toolbar$SavedState
at android.os.Parcel.readParcelableCreator(Parcel.java:2839)
at android.os.Parcel.readParcelable(Parcel.java:2765)
at android.os.Parcel.readValue(Parcel.java:2668)
at android.os.Parcel.readSparseArrayInternal(Parcel.java:3118)
at android.os.Parcel.readSparseArray(Parcel.java:2351)
at android.os.Parcel.readValue(Parcel.java:2725)
at android.os.Parcel.readArrayMapInternal(Parcel.java:3037)
at android.os.BaseBundle.initializeFromParcelLocked(BaseBundle.java:288)
at android.os.BaseBundle.unparcel(BaseBundle.java:232)
at android.os.Bundle.getSparseParcelableArray(Bundle.java:1010)
at com.android.internal.policy.PhoneWindow.restoreHierarchyState(PhoneWindow.java:2133)
at android.app.Activity.onRestoreInstanceState(Activity.java:1135)
at android.app.Activity.performRestoreInstanceState(Activity.java:1090)
at android.app.Instrumentation.callActivityOnRestoreInstanceState(Instrumentation.java:1317)
at android.app.ActivityThread.handleStartActivity(ActivityThread.java:2953)
at android.app.servertransaction.TransactionExecutor.performLifecycleSequence(TransactionExecutor.java:180)
at android.app.servertransaction.TransactionExecutor.cycleToPath(TransactionExecutor.java:165)
at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:142)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:70)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808)
at android.os.Handler.dispatchMessage(Handler.java:106)
at androidx.test.espresso.base.Interrogator.loopAndInterrogate(Interrogator.java:14)
at androidx.test.espresso.base.UiControllerImpl.loopUntil(UiControllerImpl.java:8)
at androidx.test.espresso.base.UiControllerImpl.loopUntil(UiControllerImpl.java:1)
at androidx.test.espresso.base.UiControllerImpl.injectMotionEvent(UiControllerImpl.java:6)
at androidx.test.espresso.action.MotionEvents.sendUp(MotionEvents.java:7)
at androidx.test.espresso.action.MotionEvents.sendUp(MotionEvents.java:1)
at androidx.test.espresso.action.Tap.sendSingleTap(Tap.java:5)
at androidx.test.espresso.action.Tap.sendSingleTap$bridge(Unknown Source:0)
at androidx.test.espresso.action.Tap$1.sendTap(Tap.java:3)
at androidx.test.espresso.action.GeneralClickAction.perform(GeneralClickAction.java:6)
at androidx.test.espresso.ViewInteraction$SingleExecutionViewAction.perform(ViewInteraction.java:2)
at androidx.test.espresso.ViewInteraction.doPerform(ViewInteraction.java:25)
at androidx.test.espresso.ViewInteraction.doPerform$bridge(Unknown Source:0)
at androidx.test.espresso.ViewInteraction$1.call(ViewInteraction.java:2)
at androidx.test.espresso.ViewInteraction$1.call(ViewInteraction.java:1)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6669)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
Ответ №1:
Из того, что я могу сказать, вы должны вместо этого использовать ActivityScenario, а затем проверять состояния различных действий с помощью ActivityScenario.moveToState или ActivityScenario.recreate.