本文基于本人在项目开发中遇到的实际问题,对ClassNotFoundException异常发生的原因和解决办法做了探究。
定位异常
首先看看出问题的代码。
Application 1 的部分代码:
...
private MediaSessionCompat mMediaSession = new MediaSessionCompat(this, TAG);
...
private void sendPlayingList(ArrayList<MediaSessionCompat.QueueItem> curQueue, int count) {
Bundle extras= new Bundle();
extras.putInt("count", count);
extras.putParcelableArrayList("playing_list", curQueue);
sendMediaSessionEvent("playing_list_event", extras);
}
private void sendMediaSessionEvent(String event, Bundle extras){
synchronized (mMediaSession){
mMediaSession.sendSessionEvent(event, extras);
}
}
Application 2 的部分代码:
private class MediaControlCallback extends MediaControllerCompat.Callback {
@Override
public void onSessionEvent(String event, Bundle extras) {
if (event.equals(“playing_list_event”)) {
int count = extras.getInt("count");
ArrayList<MediaSessionCompat.QueueItem> nowPlayingList = extras.getParcelableArrayList("playing_list");
...
}
...
}
...
}
...
代码功能很简单,利用MediaSession的SessionEvent机制,Application 1 将一个Integer对象和ArrayList对象跨进程传递给Application 2,其中,MediaSessionCompat.QueueItem是一个可序列化的类:
...
public class MediaSessionCompat {
...
/**
* A single item that is part of the play queue. It contains a description
* of the item and its id in the queue.
*/
public static final class QueueItem implements Parcelable {
...
}
...
}
代码运行结果如下,在Application 2 的进程中出现异常:
02-02 18:08:38.162 8892 8892 E AndroidRuntime: FATAL EXCEPTION: main
02-02 18:08:38.162 8892 8892 E AndroidRuntime: Process: com.xx.xx.xx.xx, PID: 8892
02-02 18:08:38.162 8892 8892 E AndroidRuntime: android.os.BadParcelableException: ClassNotFoundException when unmarshalling: android.support.v4.media.session.MediaSessionCompat$QueueItem
异常产生的原因
既然遇到问题,那咱们就想办法解决。
异常很明显,说是QueueItem这个类无法找到,到底是什么原因呢?
要解决这个问题,首先需要了解一个重要的类ClassLoader,对Java反射比较熟悉的童鞋对该类应该不会陌生。对于该类官方给出的说法是:
A class loader is an object that is responsible for loading classes. The class ClassLoader is an abstract class. Given the binary name of a class, a class loader should attempt to locate or generate data that constitutes a definition for the class. A typical strategy is to transform the name into a file name and then read a “class file” of that name from a file system.
类加载器是负责加载类的对象。 ClassLoader类是一个抽象类。 给定一个类的二进制名称,类加载器应尝试去定位或生成定义一个类所需的数据。 一个典型的策略是将名称转换为文件名,然后从文件系统中读取该名称的“类文件”。
Every Class object contains a Class#getClassLoader() to the ClassLoader that defined it.
每个Class对象都包含一个Class#getClassLoader(),用于定义它的ClassLoader。
Class objects for array classes are not created by class loaders, but are created automatically as required by the Java runtime. The class loader for an array class, as returned by Class#getClassLoader() is the same as the class loader for its element type; if the element type is a primitive type, then the array class has no class loader.
数组类的类对象不是由类加载器创建的,而是根据Java运行时的需要自动创建的。 Class#getClassLoader()返回的数组类的类加载器与其元素类型的类加载器相同; 如果元素类型是基本类型,则数组类没有类加载器。
在对ClassLoader有了较清楚的认识后,我们再回到问题本身。本例中需要从Application 1 传递一个Integer对象和一个ArrayList对象到Application 2,而ArrayList中的成员是MediaSessionCompat.QueueItem对象。对于Integer对象,它是一个基础类型,如官方文档说明,它是不需要class loader的,而MediaSessionCompat.QueueItem并不是一个基础类型,它是需要对应的class loader来加载类的。严谨起见,我做了一个实验,对Application 2 的代码做了如下修改:
public void onSessionEvent(String event, Bundle extras) {
if (event.equals(“playing_list_event”)) {
Log.d("TEST_TAG", "onSessionEvent, extras class loader:" + extras.getClassLoader()):
//int count = extras.getInt("count");
//ArrayList<MediaSessionCompat.QueueItem> nowPlayingList = extras.getParcelableArrayList("playing_list");
...
}
运行后,日志打印如下:
05-15 18:32:24.913 7315 7315 D xx: TEST_TAG__onSessionEvent, extras class loader:null
看明白了吧,这里Bundle对象并没有class loader,所以找不到MediaSessionCompat.QueueItem。
如何解决异常
很明显,需要给extras对象设置一个MediaSessionCompat.QueueItem的class loader,对Application 2 的代码又做了如下修改:
public void onSessionEvent(String event, Bundle extras) {
if (event.equals(“playing_list_event”)) {
Log.d("TEST_TAG", "onSessionEvent, extras class loader before:" + extras.getClassLoader()):
extras.setClassLoader(MediaSessionCompat.QueueItem.class.getClassLoader()); //设置class loader
Log.d("TEST_TAG", "onSessionEvent, extras class loader after:" + extras.getClassLoader()):
int count = extras.getInt("count");
ArrayList<MediaSessionCompat.QueueItem> nowPlayingList = extras.getParcelableArrayList("playing_list");
...
}
运行成功,看看运行结果:
05-15 21:31:54.796 12918 12918 D xx: TEST_TAG__onSessionEvent, bundle class loader before:null
05-15 21:31:54.796 12918 12918 D xx: TEST_TAG__onSessionEvent, bundle class loader after:dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.xx.xx-l_YWuWr6nbgqjHNoz_5vmw==/base.apk"],nativeLibraryDirectories=[/data/app/com.xx.xx-l_YWuWr6nbgqjHNoz_5vmw==/lib/x86_64, /system/lib64, /vendor/lib64]]]
问题解决。