我从Crashlytics收到有关Xamarin.Forms项目中以下崩溃的通知:
Fatal Exception: java.lang.RuntimeException: Unable to start activity
ComponentInfo{com.xxx.xxx/xxxxx.MainActivity}:
java.lang.ClassCastException: android.view.AbsSavedState$1 cannot be cast to
android.widget.CompoundButton$SavedState
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2957)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3032)
at android.app.ActivityThread.-wrap11(Unknown Source)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1696)
at android.os.Handler.dispatchMessage(Handler.java:105)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6944)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:327)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1374)
Caused by java.lang.ClassCastException:
android.view.AbsSavedState$1 cannot be cast to android.widget.CompoundButton$SavedState
at android.widget.CompoundButton.onRestoreInstanceState(CompoundButton.java:619)
at android.view.View.dispatchRestoreInstanceState(View.java:18884)
at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:3936)
at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:3936)
at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:3936)
at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:3936)
at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:3936)
at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:3936)
at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:3936)
at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:3936)
at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:3936)
at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:3936)
at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:3936)
at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:3936)
at android.view.View.restoreHierarchyState(View.java:18862)
at com.android.internal.policy.PhoneWindow.restoreHierarchyState(PhoneWindow.java:2248)
at android.app.Activity.onRestoreInstanceState(Activity.java:1153)
at android.app.Activity.performRestoreInstanceState(Activity.java:1108)
at android.app.Instrumentation.callActivityOnRestoreInstanceState(Instrumentation.java:1266)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2930)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3032)
at android.app.ActivityThread.-wrap11(Unknown Source)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1696)
at android.os.Handler.dispatchMessage(Handler.java:105)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6944)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:327)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1374)
CompoundButton
是Switch
的基类,并且我在主页上有两个开关。 SwitchRenderer
源代码及其基类,但也看不到任何状态保存代码。 在有关Stack Overflow的许多问题中,都声称该问题可能是由重复的
android:id
引起的,但是如上所述,我没有自定义布局。更新
我决定进行更深入的调查,并开始验证整个状态的保存机制。以下是我的发现:
(viewId, state)
的形式存储。事实证明,所有 View 都将状态保留为AbsSavedState
,而CompoundButton
存储CompoundButton.SavedState
。因此,我的猜测是使用某种不正确的状态来还原CompoundButton
。样本状态:{Bundle[{ android:viewHierarchyState=Bundle[{android:views= {1=android.view.AbsSavedState$1@e738983,2=android.view.AbsSavedState$1@e738983, 3=android.view.AbsSavedState$1@e738983, 4=android.view.AbsSavedState$1@e738983, 5=android.view.AbsSavedState$1@e738983, 6=android.view.AbsSavedState$1@e738983, 7=android.view.AbsSavedState$1@e738983, 8=android.view.AbsSavedState$1@e738983, 9=android.view.AbsSavedState$1@e738983, 10=android.view.AbsSavedState$1@e738983, 11=android.view.AbsSavedState$1@e738983, 12=android.view.AbsSavedState$1@e738983, 13=android.view.AbsSavedState$1@e738983, 14=android.view.AbsSavedState$1@e738983, 15=android.view.AbsSavedState$1@e738983, 16=android.view.AbsSavedState$1@e738983, 17=android.view.AbsSavedState$1@e738983, 18=android.view.AbsSavedState$1@e738983, 19=android.view.AbsSavedState$1@e738983, 20=android.view.AbsSavedState$1@e738983, 21=android.view.AbsSavedState$1@e738983, 22=android.view.AbsSavedState$1@e738983, 23=android.view.AbsSavedState$1@e738983, 24=CompoundButton.SavedState{26e683d checked=false}, 25=android.view.AbsSavedState$1@e738983, 26=CompoundButton.SavedState{8f32832 checked=true}, 27=android.view.AbsSavedState$1@e738983, 28=android.view.AbsSavedState$1@e738983, 29=android.view.AbsSavedState$1@e738983, 30=android.view.AbsSavedState$1@e738983, 31=android.view.AbsSavedState$1@e738983, 32=android.view.AbsSavedState$1@e738983, 33=android.view.AbsSavedState$1@e738983, 34=android.view.AbsSavedState$1@e738983, 35=android.view.AbsSavedState$1@e738983, 36=android.view.AbsSavedState$1@e738983, 37=android.view.AbsSavedState$1@e738983, 16908290=android.view.AbsSavedState$1@e738983, 2131558525=android.view.AbsSavedState$1@e738983, 2131558526=android.view.AbsSavedState$1@e738983}}], android:lastAutofillId=1073741825, android:fragments=android.app.FragmentManagerState@969a700}]}
- I've got
CompoundButtons
(base class forSwitch
) on two pages:MainPage
and modal page. After all I thought that maybe this possible mismatch while restoring state is caused by duplicated ids somehow. I decided to write a piece of code to print whole hierarchy with ids. Below you can seeMainPage
and modal page, in total 3 switches. However, there is no duplication here.
-- 16908290 - ContentFrameLayout ---- -1 - RelativeLayout ------ -1 - PlatformRenderer -------- 1 - PageRenderer ---------- -1 - DefaultRenderer ------------ -1 - DefaultRenderer -------------- 2 - ImageRenderer ------------ -1 - CustomScrollViewRenderer -------------- -1 - ScrollViewContainer ---------------- -1 - DefaultRenderer ------------------ -1 - DefaultRenderer -------------------- -1 - DefaultRenderer ---------------------- -1 - DefaultRenderer ------------------------ 3 - ImageRenderer ---------------------- 4 - LabelRenderer ---------------------- 5 - LabelRenderer ---------------------- -1 - DefaultRenderer ------------------------ 6 - ImageRenderer ------------------ -1 - DefaultRenderer -------------------- -1 - DefaultRenderer ---------------------- 7 - LabelRenderer ---------------------- 8 - LabelRenderer ---------------------- -1 - DefaultRenderer ------------------------ 9 - ImageRenderer ------------------ -1 - DefaultRenderer -------------------- -1 - DefaultRenderer ---------------------- -1 - DefaultRenderer ------------------------ -1 - GaugeChartRenderer ------------------------ 10 - LabelRenderer ------------------------ 11 - LabelRenderer ------------------------ -1 - GaugeChartRenderer ------------------------ 12 - LabelRenderer ------------------------ 13 - LabelRenderer ------------------ -1 - DefaultRenderer -------------------- 14 - LabelRenderer -------------------- 15 - LabelRenderer ------------------ -1 - LinearChartRenderer -------------------- 16 - LinearChart ------------------ -1 - DefaultRenderer -------------------- -1 - CustomButtonRenderer ---------------------- 17 - Button -------------------- -1 - CustomButtonRenderer ---------------------- 18 - Button -------------------- -1 - CustomButtonRenderer ---------------------- 19 - Button -------------------- -1 - CustomButtonRenderer ---------------------- 20 - Button -------------------- -1 - CustomButtonRenderer ---------------------- 21 - Button -------------------- -1 - CustomButtonRenderer ---------------------- 22 - Button ------------------ -1 - DefaultRenderer ------------------ -1 - DefaultRenderer -------------------- -1 - DefaultRenderer ---------------------- 23 - LabelRenderer ---------------------- 24 - LabelRenderer ---------------------- 25 - LabelRenderer ---------------------- 26 - LabelRenderer ---------------------- 27 - LabelRenderer -------------------- -1 - DefaultRenderer ---------------------- -1 - DefaultRenderer ------------------------ -1 - DefaultRenderer -------------------------- 33 - LabelRenderer -------------------------- 34 - LabelRenderer -------------------------- 35 - LabelRenderer ------------------ -1 - DefaultRenderer -------------------- -1 - CustomSwitchRenderer ---------------------- 28 - Switch -------------------- 29 - LabelRenderer -------------------- -1 - DefaultRenderer ---------------------- 36 - ImageRenderer ------------------ -1 - DefaultRenderer -------------------- -1 - CustomSwitchRenderer ---------------------- 30 - Switch -------------------- 31 - LabelRenderer ------------------ -1 - DefaultRenderer -------------------- 37 - ImageRenderer -------------------- -1 - CustomButtonRenderer ---------------------- 32 - Button -------- 44 - ModalContainer ---------- -1 - View ---------- 38 - PageRenderer ------------ -1 - DefaultRenderer -------------- -1 - DefaultRenderer ---------------- -1 - DefaultRenderer ------------------ 39 - LabelRenderer ------------------ -1 - DefaultRenderer -------------------- 45 - ImageRenderer ---------------- -1 - SearchBarRenderer ------------------ 40 - SearchView -------------------- 16909226 - LinearLayout ---------------------- 16909225 - AppCompatTextView ---------------------- 16909227 - AppCompatImageView ---------------------- 16909229 - LinearLayout ------------------------ 16909231 - AppCompatImageView ------------------------ 16909232 - LinearLayout -------------------------- 16909233 - AutoCompleteTextView -------------------------- 16909228 - AppCompatImageView ------------------------ 16909321 - LinearLayout -------------------------- 16909230 - AppCompatImageView -------------------------- 16909235 - AppCompatImageView -------------- -1 - DefaultRenderer ---------------- -1 - ListViewRenderer ------------------ -1 - SwipeRefreshLayout -------------------- 41 - ListView ---------------------- -1 - Container ---------------------- -1 - Container ------------------------ -1 - DefaultRenderer -------------------- -1 - ImageView -------------- -1 - DefaultRenderer ---------------- -1 - DefaultRenderer ------------------ -1 - CustomSwitchRenderer -------------------- 42 - Switch ------------------ 43 - LabelRenderer
- Later I thought that maybe Xamarin's id generation mechanism fails after state restoration. But I checked it and after restoration it is properly increased. I even checked source code in Xamarin.Forms/Platform.cs:
internal static int GenerateViewId() { if ((int)Build.VERSION.SdkInt >= 17) return global::Android.Views.View.GenerateViewId(); if (s_id >= 0x00ffffff) s_id = 0x00000400; return s_id++; } static int s_id = 0x00000400;
It looks fine, unless there is some race condition. I'm running out of ideas.
Update 2
I subclassed Switch
control and overrode OnRestoreSavedInstance
and strange thing that it's never called on my devices. However, OnSaveInstanceState
is called. Please mind that I properly simulated state restoration (it is called in MainActivity
, but doesn't propagate to Switch
).
I found the reason why it behaves in this way. Please take a look at Android's implementation for View.dispatchRestoreState
:
protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container)
{
if (mID != NO_ID) {
Parcelable state = container.get(mID); // <--- HERE
if (state != null) {
// Log.i("View", "Restoreing #" + Integer.toHexString(mID)
// + ": " + state);
mPrivateFlags &= ~SAVE_STATE_CALLED;
onRestoreInstanceState(state);
if ((mPrivateFlags & SAVE_STATE_CALLED) == 0) {
throw new IllegalStateException(
"Derived class did not call super.onRestoreInstanceState()");
}
}
}
}
Xamarin.Forms通过增加计数器自动设置ID。因此,在创建页面后,它将ids从
1
设置为n
。再次娱乐(例如,旋转屏幕后)后,它将ids从n+1
设置为2n+1
。因此,没有一个控件能够恢复其状态,因为在保留状态时,它将被另存为id=x
的状态,但是,在重新创建Activity
之后,此控件将具有不同的id。因此,由于没有状态恢复,永远不会发生此崩溃。
更新3
我还注意到Android实现中的一些奇怪之处。
CompoundButton
具有以下实现:@Override
public void onRestoreInstanceState(Parcelable state) {
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
setChecked(ss.checked);
requestLayout();
}
但是,
TextView
(CompoundButton
的祖先)具有以下实现:@Override
public void onRestoreInstanceState(Parcelable state) {
if (!(state instanceof SavedState)) {
super.onRestoreInstanceState(state);
return;
}
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
// ...
}
如您所见,
TextView
首先验证此转换是否成功,CompoundButton
无效。也许这是Android中的缺陷。但是我仍然看不到状态不匹配以及AbsSavedState
传递给CompoundButton
而不是CompoundButton.SavedState
的可能性。 最佳答案
这不能解决您的总体问题,但是我相信我可以为您的 Update 3 部分提供一些帮助。
首先让我重申一下您的问题:为什么TextView
和CompoundButton
具有两种不同的实现onRestoreInstanceState()
的策略?
TextView根据传递给它的特定Parcelable
执行条件逻辑:
尽管CompoundButton不会:
原因是TextView
和CompoundButton
具有两种不同的实现onSaveInstanceState()
的策略,因此每个类都有相应的策略来恢复状态。
TextView可以从onSaveInstanceState()
返回两种不同的类型:
只有在SavedState
调用无法保存所需的所有内容的情况下(例如,当TextView被要求卡住其文本或具有选择内容时),TextView才会返回其自己的自定义super
类。在所有其他情况下,它仅委托(delegate)给super
调用并直接返回该调用。
由于onRestoreInstanceState()
将接收返回的任何onSaveInstanceState()
,因此TextView在接收super
返回值或自己的SavedState
时需要能够工作。
另一方面,CompoundButton只能从onSaveInstanceState()
返回一种类型:
因为我们知道传入的state
对象将始终为SavedState
类型,所以我们不必执行任何条件逻辑。我们可以将其抛弃。
希望此答案为其他回答者可以建立的基础,并可能最终回答您的主要问题。
关于android - 恢复Android状态时崩溃-无法转换AbsSavedState,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/53497612/