使用 Parcelable 接口时如何序列化空值

Posted

技术标签:

【中文标题】使用 Parcelable 接口时如何序列化空值【英文标题】:How to serialize null value when using Parcelable interface 【发布时间】:2011-08-19 19:00:43 【问题描述】:

关于我的代码示例,如果一个 Locable 的变量为空,我该怎么办?例如,现在如果 l.getZoom() 返回 null,我得到 NullPointerException。

@Override
public void writeToParcel(Parcel parcel, int arg1) 
    parcel.writeInt(count);
    for(Locable l:locableArr)
        parcel.writeInt(l.getOriginId());
        parcel.writeInt(l.getLocableType());
        parcel.writeInt(l.getZoom());
        parcel.writeDouble(l.getLatituda());
        parcel.writeDouble(l.getLongituda());
        parcel.writeString(l.getTitle());
        parcel.writeString(l.getSnipet());
    


谢谢!

【问题讨论】:

似乎 NPE 是自动拆箱的结果。也许我应该只检查空值并将其设置为 0? 如果 zoom 的值为 0+,考虑将 null 表示为 -1 现在我正在做类似的事情,如果值为 NULL,我正在写一些值,例如 -1 或我在反序列化设置为 NULL 期间的空字符串。丑陋...如果有人能确认这是正确的方法,我会更满意我的代码。 以及原语没有 null 的概念(或目标 C 的 nil)作为一个通用解决方案,我想知道您是否可以发送成对的原语来表示一个对象,比如 Int 代表 int,首先是布尔 isNull,然后是原始 int 允许您创建 Integer(int) 或 Integer(null)。 【参考方案1】:

您可以使用Parcel.writeValue 编组具有空值的通用对象。

【讨论】:

并读回值... s = (String) in.readValue(String.class.getClassLoader()); @SofiSoftwareLLC 查看 parcel.readValue 的源代码,它只需要 Map、Parcelable、ArrayList、Object[] 和 Parcelable[] 类型的类加载器。对于其他类型,您可以为类加载器传入 null。 github.com/android/platform_frameworks_base/blob/… 应该被接受的答案,与@SofiSoftwareLLC 的评论集成【参考方案2】:

我正在使用一个Parcelable 类,它也有IntegerBoolean 字段,这些字段可以为空。

我在使用通用 Parcel.writeValue 方法时遇到了问题,尤其是当我试图通过 Parcel.readValue 将其读回时。我不断收到运行时异常,说它无法确定打包对象的类型。

最终,我能够通过使用带有类型转换的Parcel.writeSerializableParcel.readSerializable 来解决这个问题,因为IntegerBoolean 都实现了Serializable 接口。读取和写入方法为您处理 null 值。

【讨论】:

是的,使用 cast 进行序列化就像一个魅力。【参考方案3】:

这是我想出的安全编写字符串的解决方案:

private void writeStringToParcel(Parcel p, String s) 
    p.writeByte((byte)(s != null ? 1 : 0));
    p.writeString(s);


private String readStringFromParcel(Parcel p) 
    boolean isPresent = p.readByte() == 1;
    return isPresent ? p.readString() : null;

【讨论】:

在复制并粘贴您的代码后,我遇到了一些麻烦,没有意识到在 readStringFromParcel() 中我必须将 1 转换为字节,然后再将其与 p.readByte() 进行比较。最后我选择了“p.writeInt((s != null ? 1 : 0));”和“布尔 isPresent = p.readInt() == 1;” 你应该使用p.writeString(s)这一行写入字符串,只有当它不为空时;否则你会在这一行得到一个 NullPointerException。 如果你使用p.writeParcelable而不是p.writeString,你不会立即得到NullPointerException,只是稍后由未对齐的读写序列引起的一些可疑的ClassCastException。你应该只写不为空的对象。【参考方案4】:

我见过的大多数序列化代码使用任一标志来指示值的存在/不存在,或者在值之前使用计数字段(例如,在写入数组时),其中计数字段仅设置为零,如果价值根本不存在。

检查 Android 核心类的源代码会发现这样的代码(来自 Message 类):

    if (obj != null) 
        try 
            Parcelable p = (Parcelable)obj;
            dest.writeInt(1);
            dest.writeParcelable(p, flags);
         catch (ClassCastException e) 
            throw new RuntimeException(
                "Can't marshal non-Parcelable objects across processes.");
        
     else 
        dest.writeInt(0);
    

或者这个(来自 Intent 类):

    if (mCategories != null) 
        out.writeInt(mCategories.size());
        for (String category : mCategories) 
            out.writeString(category);
        
     else 
        out.writeInt(0);
    

我的建议:在您的代码中,如果“zoom == null”和“zoom == 0”之间没有功能上的区别,那么我只需将缩放声明为原语(@987654323 @ 而不是 Integer) 或在构造函数中将其初始化为零并确保您永远不会将其设置为 null (然后您可以保证它永远不会为 null 并且您不必添加特殊代码来处理它在您的序列化/反序列化方法中)。

【讨论】:

您可以编写代码来回答读取对象的部分。

以上是关于使用 Parcelable 接口时如何序列化空值的主要内容,如果未能解决你的问题,请参考以下文章

parcelable 和 serializable 区别

Android Parcelable接口的使用/序列化数据

对 Serializable和Parcelable理解

Android:使用Parcelable接口在跳转Activity时传递数据以及错误信息 Parcel: unable to marshal value 的解决

Android:使用Parcelable接口在跳转Activity时传递数据以及错误信息 Parcel: unable to marshal value 的解决

Parcelable接口