【问题标题】:IllegalArgumentException in ObjectInputStream.readObjectObjectInputStream.readObject 中的 IllegalArgumentException
【发布时间】:2018-02-12 06:28:28
【问题描述】:

我有一个显示奇怪行为的 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)

【问题讨论】:

  • 请提供完整的(未经编辑的)异常消息和堆栈跟踪
  • 我不明白为什么要否决这个问题。
  • 我没有投反对票。但是,我可以看到多种拒绝投票的原因,包括不包括 MVCE,以及​​花费很长时间来发布堆栈跟踪。
  • 我的意思是你。只是想知道原因
  • 你在一个(现已删除的)“答案”中问过这个问题:“我怎样才能控制它?从我的侧面对象是从应用程序写入的。这肯定不会改变表示”我>

标签: java android serialization illegalargumentexception objectinputstream


【解决方案1】:

对源代码的粗略检查表明,当序列化对象表示与读取类所期望的内容不匹配时,通常会在 ObjectInputStream 中抛出 IllegalArgumentException。例如,您可能有不兼容的自定义 readObjectwriteObject 方法。或者,您可能已经对对象表示进行了二进制不兼容的1更改,而没有更改硬编码的serialVersionUID 数字或实施自定义方法来处理这个2。 p>

在你的stacktraces中会有更多的线索......以及ObjectInputStream的源代码。

ClassCastExceptions 可能是另一种表现形式。


1 - 导致语义不兼容的更改是另一回事。它们不会导致IllegalArgumentException,但无论如何你都应该对它们做点什么。

2 - 如果您想处理不兼容问题,那么您可能不想更改serialVerionUID。如果你这样做了,那么你将需要通过类加载和类的多个版本来做“聪明的事情”。但另一方面是,如果您的代码需要处理具有相同 serialVersionUID 的多个表示,则表示版本必须可以从表示本身推导出来。这需要计划。


跟进

我获取了您的堆栈跟踪,并尝试将其与https://android.googlesource.com 上的 Android 源代码进行匹配

行号不完全匹配,但我认为问题出在下面的方法中。特别是我标记为“这里”的那一行。根据 Field.set 的 javadoc:

  1. 如果指定的对象参数不是声明基础字段的类或接口的实例,则该方法将引发 IllegalArgumentException。

  2. 如果基础字段是原始类型,则尝试展开转换以将新值转换为原始类型的值。如果此尝试失败,该方法将引发 IllegalArgumentException。

  3. 如果在可能展开后,新值无法通过标识或扩展转换转换为基础字段的类型,则该方法将引发 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) {
        }
    }
}

【讨论】:

  • '或者你可能已经改变了对象表示而不改变硬编码的serialVersionUID数字':这是他应该做的,除非他实际上引入了序列化不兼容,这比一般认为的要难,如果他做到了,他应该编写自定义的readObject()/writeObject()/readResolve()/writeReplace() 方法,而不是尽可能地扰乱serialVersionUID。您的回答听起来像 serialVersionUID 应该在每次对象表示更改时更新,当然不是这样。
  • 我在谈论原因。我并不是暗示您应该更改 UID。但无论如何我已经澄清了。
  • 我发布的 readData 方法用于从反序列化不同对象的不同文件中读取。当我在方法 readData 中时,我不知道类型,因此我正在反序列化为 Object。当我将文件读入 Object 类的对象时,文件中的对象作为日志真的很重要吗?需要注意的另一件事是,我在每个 Serializable 类中都对 serialVersionUID 进行了硬编码。
  • 1) 不,没关系。 2) 对 serialVersionUID 进行硬编码是相关的,但不是问题的(可能)原因。可能的原因是您对破坏序列化兼容性的一种类型的表示进行了更改;见docs.oracle.com/javase/7/docs/platform/serialization/spec/…
  • 因为您更新了应用程序?另一种解释是您已经实现了自定义 readObject / writeObject 方法并且它们不兼容。无论如何,线索在您的代码库或堆栈跟踪中。
猜你喜欢
  • 1970-01-01
  • 2013-11-04
  • 2015-07-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-07-07
相关资源
最近更新 更多