我从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的基类,并且我在主页上有两个开关。
  • 我只有一项主要 Activity 。
  • 我使用Xamarin.Forms,但Xamarin.Android中没有任何自定义布局。
  • 对于状态保存/恢复,我没有任何自定义操作。
  • 我检查了Xamarin.Forms的SwitchRenderer源代码及其基类,但也看不到任何状态保存代码。

  • 在有关Stack Overflow的许多问题中,都声称该问题可能是由重复的android:id引起的,但是如上所述,我没有自定义布局。

    更新

    我决定进行更深入的调查,并开始验证整个状态的保存机制。以下是我的发现:
  • 我发现整个 View 层次结构以对(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}]}
    1. I've got CompoundButtons (base class for Switch) 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 see MainPage 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
    1. 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 部分提供一些帮助。

    首先让我重申一下您的问题:为什么TextViewCompoundButton具有两种不同的实现onRestoreInstanceState()的策略?

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



    尽管CompoundButton不会:



    原因是TextViewCompoundButton具有两种不同的实现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/

    10-12 02:00