“BadParcelableException: ClassNotFoundException when unmarshalling ” while using Parcel.read method that has a ClassLoader as argument

前端 未结 4 1684
我寻月下人不归
我寻月下人不归 2020-12-02 10:16

Given a custom class org.example.app.MyClass implements Parcelable, I want to write a List to a Parcel. I did the marshalling with

相关标签:
4条回答
  • 2020-12-02 10:51

    Don't unmarshall a custom class (i.e. one provided by your application and not by the Android framework) with the framework class loader that is used when you give null as the ClassLoader argument. Use the application class loader:

    parcel.readList(myclassList, getClass().getClassLoader());
    

    Whenever a Parcel.read*() method also has a ClassLoader as an argument (e.g. Parcel.readList(List outVal, ClassLoader loader)) and you want to read an application class from a Parcel, use the application class loader that can be retrieved with getClass().getClassLoader().

    Background: Android comes with two class loaders: the system class loader, that is able to load all system classes but the ones provided by your application; and the application class loader, which has set the system class loader as its parent and therefore is able to load all classes. If you give null as the class loader, Parcel.readParcelableCreator() will use the framework class loader, causing a ClassNotFoundException.

    Thanks to alexanderblom for providing the hint that led me on the right track.

    0 讨论(0)
  • 2020-12-02 10:55

    I believe a more correct form of this would be:

    List<MyClass> myclassList = new ArrayList<MyClass>();
    parcel.readList(myclassList, MyClass.class.getClassLoader());
    

    Because here you are explicitly using the class loader for the List's generic type.

    0 讨论(0)
  • 2020-12-02 11:02

    You can get this error if you subclass a custom View incorrectly.

    Assume you are subclassing BottomNavigationView and you want to add saved state to the superstate in onSaveInstanceState().

    An incorrect implementation of the Parcelable boilerplate (copied from another class or a template) would look like this:

    static class State extends BaseSavedState {
    
        Bundle stateBundle;
    
        //incorrect as super state uses ClassLoaderCreator
        public static final Creator<State> CREATOR = new Creator<State>() {
            public State createFromParcel(Parcel in) {
                return new State(in);
            }
    
            public State[] newArray(int size) {
                return new State[size];
            }
        };
    
        State(Parcel source) {
            super(source);
            this.stateBundle = source.readBundle(getClass().getClassLoader());
        }
    
        State(Parcelable superState) {
            super(superState);
        }
    
        @Override
        public void writeToParcel(Parcel out, int flags) {
            super.writeToParcel(out, flags);
            out.writeBundle(stateBundle);
        }
    }
    

    This would not work as the superstate from BottomNavigationView requires a classloader. Instead you should carefully inspect the SavedState class from BottomNavigationView and use the correct ClassLoaderCreator rather than Creator:

    static class State extends AbsSavedState {
    
        Bundle stateBundle;
    
        public static final Creator<State> CREATOR = new ClassLoaderCreator<State>() {
            public State createFromParcel(Parcel in, ClassLoader classLoader) {
                return new State(in, classLoader);
            }
    
            @Override
            public State createFromParcel(Parcel source) {
                return new State(source, null);
            }
    
            public State[] newArray(int size) {
                return new State[size];
            }
        };
    
        State(Parcel source, ClassLoader classLoader) {
            super(source, classLoader);
            this.stateBundle = source.readBundle(classLoader);
        }
    
        State(Parcelable superState) {
            super(superState);
        }
    
        @Override
        public void writeToParcel(Parcel out, int flags) {
            super.writeToParcel(out, flags);
            out.writeBundle(stateBundle);
        }
    }
    

    Note that extending android.support.v4.view.AbsSavedState may be a better choice than BaseSavedState or android.view.AbsSavedState since it will allow you to pass a class loader to the superclass:

    SavedState(Parcel source, ClassLoader classLoader) {
        super(source, classLoader); //available in android.support.v4.view.AbsSavedState
        this.stateBundle = source.readBundle(classLoader);
    }
    
    0 讨论(0)
  • 2020-12-02 11:05

    I have faced the same problem and instead of parcleable I used bundle

    intent.setExtrasClassLoader(getClassLoader());
    /* Send optional extras */
    Bundle bundle = new Bundle();
    bundle.putParcelable("object", mObject);
    bundle.putString("stringVal", stringVal);
    
    intent.putExtra("bundle",bundle);
    

    And in my intent class I used this to retrieve the value:

    Bundle oldBundle = intent.getBundleExtra("bundle");
    ResultReceiver receiver = oldBundle.getParcelable("object");
    String stringVal = oldBundle.getString("stringVal");
    intent.setExtrasClassLoader(getClassLoader());
    

    Hope it will be helpful for some.

    0 讨论(0)
提交回复
热议问题