使用具有ClassLoader参数的Parcel.read方法时出现BadParcelableException: ClassNotFoundException when unmarshalling <myclass>。

56

假设有一个自定义类org.example.app.MyClass实现了Parcelable接口,我想将一个List<MyClass>写入Parcel。我使用下列代码进行编组:

 List<MyClass> myclassList = ...
 parcel.writeList(myclassList);
无论何时我尝试使用反序列化将该类解析为:

whenever I try to unmarshall the class with

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

出现了"BadParcelableException: ClassNotFoundException when unmarshalling org.example.app.MyClass"异常。

问题出在哪里?为什么找不到这个类?


1
我在不同的上下文中遇到了这个错误 - 在包含 Parcelable 的 bundle 上调用 bundle.keySet()。当针对相关代码运行单独的测试类时,测试通过,但是运行整个测试套件会导致 BadParcelableException。"解决"方法是在 bundle.keySet() 之前执行 bundle.setClassloader(MyClass.class.getClassLoader()) - Stan Kurdziel
这个回答解决了你的问题吗?BadParcelableException: 在取消编组时出现ClassNotFoundException - m0skit0
4个回答

112
不要使用框架类加载器对自定义类(即由应用程序而非Android框架提供的类)进行取消编组。当您将null作为ClassLoader参数时,应使用应用程序类加载器:
parcel.readList(myclassList, getClass().getClassLoader());

无论何时,当Parcel.read*()方法也有一个ClassLoader作为参数(例如Parcel.readList(List outVal, ClassLoader loader)),并且你想从Parcel中读取一个应用程序类时,请使用可以通过getClass().getClassLoader()检索的应用程序类加载器。

背景:Android自带两个类加载器:系统类加载器,可以加载所有系统类但不包括应用程序提供的类;应用程序类加载器将系统类加载器设置为其父级,因此可以加载所有类。如果您将null作为类加载器传递给Parcel.readParcelableCreator(),它将使用框架类加载器,从而导致ClassNotFoundException(类无法找到)

感谢alexanderblom提供的提示,帮助我找到了正确的解决方案。


2
这种情况为什么不是100%发生呢?我在一些用户设备上遇到了这个问题,但在我的设备上无法复现。 - Arka Prava Basu

19

我认为更正确的形式应该是:

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

因为你在这里明确使用了 List 的泛型类型的类加载器。


7
基本上这和我的答案一样。MyClass.classgetClass()没有区别,因为getClass()是在MyClass内部调用的,两者都返回相同的类对象。此外,你可以使用任何自定义类,只要它不是由框架提供的即可。 - Flow
9
我没有说你是不正确的,只是这种形式可能更加适当。另外请考虑,当您静态引用类时,调用 getClass() 是不必要的额外方法调用。 - Tanner Perrien
1
在我的情况下,它不起作用。使用如上所述的ClassLoader。 - SimpleCoder

8

我曾经遇到过同样的问题,但是我没有使用Parcleable而是使用了Bundle。

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

intent.putExtra("bundle",bundle);

在我的意图类中,我使用以下代码来获取值:

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

希望这对一些人有所帮助。

5

如果您不正确地子类化自定义视图,则可能会出现此错误。

假设您正在对BottomNavigationView进行子类化,并且希望在onSaveInstanceState()中向superstate添加已保存的状态。

Parcelable样板(从其他类或模板中复制)的不正确实现如下:

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);
    }
}

由于超级状态需要类加载器,因此这样做是行不通的BottomNavigationView。相反,您应该仔细检查BottomNavigationView中的SavedState类,并使用正确的ClassLoaderCreator而不是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);
    }
}

请注意,扩展android.support.v4.view.AbsSavedState可能比BaseSavedStateandroid.view.AbsSavedState更好,因为它允许您将类加载器传递给超类:
SavedState(Parcel source, ClassLoader classLoader) {
    super(source, classLoader); //available in android.support.v4.view.AbsSavedState
    this.stateBundle = source.readBundle(classLoader);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接