簡體   English   中英

恢復Android狀態時崩潰 - 無法強制轉換AbsSavedState

[英]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)
  • 不幸的是,我無法重現它。
  • 我檢查過CompoundButtonSwitch的基類,我的主頁上有兩個開關。
  • 我只有一個主要活動。
  • 我在Xamarin.Android中沒有任何自定義布局使用Xamarin.Forms。
  • 我對狀態保存/恢復沒有任何自定義操作。
  • 我檢查了SwitchRenderer及其基類的Xamarin.Forms源代碼,我也沒有看到任何狀態保存代碼。

在Stack Overflow的許多問題中,聲稱問題可能是由重復的android:id引起的,但是正如我上面提到的,我沒有自定義布局。


更新

我決定深入調查,開始驗證整個州的保全機制。 以下是我的發現:

  1. 我發現整個視圖層次結構都存儲為對(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}]}
  1. 我在兩個頁面上有CompoundButtonsSwitch基類): 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
  1. 后來我認為,在狀態恢復后,Xamarin的id生成機制可能會失敗。 但我檢查了它,並在恢復后適當增加。 我甚至檢查了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;

它看起來很好,除非有一些競爭條件。 我的想法已經不多了。


更新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設置為1n 在另一次娛樂之后(例如在旋轉屏幕之后),它將id從n+12n+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();
}

但是, TextViewCompoundButton的祖先)有這個實現:

@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部分有所了解。

首先讓我重新陳述你的問題:為什么TextViewCompoundButton有兩種不同的策略來實現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()); ... } 

原因是TextViewCompoundButton有兩種不同的策略來實現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設置為1n 在另一次娛樂之后(例如在旋轉屏幕之后),它將id從n+12n+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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM