Intent intent = new Intent();
intent.setComponent(new ComponentName("com.test.test", "com.test.test.MainActivity"));
intent.putExtra("anykey", new Boom());
startActivity(intent);
其中Boom是一个序列化类(serializable),而且extra的key可以是任何值。
Bundle bundle = intent.getExtras();
if (bundle != null) {
int sd = bundle.getInt("key");
}
或
int sd = intent.getIntExtra("key", -1);
注意,不仅仅是getInt,任何类型的都会出问题,而且key不必与之前的anykey一样!
E/AndroidRuntime: FATAL EXCEPTION: mainProcess: xxx, PID: 1688java.lang.RuntimeException: Parcelable encountered ClassNotFoundException reading a Serializable object (name = com.example.Boom)at android.os.Parcel.readSerializable(Parcel.java:2630)at android.os.Parcel.readValue(Parcel.java:2416)at android.os.Parcel.readArrayMapInternal(Parcel.java:2732)at android.os.BaseBundle.unparcel(BaseBundle.java:271)at android.os.BaseBundle.get(BaseBundle.java:364)at com.test.test.MainActivity .onNewIntent(MainActivity.java:128)...Caused by: java.lang.ClassNotFoundException: com.example.Boomat java.lang.Class.classForName(Native Method)at java.lang.Class.forName(Class.java:400)at android.os.Parcel$2.resolveClass(Parcel.java:2616)at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1613)at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1518)at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1772)at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351)at java.io.ObjectInputStream.readObject(ObjectInputStream.java:373)at android.os.Parcel.readSerializable(Parcel.java:2624)at android.os.Parcel.readValue(Parcel.java:2416)at android.os.Parcel.readArrayMapInternal(Parcel.java:2732)at android.os.BaseBundle.unparcel(BaseBundle.java:271)at android.os.BaseBundle.get(BaseBundle.java:364)at com.test.test.MainActivity .onNewIntent(MainActivity.java:128)...02-27 17:33:33.799 1688-1688/? E/AndroidRuntime: Caused by: java.lang.ClassNotFoundException: Didn't find class "com.example.Boom" on path: DexPathList[...]at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:56)at java.lang.ClassLoader.loadClass(ClassLoader.java:380)at java.lang.ClassLoader.loadClass(ClassLoader.java:312)... 27 more02-27 17:33:33.813 1688-1688/? E/MobclickAgent: onPause called before onResume
public int getInt(String key, int defaultValue) {
unparcel();
Object o = mMap.get(key);
if (o == null) {
return defaultValue;
}
try {
return (Integer) o;
} catch (ClassCastException e) {
typeWarning(key, o, "Integer", defaultValue, e);
return defaultValue;
}
}
public void putString(@Nullable String key, @Nullable String value) {
unparcel();
mMap.put(key, value);
}
unparcel函数我们后面再说,先看看在这两种函数中存取数据实际上都是在mMap中做的,这是BaseBundle中一个重要的参数,它存储着Bundle的数据。
/* package */ synchronized void unparcel() {
synchronized (this) {
...
ArrayMap<String, Object> map = mMap;
if (map == null) {
map = new ArrayMap<>(N);
} else {
map.erase();
map.ensureCapacity(N);
}
try {
mParcelledData.readArrayMapInternal(map, N, mClassLoader);
} catch (BadParcelableException e) {
if (sShouldDefuse) {
Log.w(TAG, "Failed to parse Bundle, but defusing quietly", e);
map.erase();
} else {
throw e;
}
} finally {
mMap = map;
mParcelledData.recycle();
mParcelledData = null;
}
if (DEBUG) Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
+ " final map: " + mMap);
}
}
这里面涉及到了Bundle中的两个重要参数mMap和mParcelledData,mMap我们上面说过,另外一个mParcelledData则是一个Parcel对象。它是怎么来的呢?
Bundle options = data.readInt() != 0
? Bundle.CREATOR.createFromParcel(data) : null;
这行的作用就是从binder的消息中解析出传送过来的Bundle数据,继续看来Bundle.CREATOR:
public static final Parcelable.Creator<Bundle> CREATOR =
new Parcelable.Creator<Bundle>() {
@Override
public Bundle createFromParcel(Parcel in) {
return in.readBundle();
}
@Override
public Bundle[] newArray(int size) {
return new Bundle[size];
}
};
createFromParcel函数其实就是调用来Parcel的readBundle函数,代码如下:
public final Bundle readBundle() {
return readBundle(null);
}
public final Bundle readBundle(ClassLoader loader) {
int length = readInt();
...
final Bundle bundle = new Bundle(this, length);
...
return bundle;
}
通过Bundle的构造函数来新建了一个对象,这个构造函数则调用了父类BaseBundle对应的构造函数如下:
BaseBundle(Parcel parcelledData, int length) {
readFromParcelInner(parcelledData, length);
}
private void readFromParcelInner(Parcel parcel, int length) {
...
Parcel p = Parcel.obtain();
p.setDataPosition(0);
p.appendFrom(parcel, offset, length);
if (DEBUG) Log.d(TAG, "Retrieving " + Integer.toHexString(System.identityHashCode(this))
+ ": " + length + " bundle bytes starting at " + offset);
p.setDataPosition(0);
mParcelledData = p;
}
这样我们就在readFromParcelInner函数中找到了mParcelledData的来源,它实际上就是传送过来的Bundle序列化后的数据。
/* package */ void readArrayMapInternal(ArrayMap outVal, int N,
ClassLoader loader) {
if (DEBUG_ARRAY_MAP) {
RuntimeException here = new RuntimeException("here");
here.fillInStackTrace();
Log.d(TAG, "Reading " + N + " ArrayMap entries", here);
}
int startPos;
while (N > 0) {
if (DEBUG_ARRAY_MAP) startPos = dataPosition();
String key = readString();
Object value = readValue(loader);
if (DEBUG_ARRAY_MAP) Log.d(TAG, " Read #" + (N-1) + " "
+ (dataPosition()-startPos) + " bytes: key=0x"
+ Integer.toHexString((key != null ? key.hashCode() : 0)) + " " + key);
outVal.append(key, value);
N--;
}
outVal.validate();
}
在这个函数里就可以比较明显的看出来,从Parcel中分别读取出key和value,然后put进map中。这样就解决了之前的疑惑,unparcel函数的作用实际上是预处理,提前将序列化的数据反序列化并放入mMap中,然后Bundle再从mMap中存取数据。
public final Object readValue(ClassLoader loader) {
int type = readInt();
switch (type) {
case VAL_NULL:
return null;
case VAL_STRING:
return readString();
case VAL_INTEGER:
return readInt();
...
case VAL_SERIALIZABLE:
return readSerializable(loader);
...
default:
int off = dataPosition() - 4;
throw new RuntimeException(
"Parcel " + this + ": Unmarshalling unknown type code " + type + " at offset " + off);
}
}
根据不同的类型调用不同的函数来获得value,这里我们只关注Serializable这个类型,readSerializable代码如下:
private final Serializable readSerializable(final ClassLoader loader) {
String name = readString();
...
try {
ObjectInputStream ois = new ObjectInputStream(bais) {
@Override
protected Class<?> resolveClass(ObjectStreamClass osClass)
throws IOException, ClassNotFoundException {
if (loader != null) {
Class<?> c = Class.forName(osClass.getName(), false, loader);
if (c != null) {
return c;
}
}
return super.resolveClass(osClass);
}
};
return (Serializable) ois.readObject();
} catch (IOException ioe) {
throw new RuntimeException("Parcelable encountered " +
"IOException reading a Serializable object (name = " + name +
")", ioe);
} catch (ClassNotFoundException cnfe) {
throw new RuntimeException("Parcelable encountered " +
"ClassNotFoundException reading a Serializable object (name = "
+ name + ")", cnfe);
}
}
我们终于找到了最开始的崩溃错误的源头,在这里反序列化时需要根据类名去找到Class对象,这时就出问题了,因为通过上面我们知道,unparcel函数预处理时会将mParcelledData中所有的数据都解析出来,这时当解析到最开始的Boom类时,由于在本App中并不存在这个类,所以无法找到这个类,这样就出问题了。这样也解释了为什么任意key都会出问题。
E/AndroidRuntime: FATAL EXCEPTION: mainjava.lang.RuntimeException: Unable to start activity ComponentInfo{com.test.test /com.test.test .MainActivity}: android.os.BadParcelableException: ClassNotFoundException when unmarshalling: com.example.Boomat android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2369)at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2431)...Caused by: android.os.BadParcelableException: ClassNotFoundException when unmarshalling: com.example.bennu.testapp.Boomat android.os.Parcel.readParcelableCreator(Parcel.java:2295)at android.os.Parcel.readParcelable(Parcel.java:2245)at android.os.Parcel.readValue(Parcel.java:2152)at android.os.Parcel.readArrayMapInternal(Parcel.java:2485)at android.os.BaseBundle.unparcel(BaseBundle.java:221)at android.os.BaseBundle.get(BaseBundle.java:280)at com.test.test .MainActivity.onCreate(MainActivity.java:142)...
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);
}
/** @hide */
public final Parcelable.Creator<?> readParcelableCreator(ClassLoader loader) {
String name = readString();
if (name == null) {
return null;
}
Parcelable.Creator<?> creator;
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 {
ClassLoader parcelableClassLoader =
(loader == null ? getClass().getClassLoader() : loader);
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)) {
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;
}
在readParcelable函数中调用readParcelableCreator函数来解析数据,在这个函数中就可以看到同样需要查找class来反序列化,而不同的是对Expection没有直接抛出,而是包装成BadParcelableException抛出的,这也是为什么crash信息有区别。
/* package */ synchronized void unparcel() {
synchronized (this) {
...
try {
mParcelledData.readArrayMapInternal(map, N, mClassLoader);
} catch (BadParcelableException e) {
if (sShouldDefuse) {
Log.w(TAG, "Failed to parse Bundle, but defusing quietly", e);
map.erase();
} else {
throw e;
}
} finally {
mMap = map;
mParcelledData.recycle();
mParcelledData = null;
}
if (DEBUG) Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
+ " final map: " + mMap);
}
}
可以看到mParcelledData.readArrayMapInternal是在一个try-catch中的,而是catch部分又catch了BadParcelableException,这里就有了一个小彩蛋:当sShouldDefuse为true时,这个错误就被吞掉了,而为false时继续抛出。
/**
* Set global variable indicating that any Bundles parsed in this process
* should be "defused." That is, any {@link BadParcelableException}
* encountered will be suppressed and logged, leaving an empty Bundle
* instead of crashing.
*
* @hide
*/
public static void setShouldDefuse(boolean shouldDefuse) {
sShouldDefuse = shouldDefuse;
}
这个函数是static的,但是是隐藏的,所以我们不能直接使用。通过这个函数的注释我们可以知道,当设为true的时候,会吞掉所有BadParcelableException错误,这时会返回一个空的Bundle代替crash。
