![](/img/trans.png)
[英]android.view.AbsSavedState$1 cannot be cast to android.widget.ScrollView$SavedState
[英]Crash when restoring Android state - AbsSavedState cannot be cast
我從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
及其基類的Xamarin.Forms源代碼,我也沒有看到任何狀態保存代碼。 在Stack Overflow的許多問題中,聲稱問題可能是由重復的android:id
引起的,但是正如我上面提到的,我沒有自定義布局。
更新
我決定深入調查,開始驗證整個州的保全機制。 以下是我的發現:
(viewId, state)
。 事實證明,所有視圖都將狀態保留為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}]}
CompoundButtons
( Switch
基類): MainPage
和modal頁面。 畢竟我認為這可能是這種可能的不匹配,而恢復狀態是由重復的ID以某種方式引起的。 我決定寫一段代碼用ids打印整個層次結構。 您可以在下面看到MainPage
和模態頁面,總共3個開關。 但是,這里沒有重復。 -- 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
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;
它看起來很好,除非有一些競爭條件。 我的想法已經不多了。
更新2
我OnRestoreSavedInstance
Switch
控制並覆蓋了OnRestoreSavedInstance
和奇怪的事情,它從未在我的設備OnRestoreSavedInstance
。 但是,會調用OnSaveInstanceState
。 請注意,我正確模擬了狀態恢復(它在MainActivity
調用,但不會傳播到Switch
)。
我找到了它以這種方式表現的原因。 請查看Android的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。 因此,在創建頁面后,它會將ID設置為1
到n
。 在另一次娛樂之后(例如在旋轉屏幕之后),它將id從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
首先驗證此TextView
是否成功,而CompoundButton
則不成功。 也許這是Android的缺陷。 但是我仍然沒有看到狀態如何不匹配,並且AbsSavedState
已經傳遞給CompoundButton
而不是CompoundButton.SavedState
。
這並不能解決您的整體問題,但我相信我可以對您的更新3部分有所了解。
首先讓我重新陳述你的問題:為什么TextView
和CompoundButton
有兩種不同的策略來實現onRestoreInstanceState()
?
TextView根據傳遞給它的特定Parcelable
執行條件邏輯:
@Override public void onRestoreInstanceState(Parcelable state) { if (!(state instanceof SavedState)) { super.onRestoreInstanceState(state); return; } SavedState ss = (SavedState) state; super.onRestoreInstanceState(ss.getSuperState()); ... }
雖然CompoundButton沒有:
@Override public void onRestoreInstanceState(Parcelable state) { SavedState ss = (SavedState) state; super.onRestoreInstanceState(ss.getSuperState()); ... }
原因是TextView
和CompoundButton
有兩種不同的策略來實現onSaveInstanceState()
,因此每個類都有相應的策略來恢復狀態。
TextView可以從onSaveInstanceState()
返回兩種不同的類型:
@Override public Parcelable onSaveInstanceState() { Parcelable superState = super.onSaveInstanceState(); ... if (freezesText || hasSelection) { SavedState ss = new SavedState(superState); ... return ss; } return superState; }
TextView只會在super
調用不保存所需內容的情況下返回自己的自定義SavedState
類(即,當TextView被要求凍結其文本或有選擇時)。 在所有其他情況下,它只是委托super
調用並直接返回。
由於onRestoreInstanceState()
將接收返回的onSaveInstanceState()
,因此當TextView收到super
返回值或其自己的SavedState
時,它需要能夠工作。
另一方面,CompoundButton只能從onSaveInstanceState()
返回一個類型:
@Override public Parcelable onSaveInstanceState() { Parcelable superState = super.onSaveInstanceState(); SavedState ss = new SavedState(superState); ss.checked = isChecked(); return ss; }
因為我們知道傳入的state
對象將始終是SavedState
類型,所以我們不必執行任何條件邏輯。 我們可以把它扔掉。
希望這個答案提供了其他答案者可以建立的基礎,並可能最終回答您的主要問題。
畢竟看起來在保留狀態下必須有重復的id,但是我沒有看到任何合理的解釋原因。 我也不能在我的設備上重現它。 正如我上面所描述的:
Xamarin.Forms通過增加計數器自動設置ID。 因此,在創建頁面后,它會將ID設置為
1
到n
。 在另一次娛樂之后(例如在旋轉屏幕之后),它將id從n+1
為2n+1
。 因此,沒有控件可以恢復其狀態,因為在保留狀態時它將被保存為id = x的狀態,但是在重新創建Activity之后,此控件將具有不同的id。
不過我找到了一種解決方法來阻止崩潰。
using Android.Content;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(Switch), typeof(MyApp.Droid.CustomRenderers.CustomSwitchRenderer))]
namespace MyApp.Droid.CustomRenderers
{
public class CustomSwitchRenderer : SwitchRenderer
{
public CustomSwitchRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Switch> e)
{
base.OnElementChanged(e);
if (this.Control != null)
{
this.Control.Id = -1;
this.Control.SaveEnabled = false;
}
}
}
}
它禁用所有Switch
控件的狀態保留。 以防萬一我也設置Id = -1
來覆蓋Xamarin指定的id。 -1
是Android中的常量,表示“無id”。
這種解決方法不會破壞Xamarin.Forms
狀態保留,因為在Page
重建狀態依賴於綁定,而不是Android的機制。
但是,如果您希望在不禁用狀態保留的情況下使其工作。 您可以設置一些在運行之間保持不變的大ID。 當然,您需要為每個Switch
設置不同的ID,因此您可能需要創建自定義Switch
並添加一些屬性,如AndroidId
。 請注意,id應低於0x00ffffff
且足夠大以避免與Xamarin的自動生成的ID沖突。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.