还原Android状态时崩溃-无法强制转换AbsSavedState

还原Android状态时崩溃-无法强制转换AbsSavedState,android,xamarin,xamarin.forms,Android,Xamarin,Xamarin.forms,我收到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.wi

我收到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
    的基类,我的主页上有两个开关
  • 我只有一项主要活动
  • 我在Xamarin.Android中使用Xamarin.Forms,没有任何自定义布局
  • 我没有任何关于国家保护/恢复的习惯行动
  • 我检查了Xamarin.Forms源代码中的
    SwitchRenderer
    及其基类,也没有看到任何状态保存代码
在许多关于堆栈溢出的问题中,有人声称问题可能是由重复的
android:id
引起的,但是正如我上面提到的,我没有自定义布局


更新

我决定深入调查,并开始验证整个国家保护机制。以下是我的调查结果:

  • 我发现整个视图层次结构是成对存储的
    (viewId,state)
    。此外,所有视图都将状态保留为
    AbsSavedState
    CompoundButton
    存储
    CompoundButton.SavedState
    。因此,我的猜测是,在还原
    CompoundButton
    时使用了不正确的状态。样本状态:
  • 后来我想也许Xamarin的id生成机制在状态恢复后失败了。但我检查了一下,在修复后,它被适当地增加了。我甚至检查了Xamarin.Forms/Platform.cs中的源代码:
  • Forms通过增加计数器自动设置ID。因此,在创建页面后,它将ID从
    1
    设置为
    n
    。在另一次重新创建之后(例如在旋转屏幕之后),它将ID从
    n+1
    设置为
    2n+1
    。因此,没有一个控件能够恢复其状态,因为在保存状态时,它将被保存为
    id=x
    的状态,但是在重新创建
    活动之后,此控件将具有不同的id

    因此,此崩溃不应发生,因为没有状态恢复


    更新3

    我还注意到Android的实现中有一些奇怪的东西
    CompoundButton
    具有以下实现:

    @Override
    public void onRestoreInstanceState(Parcelable state) {
        SavedState ss = (SavedState) state;
        super.onRestoreInstanceState(ss.getSuperState());
        setChecked(ss.checked);
        requestLayout();
    }
    
    @Override
    public void onRestoreInstanceState(Parcelable state) {
        if (!(state instanceof SavedState)) {
            super.onRestoreInstanceState(state);
            return;
        }
        SavedState ss = (SavedState) state;
        super.onRestoreInstanceState(ss.getSuperState());
    
        // ...
    }
    
    但是,
    TextView
    CompoundButton
    的祖先)有以下实现:

    @Override
    public void onRestoreInstanceState(Parcelable state) {
        SavedState ss = (SavedState) state;
        super.onRestoreInstanceState(ss.getSuperState());
        setChecked(ss.checked);
        requestLayout();
    }
    
    @Override
    public void onRestoreInstanceState(Parcelable state) {
        if (!(state instanceof SavedState)) {
            super.onRestoreInstanceState(state);
            return;
        }
        SavedState ss = (SavedState) state;
        super.onRestoreInstanceState(ss.getSuperState());
    
        // ...
    }
    

    如您所见,
    TextView
    首先验证此强制转换是否成功,
    CompoundButton
    不成功。也许这是安卓系统的一个缺陷。但是我仍然不明白状态不匹配的可能性,并且
    AbsSavedState
    被传递到
    CompoundButton
    而不是
    CompoundButton。SavedState
    这并不能解决您的整体问题,但我相信我可以对您的更新3部分有所帮助

    首先,让我重申您的问题:为什么
    TextView
    CompoundButton
    有两种不同的策略来实现
    onRestoreInstanceState()

    TextView根据传递给它的特定
    包裹执行条件逻辑:

    而CompoundButton不:


    原因是
    TextView
    CompoundButton
    有两种不同的策略来实现
    onSaveInstanceState()
    ,因此每个类都有相应的策略来恢复状态

    TextView可以从
    onSaveInstanceState()返回两种不同的类型:

    super
    调用没有保存所需的所有内容的情况下(例如,当要求TextView冻结其文本或有选择时),TextView将仅返回其自己的自定义
    SavedState
    类。在所有其他情况下,它只是委托给
    super
    调用并直接返回该调用

    由于
    onRestoreInstanceState()
    将接收返回的任何
    onSaveInstanceState()
    内容,因此当TextView接收到
    super
    返回值或其自身的
    SavedState
    时,它需要能够工作

    另一方面,CompoundButton只能从
    onSaveInstanceState()
    返回一种类型:

    因为我们知道传入的
    状态
    对象总是类型为
    SavedState
    ,所以我们不必执行任何条件逻辑。我们可以把它扔了就走



    希望这个答案能为其他回答者提供一个基础,也许最终会回答你的首要问题。

    毕竟,在保存状态中看起来必须有重复ID,但是我看不出有什么合理的解释。我都不能在我的设备上复制它。如上所述:

    Forms通过增加计数器自动设置ID。因此,在创建页面后,它将ID从
    1
    设置为
    n
    。在另一次重新创建之后(例如在旋转屏幕之后),它将ID从
    n+1
    设置为
    2n+1
    。因此,没有控件能够恢复其状态,因为在保留状态时,它将保存为id=x的状态,但是在重新创建活动后,此控件将具有不同的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;
                }
            }
        }
    }
    
    使用Android.Content;
    使用Xamarin.Forms;
    使用Xamarin.Forms.Platform.Android;
    [程序集:ExportRenderer(typeof(Switch)、typeof(MyApp.Droid.CustomRenderers.CustomSwitchRenderer))]
    命名空间MyApp.Droid.CustomRenders
    {
    公共类CustomSwitchRenderer:SwitchRenderer
    {
    公共CustomSwitchRenderer(上下文):基础(上下文)
    {
    }
    受保护的覆盖无效OnElementChanged(ElementChangedEventArgs e)
    {
    基础。一个要素发生变化(e);
    if(this.Control!=null)
    {
    this.Control.Id=-
    
    @Override
    public void onRestoreInstanceState(Parcelable state) {
        if (!(state instanceof SavedState)) {
            super.onRestoreInstanceState(state);
            return;
        }
        SavedState ss = (SavedState) state;
        super.onRestoreInstanceState(ss.getSuperState());
    
        // ...
    }
    
    @Override
    public void onRestoreInstanceState(Parcelable state) {
        if (!(state instanceof SavedState)) {
            super.onRestoreInstanceState(state);
            return;
        }
    
        SavedState ss = (SavedState) state;
        super.onRestoreInstanceState(ss.getSuperState());
        ...
    }
    
    @Override
    public void onRestoreInstanceState(Parcelable state) {
        SavedState ss = (SavedState) state;
        super.onRestoreInstanceState(ss.getSuperState());
        ...
    }
    
    @Override
    public Parcelable onSaveInstanceState() {
        Parcelable superState = super.onSaveInstanceState();
        ...
    
        if (freezesText || hasSelection) {
            SavedState ss = new SavedState(superState);
            ...
            return ss;
        }
    
        return superState;
    }
    
    @Override
    public Parcelable onSaveInstanceState() {
        Parcelable superState = super.onSaveInstanceState();
        SavedState ss = new SavedState(superState);
        ss.checked = isChecked();
        return ss;
    }
    
    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;
                }
            }
        }
    }