今天发现了一个在对象序列化时的错误。
1 | E/Parcel: Class not found when unmarshalling: com.lijia.study.InventoryDetail |
日志只截取了核心的部分,意思说得很明显了,无法找到类InventoryDetail
进行序列化。最后定位错误为使用InventoryDetail
类作为HashMap的Value
,进行序列化失败。看下使用类:
1 | public class ChooseProductItem implements Parcelable { |
最后错误定位到:
1 | this.inventoryMap = in.readHashMap(HashMap.class.getClassLoader()); |
这里在读取 HashMap 的时候找不到类,导致序列化失败。
这里先讲述一下 ClassLoader。Android 中的 ClassLoader 类型分为两种类型,分别是系统 ClassLoader 和自定义 ClassLoader 。其中系统 ClassLoader 包括三种分别是 BootClassLoader、PathClassLoader 和 DexClassLoader。
- BootClassLoader:Android 系统启动时会使用 BootClassLoader 来预加载常用类。BootClassLoader 是一个单例类,需要注意的是 BootClassLoader 的访问修饰符是默认的,只有在同一个包中才可以访问,因此我们在应用程序中是无法直接调用的。
- PathClassLoader:Android 系统使用 PathClassLoader 来加载系统类和应用程序的类,如果是加载非系统应用程序类,则会加载data/app/目录下的dex文件以及包含dex的apk文件或jar文件,不管是加载哪种文件,最终都是要加载dex文件,在这里为了方便理解,我们将dex文件以及包含dex的apk文件或jar文件统称为dex相关文件。PathClassLoader 不建议开发直接使用。
- DexClassLoader:DexClassLoader 可以加载dex文件以及包含dex的apk文件或jar文件,也支持从SD卡进行加载,这也就意味着 DexClassLoader 可以在应用未安装的情况下加载dex相关文件。因此,它是热修复和插件化技术的基础。
现在回过头来看之前的代码:
1 | public HashMap<String, InventoryDetail> inventoryMap = new HashMap<>(); |
readHashMap
传入的参数是ClassLoader,我传入的是HashMap.class.getClassLoader()
,HashMap 作为系统自带常用类,是由BootClassLoader
进行加载的,而我应用自己编写的类InventoryDetail
是由PathClassLoader
进行加载的。那么显然,在BootClassLoader
中找由PathClassLoader
加载的类显然是找不到的,便会报错了。可以看到chooseCountMap、stockMap
传入的也是HashMap.class.getClassLoader()
,因为其 Value 类型为 Double,也是系统常用类,也是由BootClassLoader
进行加载的,所以不会有问题。以后在涉及到 ClassLoader 的时候可要细心点了。
这里简单验证一下:
1 | ClassLoader classLoader1 = Double.class.getClassLoader(); |
很显然了。
另外说下,ArrayList 在序列化的时候可以直接使用:
1 | this.transactionList = in.createTypedArrayList(Manifest.ManifestTransaction.CREATOR); |
可以消除Unchecked assignment: 'java.util.ArrayList' to 'java.util.ArrayList<xxx.Manifest.ManifestTransaction>'
的警告,只不过使用createTypedArrayList
时 List 需要显示定义成 ArrayList。