我有一个显示奇怪行为的Android应用程序。 我有一个方法从文件反序列化对象。以下是我正在使用的代码:
public static Object readData(Context context, String fileName)
{
synchronized (context) {
ObjectInputStream input = null;
Object object = null;
if (fileExists(context, fileName)) {
try {
input = new ObjectInputStream(context.openFileInput(fileName));
object = input.readObject();
Log.v(Constant.TAG, "Writable object has been loaded from file "+fileName);
} catch (IOException e) {
Log.e(Constant.TAG, e.getMessage(), e);
} catch (ClassNotFoundException e) {
Log.e(Constant.TAG, e.getMessage(), e);
} finally {
try {
if (input != null)
input.close();
} catch (IOException e) {
Log.e(Constant.TAG, e.getMessage(), e);
}
}
}
return object;
}
}
通常它运行良好,但是当有人最小化我的应用程序并在一段时间后重新打开时,它会崩溃。从崩溃报告中我发现它从上面的代码
中将IllegalArgumentException
投放到下面一行
object = input.readObject();
我浏览了ObjectInputStream.readObject
的文档,但没有说明它可以抛出IllegalArgumentException
的情况。
只有在用户从后台运行应用程序时才会发生这种情况。应用程序启动时它非常有效(开始时我指的是应用程序未运行时,甚至不在后台运行)。
PS:有一些崩溃报告在同一行显示ClassCastException
,由于我没有投射,只读取Object
,这个报告甚至更奇怪。
更新
堆栈跟踪
java.lang.RuntimeException:
at android.app.ActivityThread.performLaunchActivity (ActivityThread.java:2423)
at android.app.ActivityThread.handleLaunchActivity (ActivityThread.java:2483)
at android.app.ActivityThread.access$900 (ActivityThread.java:153)
at android.app.ActivityThread$H.handleMessage (ActivityThread.java:1349)
at android.os.Handler.dispatchMessage (Handler.java:102)
at android.os.Looper.loop (Looper.java:148)
at android.app.ActivityThread.main (ActivityThread.java:5441)
at java.lang.reflect.Method.invoke (Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run (ZygoteInit.java:738)
at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:628)
Caused by: java.lang.IllegalArgumentException:
at java.lang.reflect.Field.set (Native Method)
at java.io.ObjectInputStream.readFieldValues (ObjectInputStream.java:1127)
at java.io.ObjectInputStream.defaultReadObject (ObjectInputStream.java:454)
at java.io.ObjectInputStream.readObjectForClass (ObjectInputStream.java:1345)
at java.io.ObjectInputStream.readHierarchy (ObjectInputStream.java:1242)
at java.io.ObjectInputStream.readNewObject (ObjectInputStream.java:1835)
at java.io.ObjectInputStream.readNonPrimitiveContent (ObjectInputStream.java:761)
at java.io.ObjectInputStream.readObject (ObjectInputStream.java:1983)
at java.io.ObjectInputStream.readObject (ObjectInputStream.java:1940)
at com.pixyfisocial.pixyfi.util.IOUtil.readData (IOUtil.java:245)
at com.pixyfisocial.Login.getLoggedInUserInfoFromCache (Login.java:313)
at com.pixyfisocial.Login.startApp (Login.java:124)
at com.pixyfisocial.Login.onCreate (Login.java:98)
at android.app.Activity.performCreate (Activity.java:6303)
at android.app.Instrumentation.callActivityOnCreate (Instrumentation.java:1108)
at android.app.ActivityThread.performLaunchActivity (ActivityThread.java:2376)
答案 0 :(得分:2)
对源代码的粗略检查表明,当序列化对象表示与读取类期望的不匹配时,IllegalArgumentException
通常会抛出ObjectInputStream
。例如,您可能拥有不兼容的自定义readObject
和writeObject
方法。或者您可能对对象表示进行了二进制不兼容的 1 更改,而不更改硬编码的serialVersionUID
数字或实现自定义方法来处理此 2 。
您的堆栈跟踪......以及ObjectInputStream
的源代码中会有更多线索。
ClassCastException
可能是另一种表现形式。
1 - 导致语义不兼容的变化是另一回事。它们不会导致IllegalArgumentException
,但无论如何你都应该对它们做些什么。
2 - 如果您想应对不兼容性,那么您可能不想更改serialVerionUID
。如果你这样做,那么你将需要通过类加载和类的多个版本来做“聪明的事情”。但另一方面,如果您的代码需要处理具有相同serialVersionUID
的多个表示,则表示版本必须可以从表示本身中进行推导。这需要进行规划。
<强>后续强>
我带了你的stacktrace并试图将它与https://android.googlesource.com
上提供的Android源代码相匹配行号不完全匹配,但我认为问题出现在下面的方法中。特别是我标记为“HERE”的行。根据Field.set的javadoc:
如果指定的对象参数不是声明基础字段的类或接口的实例,则该方法将抛出IllegalArgumentException。
如果基础字段是基本类型,则尝试进行解包转换以将新值转换为基本类型的值。如果此尝试失败,则该方法抛出IllegalArgumentException。
- 醇>
如果在可能的解包之后,新值无法通过标识或扩展转换转换为基础字段的类型,则该方法将抛出IllegalArgumentException。
这三件事之一正在发生。除非你提供一个完整的工作MCVE(有人可以在Android模拟器上运行!),否则无法说出哪一个......但是这些迹象表明你已经(以某种方式)打破了序列化兼容性规则。
注意,由于行号不匹配,我不能肯定地确定您使用的Android符合以下内容。如果您想确定,您需要在GIT仓库中搜索历史记录以查找匹配的版本....或查看供应商特定的源代码包/仓库中的设备。
/**
* Reads a collection of field values for the class descriptor
* {@code classDesc} (an {@code ObjectStreamClass}). The
* values will be used to set instance fields in object {@code obj}.
* This is the default mechanism, when emulated fields (an
* {@code GetField}) are not used. Actual values to load are stored
* directly into the object {@code obj}.
*
* @param obj
* Instance in which the fields will be set.
* @param classDesc
* A class descriptor (an {@code ObjectStreamClass})
* defining which fields should be loaded.
*
* @throws IOException
* If an IO exception happened when reading the field values.
* @throws InvalidClassException
* If an incompatible type is being assigned to an emulated
* field.
* @throws OptionalDataException
* If optional data could not be found when reading the
* exception graph
* @throws ClassNotFoundException
* If a class of an object being de-serialized can not be found
*
* @see #readFields
* @see #readObject()
*/
private void readFieldValues(Object obj, ObjectStreamClass classDesc) throws OptionalDataException, ClassNotFoundException, IOException {
// Now we must read all fields and assign them to the receiver
ObjectStreamField[] fields = classDesc.getLoadFields();
fields = (fields == null) ? ObjectStreamClass.NO_FIELDS : fields;
Class<?> declaringClass = classDesc.forClass();
if (declaringClass == null && mustResolve) {
throw new ClassNotFoundException(classDesc.getName());
}
for (ObjectStreamField fieldDesc : fields) {
Field field = classDesc.getReflectionField(fieldDesc);
if (field != null && Modifier.isTransient(field.getModifiers())) {
field = null; // No setting transient fields! (http://b/4471249)
}
// We may not have been able to find the field, or it may be transient, but we still
// need to read the value and do the other checking...
try {
Class<?> type = fieldDesc.getTypeInternal();
if (type == byte.class) {
byte b = input.readByte();
if (field != null) {
field.setByte(obj, b);
}
} else if (type == char.class) {
char c = input.readChar();
if (field != null) {
field.setChar(obj, c);
}
} else if (type == double.class) {
double d = input.readDouble();
if (field != null) {
field.setDouble(obj, d);
}
} else if (type == float.class) {
float f = input.readFloat();
if (field != null) {
field.setFloat(obj, f);
}
} else if (type == int.class) {
int i = input.readInt();
if (field != null) {
field.setInt(obj, i);
}
} else if (type == long.class) {
long j = input.readLong();
if (field != null) {
field.setLong(obj, j);
}
} else if (type == short.class) {
short s = input.readShort();
if (field != null) {
field.setShort(obj, s);
}
} else if (type == boolean.class) {
boolean z = input.readBoolean();
if (field != null) {
field.setBoolean(obj, z);
}
} else {
Object toSet = fieldDesc.isUnshared() ? readUnshared() : readObject();
if (toSet != null) {
// Get the field type from the local field rather than
// from the stream's supplied data. That's the field
// we'll be setting, so that's the one that needs to be
// validated.
String fieldName = fieldDesc.getName();
ObjectStreamField localFieldDesc = classDesc.getField(fieldName);
Class<?> fieldType = localFieldDesc.getTypeInternal();
Class<?> valueType = toSet.getClass();
if (!fieldType.isAssignableFrom(valueType)) {
throw new ClassCastException(classDesc.getName() + "." + fieldName + " - " + fieldType + " not compatible with " + valueType);
}
if (field != null) {
field.set(obj, toSet); // <<< --- HERE
}
}
}
} catch (IllegalAccessException iae) {
// ObjectStreamField should have called setAccessible(true).
throw new AssertionError(iae);
} catch (NoSuchFieldError ignored) {
}
}
}