异常说明

今天在测试工程的时候,出现如下异常:

1
2
3
4
5
Caused by android.os.BadParcelableException: ClassNotFoundException when unmarshalling:
...
android.os.Parcel.readParcelableCreator (Parcel.java:2203)
android.view.View$BaseSavedState. (View.java:18696)
...

异常解析

可以看到报错在ParcelreadParcelableCreator方法中,而且是因为类找不到导致的,所以我们查看这个方法的代码,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
public final Parcelable.Creator<?> readParcelableCreator(ClassLoader loader) {
...
synchronized (mCreators) {
HashMap<String,Parcelable.Creator<?>> map = mCreators.get(loader);
if (map == null) {
map = new HashMap<>();
mCreators.put(loader, map);
}
creator = map.get(name);
if (creator == null) {
try {
// If loader == null, explicitly emulate Class.forName(String) "caller
// classloader" behavior.
ClassLoader parcelableClassLoader =
(loader == null ? getClass().getClassLoader() : loader);
// Avoid initializing the Parcelable class until we know it implements
// Parcelable and has the necessary CREATOR field. http://b/1171613.
Class<?> parcelableClass = Class.forName(name, false /* initialize */,
parcelableClassLoader);
if (!Parcelable.class.isAssignableFrom(parcelableClass)) {
throw new BadParcelableException("Parcelable protocol requires that the "
+ "class implements Parcelable");
}
Field f = parcelableClass.getField("CREATOR");
if ((f.getModifiers() & Modifier.STATIC) == 0) {
throw new BadParcelableException("Parcelable protocol requires "
+ "the CREATOR object to be static on class " + name);
}
Class<?> creatorType = f.getType();
if (!Parcelable.Creator.class.isAssignableFrom(creatorType)) {
// Fail before calling Field.get(), not after, to avoid initializing
// parcelableClass unnecessarily.
throw new BadParcelableException("Parcelable protocol requires a "
+ "Parcelable.Creator object called "
+ "CREATOR on class " + name);
}
creator = (Parcelable.Creator<?>) f.get(null);
}
catch (IllegalAccessException e) {
Log.e(TAG, "Illegal access when unmarshalling: " + name, e);
throw new BadParcelableException(
"IllegalAccessException when unmarshalling: " + name);
}
catch (ClassNotFoundException e) {
Log.e(TAG, "Class not found when unmarshalling: " + name, e);
throw new BadParcelableException(
"ClassNotFoundException when unmarshalling: " + name);
}
catch (NoSuchFieldException e) {
throw new BadParcelableException("Parcelable protocol requires a "
+ "Parcelable.Creator object called "
+ "CREATOR on class " + name);
}
if (creator == null) {
throw new BadParcelableException("Parcelable protocol requires a "
+ "non-null Parcelable.Creator object called "
+ "CREATOR on class " + name);
}

map.put(name, creator);
}
}

return creator;
}

从上面的部分代码中可以看到第14行和第44行,就是导致异常以及抛出异常的根源,可以看出,如果传入的ClassLoadernull,那么使用的ClassLoader就为getClass().getClassLoader()也就是Parcel类的ClassLoader,也就是java.lang.BootClassLoader也可以称之Framework ClassLoader(该类加载器加载不了我们的自定义类,后面会有说明,现在先跳过),所以现在我们知道了,如果ClassLoader传的是null,那么是加载不了我们需要加载的自定义目标类的,所以现在的问题是这个ClassLoader是什么时候传入的,我们继续看Parcel类,如下:

1
2
3
4
5
6
7
8
9
10
11
12
public final <T extends Parcelable> T readParcelable(ClassLoader loader) {
Parcelable.Creator<?> creator = readParcelableCreator(loader);
if (creator == null) {
return null;
}
if (creator instanceof Parcelable.ClassLoaderCreator<?>) {
Parcelable.ClassLoaderCreator<?> classLoaderCreator =
(Parcelable.ClassLoaderCreator<?>) creator;
return (T) classLoaderCreator.createFromParcel(this, loader);
}
return (T) creator.createFromParcel(this);
}

上面的代码不难看出,ClassLoader使用的是readParcelable方法传入的ClassLoader,所以我们在使用readParcelable方法的时候,如果传入的参数为null或者传入java.lang.BootClassLoader,都会使readParcelableCreator方法加载自定义类的时候抛出ClassNotFound异常,从而导致android.os.BadParcelableException: ClassNotFoundException when unmarshalling异常产生。

关于Android的ClassLoader

对于Android应用来说至少有两个ClassLoader,一个是Android系统启动时候创建的,还有一个是APK启动时候创建的,他们分别是java.lang.BootClassLoader(系统启动)dalvik.system.PathClassLoader(APK启动),其中BootClassLoader加载Android类,PathClassLoader加载应用DEX中的类。需要注意的是PathClassLoader继承BootClassLoader,所以PathClassLoader也可以加载Android相关的类。就像上面的例子,Parcel类是Android类所以,获取到的ClassLoaderBootClassLoader。还有需要注意的是Activity.class.getClassLoader()BootClassLoader,而继承Activity的类的ClassLoaderPathClassLoader,比如FragmentActivity,AppCompatActivity等。