Android-对象序列化

Android安全攻击——对象序列化OOM问题 前言 最近在项目中使用ObjectInputStream/ObjectOutputStream进行对象的序列化和反序列化,出现了OOM的问题,在解决的过程中简单的研究了一下对象的序列化和反序列化(使用Serializable接口)的过程,简单做一个记录。发现了一个持久化存储序列化数据的安全风险,可能会受到恶意攻击,导致必现的OOM。 使用场景 应用在使用过程中,首先使用ObjectO 阅读详情

Serializable

简单使用

Serializable是Java提供的序列化接口,是一个空接口,为对象提供标准的序列化和反序列化操作。

package java.io;

public interface Serializable {
}

为一个对象实现序列化很简单,就是实现此接口即可。

public class SerializableBean implements Serializable {
    private String mString;
    private int mInt;
    private Boolean mBoolean;
    private long mLong;
    private short mShort;
    private byte mByte;
    private char mChar;

    public SerializableBean() {
        mString = "ABC";
        mInt = 21;
        mBoolean = true;
        mLong = 221;
        mShort = 2;
        mByte = (byte) 96;
        mChar = (char) 97;
    }

    public String getString() {
        return mString;
    }

    public void setString(String string) {
        mString = string;
    }

    public int getInt() {
        return mInt;
    }

    public void setInt(int anInt) {
        mInt = anInt;
    }

    public Boolean getBoolean() {
        return mBoolean;
    }

    public void setBoolean(Boolean aBoolean) {
        mBoolean = aBoolean;
    }

    public long getLong() {
        return mLong;
    }

    public void setLong(long aLong) {
        mLong = aLong;
    }

    public short getShort() {
        return mShort;
    }

    public void setShort(short aShort) {
        mShort = aShort;
    }

    public byte getByte() {
        return mByte;
    }

    public void setByte(byte aByte) {
        mByte = aByte;
    }

    public char getChar() {
        return mChar;
    }

    public void setChar(char aChar) {
        mChar = aChar;
    }
    
    @Override
    public String toString() {
        return "SerializableBean{" +
                "mString='" + mString + '\'' +
                ", mInt=" + mInt +
                ", mBoolean=" + mBoolean +
                ", mLong=" + mLong +
                ", mShort=" + mShort +
                ", mByte=" + mByte +
                ", mChar=" + mChar +
                '}';
    }
}

序列化操作,将Object通过ObjectOutputStream写入ByteArrayOutputStream,并转换成字符串储存到SharedPreferences。

// onClick事件触发序列化过程
public void onSerializable(View view) {
    SerializableBean bean = new SerializableBean();
    ByteArrayOutputStream byteArrayOutputStream = null;
    ObjectOutputStream objectOutputStream = null;
    try {
        byteArrayOutputStream = new ByteArrayOutputStream();
        objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(bean);
        objectOutputStream.flush();
        byte[] bytes = Base64.encode(byteArrayOutputStream.toByteArray(), Base64.DEFAULT);
        mSharedPreferences.edit().putString("serializable_result", new String(bytes)).commit();
        mShowSerializableResultText.setText("序列化完成");
    } catch (IOException e) {
        e.printStackTrace();
    }finally {
        if (byteArrayOutputStream != null) {
            try {
                byteArrayOutputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if (objectOutputStream != null) {
            try {
                objectOutputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

反序列化操作,从SharedPreferences读取保存的序列化字符串,通过用ObjectInputStream读取ByteArrayInputStream中的字节反序列化出对象,并打印对象。

// onClick事件触发反序列化过程
public void onDeserializable(View view) {
    String serializableResult = mSharedPreferences.getString("serializable_result", null);
    if (serializableResult != null) {
        ByteArrayInputStream byteArrayInputStream = null;
        ObjectInputStream objectInputStream = null;
        try {
            byte[] resultBytes = Base64.decode(serializableResult.getBytes(), Base64.DEFAULT);
            byteArrayInputStream = new ByteArrayInputStream(resultBytes);
            objectInputStream = new ObjectInputStream(byteArrayInputStream);
            SerializableBean serializableBean = (SerializableBean) objectInputStream.readObject();
            mShowSerializableResultText.setText(serializableBean.toString());
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            if (byteArrayInputStream != null) {
                try {
                    byteArrayInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (objectInputStream != null) {
                try {
                    objectInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

输出如下:

SerializableBean{mString='ABC', mInt=21, mBoolean=true, mLong=221, mShort=2, mByte=96, mChar=a}

可见,一个对象已经被序列化完成。但是在序列化时为什么需要用Base64转换?为什么不直接用ByteArray流生成String保存。刚开始也确实是这样想的,但是结果却不尽人意,当直接把ByteArray转成String写入后再读取String后转ByteArray后,到ObjectInputStream的readObject方法时就出现了错误W/System.err: java.io.StreamCorruptedException: invalid stream header: EFBFBDEF。究其原因就是因为用ObjectOutputStream输出到ByteArray后再转String再转ByteArray的过程出现了字符集错误,将输出的ByteArray进行打印后可以看到头两个字节是0xAC 0xED,转成String后getBytes默认的字符集是按UTF-8来转的,因此转完后再输出时就成了0xEF 0xBF 0xBD 0xEF 0xBF 0xBD,之后将此ByteArray转成对象时就会出现异常。

输出原始ByteArray:

byte[] bytes = byteArrayOutputStream.toByteArray();
StringBuilder stringBuilder = new StringBuilder();
for (byte aByte : bytes) {
    stringBuilder.append(Integer.toHexString(aByte & 0xFF));
    stringBuilder.append(" ");
}
Log.e(TAG, "onSerializable: " + stringBuilder.toString());

输出String的getBytes:

byte[] bytes1 = new String(bytes).getBytes();
stringBuilder = new StringBuilder();
for (byte aByte : bytes1) {
    stringBuilder.append(Integer.toHexString(aByte & 0xFF));
    stringBuilder.append(" ");
}
Log.e(TAG, "onSerializable: " + stringBuilder.toString());

所以需要先将输出的ByteArray用Base64转成规范的不会让UTF-8等字符集产生错误转换的ByteArray,将此ByteArray再转成String就不会有问题了,等readObject时再将字符串转成Base64再转ByteArray。除此之外还可以先转为ISO-8859-1,因为此编码是单字节编码,可以将文件header识别为单字符,将纯二进制转成此编码后再转UTF-8也可以解决问题,相应的readObject时就得逆顺序操作。

容器序列化

  1. List
    需要各元素都得是可序列化的对象,基本类型都是支持序列化的,特别注意的就是元素中是否还有自定义对象不能被序列化。

    for (int i = 0; i < 3; i++) {
        SerializableBean valueBean = new SerializableBean();
        valueBean.setString("Value" + (i + 1));
        mSerializableValueList.add(valueBean);
    }
    
    objectOutputStream.writeObject(mSerializableValueList);
    

    将List序列化后反序列化的结果:

    E/RemoteActivity1: onDeserializable: [
        SerializableBean{mString='Value1', mInt=21, mBoolean=true, mLong=221, mShort=2, mByte=96, mChar=a}, 
        SerializableBean{mString='Value2', mInt=21, mBoolean=true, mLong=221, mShort=2, mByte=96, mChar=a}, 
        SerializableBean{mString='Value3', mInt=21, mBoolean=true, mLong=221, mShort=2, mByte=96, mChar=a}
    ]
    
  2. Map
    比List相同的是Value值是可序列化对象,并且Map的Key值也需要是可序列化对象。

    for (int i = 0; i < 3; i++) {
        SerializableBean valueBean = new SerializableBean();
        valueBean.setString("Value" + (i + 1));
        SerializableBean keyBean = new SerializableBean();
        keyBean.setString("Key" + (i + 1));
        mSerializableBeanMap.put(keyBean, valueBean);
        mSerializableBeanSet.add(valueBean);
    }
    

    将Map序列化后反序列化的结果:

    E/RemoteActivity1: onDeserializable: {
        SerializableBean{mString='Key3', mInt=21, mBoolean=true, mLong=221, mShort=2, mByte=96, mChar=a}=SerializableBean{mString='Value3', mInt=21, mBoolean=true, mLong=221, mShort=2, mByte=96, mChar=a}, 
        SerializableBean{mString='Key1', mInt=21, mBoolean=true, mLong=221, mShort=2, mByte=96, mChar=a}=SerializableBean{mString='Value1', mInt=21, mBoolean=true, mLong=221, mShort=2, mByte=96, mChar=a}, 
        SerializableBean{mString='Key2', mInt=21, mBoolean=true, mLong=221, mShort=2, mByte=96, mChar=a}=SerializableBean{mString='Value2', mInt=21, mBoolean=true, mLong=221, mShort=2, mByte=96, mChar=a}
    }
    
  3. Set
    类似List,只需要元素是可序列化对象。

    for (int i = 0; i < 3; i++) {
        SerializableBean valueBean = new SerializableBean();
        valueBean.setString("Value" + (i + 1));
        mSerializableBeanSet.add(valueBean);
    }
    

    将Set序列化后反序列化的结果:

    E/RemoteActivity1: onDeserializable: [
        SerializableBean{mString='Value3', mInt=21, mBoolean=true, mLong=221, mShort=2, mByte=96, mChar=a}, 
        SerializableBean{mString='Value2', mInt=21, mBoolean=true, mLong=221, mShort=2, mByte=96, mChar=a}, 
        SerializableBean{mString='Value1', mInt=21, mBoolean=true, mLong=221, mShort=2, mByte=96, mChar=a}
    ]
    
  4. 数组
    类似List,只需要元素是可序列化对象。

    private SerializableBean[] mSerializableBeans = new SerializableBean[3];
    
    for (int i = 0; i < 3; i++) {
        SerializableBean valueBean = new SerializableBean();
        valueBean.setString("Value" + (i + 1));
        mSerializableBeans[i] = valueBean;
    }
    

    将数组序列化后反序列化的结果:

    E/RemoteActivity1: onDeserializable: Array 
        SerializableBean{mString='Value1', mInt=21, mBoolean=true, mLong=221, mShort=2, mByte=96, mChar=a}|
        SerializableBean{mString='Value2', mInt=21, mBoolean=true, mLong=221, mShort=2, mByte=96, mChar=a}|
        SerializableBean{mString='Value3', mInt=21, mBoolean=true, mLong=221, mShort=2, mByte=96, mChar=a}|
    

补充

在使用序列化时,可能会碰到这样的情况,已经序列化并且持久化的对象在反序列化时以前的对象已经因为种种原因被更改了,或加属性或减属性等。此时如果再反序列化的话是会出现InvalidClassException。

  1. 增属性

    在SerializableBean中新增一个属性:

    private String mString1;
    

    当再把之前保存的字符串给反序列化输出的异常如下:

    W/System.err: java.io.InvalidClassException: com.libok.androidnote.bean.SerializableBean; local class incompatible: stream classdesc serialVersionUID = -8862606466551502984, local class serialVersionUID = 6754814338122385435
    

    意思就是从流得到的对象的SerializableID与当前类的serialVersionUID对应不上导致的反序列化失败。在创建对象类并实现Serializable接口时默认是自动生成一个serialVersionUID的,当类做了更改后会生成另一个serialVersionUID,解决办法就是手动指定一个serialVersionUID或者用编辑器去检查生成。

    在AndroidStudio的Setting中搜索serialVersionUID,并开启如下设置:

    在对应的类就会有相应的提示:

    image-20200302215550129

    当有这个serialVersionUID时,再次序列化保存之前的对象,然后再添加属性后反序列化。

    E/RemoteActivity1: onDeserializable: SerializableBean{mString='ABC', mInt=21, mBoolean=true, mLong=221, mShort=2, mByte=96, mChar=a, mString1='null'}
    

    此时就可以正确序列化了,但是因为反序列化的对象并没有mString1属性,所以mString1会直接赋值为null,相应的int->0、long->0、short->0、byte->0、char->0、Boolean->null。以上是增属性,那么删属性会是怎样。

  2. 删属性

    通过打印发现在类中被删除的属性在反序列化的对象中也直接被删除了。

    E/RemoteActivity1: onDeserializable: SerializableBean{mString='ABC', mInt=21, mBoolean=true, mLong=221, mShort=2, mByte=96}
    
  3. 取消实现Serializable接口

    如果取消实现Serializable接口的话,那么强制反序列化是会报异常的。

    W/System.err: java.io.InvalidClassException: com.libok.androidnote.bean.SerializableBean; class invalid for deserialization
    

Parcelable

Parcelable是Android特有的接口,只要实现此接口,一个类的对象就可以实现序列化并可以通过Intent和Binder传递。

简单使用

示例如下:

public class ParcelableBean implements Parcelable {

    private String mString;
    private int mInt;
    private Boolean mBoolean;
    private long mLong;
    private short mShort;
    private byte mByte;
    private char mChar;

    public ParcelableBean() {
        mString = "ABC";
        mInt = 21;
        mBoolean = true;
        mLong = 221;
        mShort = 2;
        mByte = (byte) 96;
        mChar = (char) 97;
    }

    public String getString() {
        return mString;
    }

    public void setString(String string) {
        mString = string;
    }

    public int getInt() {
        return mInt;
    }

    public void setInt(int anInt) {
        mInt = anInt;
    }

    public Boolean getBoolean() {
        return mBoolean;
    }

    public void setBoolean(Boolean aBoolean) {
        mBoolean = aBoolean;
    }

    public long getLong() {
        return mLong;
    }

    public void setLong(long aLong) {
        mLong = aLong;
    }

    public short getShort() {
        return mShort;
    }

    public void setShort(short aShort) {
        mShort = aShort;
    }

    public byte getByte() {
        return mByte;
    }

    public void setByte(byte aByte) {
        mByte = aByte;
    }

    public char getChar() {
        return mChar;
    }

    public void setChar(char aChar) {
        mChar = aChar;
    }

    private ParcelableBean(Parcel in) {
        setString(in.readString());
        setInt(in.readInt());
        setBoolean(in.readInt() == 1);
        setLong(in.readLong());
        setShort((short) in.readInt());
        setByte(in.readByte());
        setChar((char) in.readInt());
    }

    public static final Creator<ParcelableBean> CREATOR = new Creator<ParcelableBean>() {
        @Override
        public ParcelableBean createFromParcel(Parcel in) {
            return new ParcelableBean(in);
        }

        @Override
        public ParcelableBean[] newArray(int size) {
            return new ParcelableBean[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(mString);
        dest.writeInt(mInt);
        dest.writeInt(mBoolean ? 1 : 0);
        dest.writeLong(mLong);
        dest.writeInt(mShort);
        dest.writeByte(mByte);
        dest.writeInt(mChar);
    }

    @Override
    public String toString() {
        return "ParcelableBean{" +
                "mString='" + mString + '\'' +
                ", mInt=" + mInt +
                ", mBoolean=" + mBoolean +
                ", mLong=" + mLong +
                ", mShort=" + mShort +
                ", mByte=" + mByte +
                ", mChar=" + mChar +
                '}';
    }
}
/**
 * Parcelable序列化,随便启动一个Activity并将Parcelable对象放入Intent发送
 */
public void onParcelable(View view) {
    ParcelableBean bean = new ParcelableBean();
    startActivity(new Intent(this, RemoteActivity2.class).putExtra("parcelable_result", bean));
}

/**
 * Parcelable反序列化,在另一个Activity中读取并打印
 */
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_remote2);
    Parcelable parcelableResult = getIntent().getParcelableExtra("parcelable_result");
    if (parcelableResult instanceof ParcelableBean) {
        Log.e(TAG, "onDeparcelable: " + parcelableResult.toString());
    }
}

特别需要注意的是,在writeToParcel方法中的写入顺序必须和ParcelableBean构造函数中的读取顺序保持一致,因为在各种write方法中会写入当前的dataposition,在读取时也是按照dataposition来读取的。而且当写入各种容器时还会将容器长度也一并写入,如果乱序读取的话,是得不到正确结果的。

深入了解

  • Parcelable构造

    Parcel类中包装了可序列化的数据,可以再Binder中自由传输。Parcelable序列化和Serializable不同,不是简单的将类修饰标记下即可的,需要实现对象的序列化写入、反序列化读取以及内容描述。序列化写入是在writeToParcel方法中,通过Parcel中的一系列write方法完成的。反序列化读取是在CREATOR中完成,内部有两个方法分别是反序列化对象和反序列化数组,最终还是通过Parcel中一系列的read方法读取。内容描述是由describeContents方法完成,此方法几乎在所有的情况下都应该返回0,仅当对象中存在文件描述符时需要返回1。

    方法功能标志位
    createFromParcel(Parcel in)从序列化后的对象中创建原始对象
    newArray(int size)创建指定长度的原始对象数组
    ParcelableBean(Parcel in)从序列化后的对象中创建原始对象
    writeToParcel(Parcel dest, int flags)将当前对象写入序列化结构中,其中flags有两种值,0和1。为1时标识当前对象需要作为返回值返回,不能立即释放资源。几乎所有的情况都为0PARCELABLE_WRITE_RETURN_VALUE
    describeContents()返回当前对象的内容描述。如果含有文件描述符,则返回1,否则返回0.几乎所有情况都返回0CONTENTS_FILE_DESCRIPTOR

    系统中提供了很多实现了Parcelable接口的类,都是可以直接序列化的,包括Intent、Bundle、Bitmap等。与Serializable相同的,List、Map也是可以序列化的,同样的需要每个元素都得是可序列化的。

  • 创建数组

    public class ParcelableBean implements Parcelable {
    
        private static final String TAG = "ParcelableBean";
    
        public static final Creator<ParcelableBean> CREATOR = new Creator<ParcelableBean>() {
            @Override
            public ParcelableBean createFromParcel(Parcel in) {
                return new ParcelableBean(in);
            }
    
            @Override
            public ParcelableBean[] newArray(int size) {
                return new ParcelableBean[size];
            }
        };
    
        private String mString;
        private int mInt;
        private Boolean mBoolean;
        private long mLong;
        private short mShort;
        private byte mByte;
        private char mChar;
        private List<ParcelableBean1> mParcelableBean1List = new ArrayList<>();
        private ParcelableBean1[] mParcelableBean1Array = new ParcelableBean1[3];
        private ParcelableBean1[] mParcelableBean1Array1 = new ParcelableBean1[3];
    
        public ParcelableBean() {
            mString = "ABC";
            mInt = 21;
            mBoolean = true;
            mLong = 221;
            mShort = 2;
            mByte = (byte) 96;
            mChar = (char) 97;
            for (int i = 0; i < 3; i++) {
                mParcelableBean1List.add(new ParcelableBean1("TestParcelableList" + (i + 1)));
                mParcelableBean1Array[i] = new ParcelableBean1("TestParcelableArrayObject" + (i + 1));
                mParcelableBean1Array1[i] = new ParcelableBean1("TestParcelableArrayType" + (i + 1));
            }
        }
    
        private ParcelableBean(Parcel in) {
            setString(in.readString());
            setInt(in.readInt());
            setBoolean(in.readInt() == 1);
            setLong(in.readLong());
            setShort((short) in.readInt());
            setByte(in.readByte());
            setChar((char) in.readInt());
    
            in.readTypedList(mParcelableBean1List, ParcelableBean1.CREATOR);
    
            Object[] objects = in.readArray(getClass().getClassLoader());
            if (objects != null) {
                Log.e(TAG, "ParcelableBean: class type " + objects[0].getClass().getSimpleName());
                System.arraycopy(objects, 0, mParcelableBean1Array, 0, objects.length);
            }
    
            in.readTypedArray(mParcelableBean1Array1, ParcelableBean1.CREATOR);
    //        与readTypedArray方法一致,都是读取Parcelable对象数组,只不过readTypedArray是指定一个Array,并且此Array的长度与写入的数组长度一致,否则会报throw new RuntimeException("bad array lengths")异常
    //        而createTypedArray则是根据写入的Parcelable数组返回一个Array,不用担心数组长度问题
    //        通过查看源码发现,此方法调用的是CREATOR中的newArray方法,所以会在参数中需要一个目标对象的CREATOR
    //        mParcelableBean1Array1 = in.createTypedArray(ParcelableBean1.CREATOR);
        }
    
        @Override
        public int describeContents() {
            return 0;
        }
    
        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeString(mString);
            dest.writeInt(mInt);
            dest.writeInt(mBoolean ? 1 : 0);
            dest.writeLong(mLong);
            dest.writeInt(mShort);
            dest.writeByte(mByte);
            dest.writeInt(mChar);
            dest.writeTypedList(mParcelableBean1List);
            dest.writeArray(mParcelableBean1Array);
            dest.writeTypedArray(mParcelableBean1Array1, flags);
        }
    
        public String getString() {
            return mString;
        }
    
        public void setString(String string) {
            mString = string;
        }
    
        public int getInt() {
            return mInt;
        }
    
        public void setInt(int anInt) {
            mInt = anInt;
        }
    
        public Boolean getBoolean() {
            return mBoolean;
        }
    
        public void setBoolean(Boolean aBoolean) {
            mBoolean = aBoolean;
        }
    
        public long getLong() {
            return mLong;
        }
    
        public void setLong(long aLong) {
            mLong = aLong;
        }
    
        public short getShort() {
            return mShort;
        }
    
        public void setShort(short aShort) {
            mShort = aShort;
        }
    
        public byte getByte() {
            return mByte;
        }
    
        public void setByte(byte aByte) {
            mByte = aByte;
        }
    
        public char getChar() {
            return mChar;
        }
    
        public void setChar(char aChar) {
            mChar = aChar;
        }
    
        @Override
        public String toString() {
            return "ParcelableBean{" +
                    "mString='" + mString + '\'' +
                    ", mInt=" + mInt +
                    ", mBoolean=" + mBoolean +
                    ", mLong=" + mLong +
                    ", mShort=" + mShort +
                    ", mByte=" + mByte +
                    ", mChar=" + mChar +
                    ", mParcelableBean1List=" + mParcelableBean1List.size() + " " + mParcelableBean1List +
                    ", ArrayObject=" + mParcelableBean1Array[0] +
                    ", " + mParcelableBean1Array[1] +
                    ", " + mParcelableBean1Array[2] +
                    ", ArrayType=" + mParcelableBean1Array1[0] +
                    ", " + mParcelableBean1Array1[1] +
                    ", " + mParcelableBean1Array1[2] +
                    '}';
        }
    }
    
    public class ParcelableBean1 implements Parcelable {
    
        private static final String TAG = "ParcelableBean1";
    
        public static final Creator<ParcelableBean1> CREATOR = new Creator<ParcelableBean1>() {
            @Override
            public ParcelableBean1 createFromParcel(Parcel in) {
                Log.e(TAG, "createFromParcel: ");
                return new ParcelableBean1(in);
            }
    
            @Override
            public ParcelableBean1[] newArray(int size) {
                Log.e(TAG, "newArray: " + size);
                return new ParcelableBean1[size];
            }
        };
    
        private String mString;
        private List<String> mList = new ArrayList<>();
        private Map<String, String> mMap = new HashMap<>();
    
        public ParcelableBean1(String str) {
            mString = str;
            mList.add("ParcelableBean1");
            mMap.put("ParcelableBean1Key", "ParcelableBean1Value");
        }
    
        private ParcelableBean1(Parcel in) {
            in.readStringList(mList);
            in.readMap(mMap, getClass().getClassLoader());
            mString = in.readString();
            Log.e(TAG, "ParcelableBean1: " + mString);
        }
    
        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeStringList(mList);
            dest.writeMap(mMap);
            dest.writeString(mString);
        }
    
        @Override
        public int describeContents() {
            return 0;
        }
    
        @Override
        public String toString() {
            return "ParcelableBean1{" +
                    "mString='" + mString + '\'' +
                    ", mList=" + mList +
                    ", mMap=" + mMap +
                    '}';
        }
    }
    

    将简单使用中的ParcelableBean作了一点改动,内部属性除了基本类型再加个Parcelable数组。新建一个ParcelableBean1对象,内部属性为List和Map,都统一测试一下。

    在ParcelableBean中通过writeTypedList方法写入了一个ParcelableList,通过writeArray方法写入了一个ObjectArray,但是元素还都是Parcel,通过writeTypedArray方法写入一个ParcelableArray。Parcel中的方法名中带Typed的就是写入Parcelable对象的方法,在读取时需要传入Parcelable的CREATOR变量。readTypedList方法就是读取写入的ParcelableList,readArray方法就是读取ObjectArray,得注意的是需要传入一个ClassLoader,读取ParcelableArray可以用readTypedArray方法也可以用createTypedArray方法。

    其中createTypedArray方法和readTypedArray方法的区别就是readTypedArray方法需要一个现成的数组,并且该数组的长度必须与写入时数组的长度一致,否则就会报throw new RuntimeException(“bad array lengths”)异常,而createTypedArray方法是不需要事先准备一个数组的,会返回一个新的数组,在创建返回数组时是会调用Parcelable中CREATOR的newArray方法,newArray方法只有此一个用处。

    在ParcelableBean1中就简单多了,简单的写入List、Map,简单的读取List和Map,读取Map也是需要提供一个ClassLoader。以下的是传递后的结果:

    E/RemoteActivity2: onDeparcelable: ParcelableBean{
    	mString='ABC', mInt=21, mBoolean=true, mLong=221, mShort=2, mByte=96, mChar=a, 
    	mParcelableBean1List=3 [
    		ParcelableBean1{mString='TestParcelableList1', mList=[ParcelableBean1], mMap={ParcelableBean1Key=ParcelableBean1Value}}, 
    		ParcelableBean1{mString='TestParcelableList2', mList=[ParcelableBean1], mMap={ParcelableBean1Key=ParcelableBean1Value}}, 
    		ParcelableBean1{mString='TestParcelableList3', mList=[ParcelableBean1], mMap={ParcelableBean1Key=ParcelableBean1Value}}
    	], 
    	ArrayObject=
    		ParcelableBean1{mString='TestParcelableArrayObject1', mList=[ParcelableBean1], mMap={ParcelableBean1Key=ParcelableBean1Value}}, 
    		ParcelableBean1{mString='TestParcelableArrayObject2', mList=[ParcelableBean1], mMap={ParcelableBean1Key=ParcelableBean1Value}}, 
    		ParcelableBean1{mString='TestParcelableArrayObject3', mList=[ParcelableBean1], mMap={ParcelableBean1Key=ParcelableBean1Value}}, 
    	ArrayType=
    		ParcelableBean1{mString='TestParcelableArrayType1', mList=[ParcelableBean1], mMap={ParcelableBean1Key=ParcelableBean1Value}}, 
    		ParcelableBean1{mString='TestParcelableArrayType2', mList=[ParcelableBean1], mMap={ParcelableBean1Key=ParcelableBean1Value}}, 
    		ParcelableBean1{mString='TestParcelableArrayType3', mList=[ParcelableBean1], mMap={ParcelableBean1Key=ParcelableBean1Value}}
    }
    
  • 持久化

    Parcelable的持久化与Serializable差不多,都是将ByteArray写入文件,通过读方法说明就可以发现Android是极不推荐将Parcelable持久化的,因为不像Serializable一样如果在指定了SerialVersionID后,反序列化的时候会根据类属性的更改而会适应更改,维护性基本没有,有个属性的增删就会导致读取出问题。此处只是写个示例演示下。

    /**
     * Parcelable序列化
     */
    @SuppressLint("ApplySharedPref")
    public void onParcelable(View view) {
        ParcelableBean bean = new ParcelableBean();
          startActivity(new Intent(this, RemoteActivity2.class).putExtra("parcelable_result", bean));
        byte[] bytes = marshallParcel(bean);
        byte[] encode = Base64.encode(bytes, Base64.DEFAULT);
        mSharedPreferences.edit().putString("parcelable_result", new String(encode)).commit();
    }
    
    public byte[] marshallParcel(Parcelable dest) {
        Parcel parcel = Parcel.obtain();
        parcel.setDataPosition(0);
        dest.writeToParcel(parcel, 0);
        byte[] marshall = parcel.marshall();
        parcel.recycle();
        return marshall;
    }
    
    /**
     * Parcelable反序列化
     */
    public void onDeparcelable(View view) {
        String string = mSharedPreferences.getString("parcelable_result", null);
        if (string != null) {
            Parcelable parcelableResult = unMarshallParcel(Base64.decode(string, Base64.DEFAULT));
            if (parcelableResult instanceof ParcelableBean) {
                Log.e(TAG, "onDeparcelable: " + parcelableResult.toString());
                mShowSerializableResultText.setText(parcelableResult.toString());
            }
        }
    }
    
    public Parcelable unMarshallParcel(byte[] bytes) {
        Log.e(TAG, "unMarshallParcel: " + bytes.length);
        Parcel parcel = Parcel.obtain();
        parcel.unmarshall(bytes, 0, bytes.length);
        parcel.setDataPosition(0);
        Parcelable parcelable = ParcelableBean.CREATOR.createFromParcel(parcel);
        parcel.recycle();
        return parcelable;
    }
    

    持久序列化就是先新获取一个Parcel对象,然后将需要持久化的Parcelable对象写入Parcel,通过marshall方法获得对象的ByteArray,将ByteArray转成String保存或者传输。读取时先将String转成ByteArray,通过新获取的Parcel对象的unmarshall方法把数据写到Parcel对象中,别忘了重置下dataposition,让对象在创建时是从头开始读取序列化数据,之后可以用Parcelable对象的CREATOR中的createFromParcel方法创建相应的对象,此时就相当于普通反序列化的程序步骤了。

总结

Serializable是Java中的序列化接口,使用简单,但是开销大,序列化和反序列化过程都需要大量的IO操作。

Parcelable是Android中特有的序列化方式,使用略显麻烦,但是在Android平台上效率是比Serializable高的,也是Android推荐的序列化方式。但Parcelable将对象序列化到持久存储或者通过网络传输就显得比Serializable麻烦太多。

当主要是在内存中序列化时首选Parcelable,当需要持久化或者网络传输时推荐用Serializable。

Android 10.0 bad array lengths 问题解决 在10.0的系统rom定制化开发中,在进行开发中,会出现bad array lengths的异常功能,接下来 就需要看下相关的系统源码中,关于这部分的源码,看下问题的解决功能分析 阅读详情

相关推荐

Android APK启动Service的错误处理

Android APK启动Service的错误处理

阿xin*的嵌入式AI 284

java 数组长度异常_自定义通知:java.lang.RuntimeException:错误的数组长度

我想利用谷歌的样品用在我的Android应用程序的自定义通知这里(部分创建自定义通知布局).由于我使用了这个确切的代码,我RuntimeException每隔几次就会收到通知.我的堆栈跟踪:FATAL EXCEPTION: mainjava.lang.RuntimeException: bad array lengthsat android.os.Parcel.readIntArray(Parce...

weixin_33290540的博客 1942

Android 中通过Intent传递类对象,通过实现Serializable和Parcelable接口两种方式传递对象

方式一:通过实现Serializable接口传递对象 用一个小的Demo去理解,通过实现Serializable接口传递对象。 效果图: 具体讲解在代码注释中已经写出 先创建一个对象: package com.example.daydemo.bean; import java.io.Serializable; //必须实现Serializable接口,此对象才有传递的资格 public class Person implements Serializable { private String n

路宇的博客 1130

java.lang.RuntimeException: bad array lengths

在自己写的电子市场中下载“灵客风健康宝典”,默认情况下是下载完毕之后系统自动安装,自动安装不成功的,再调用系统安装(手动安装)。 现在,下载完毕后,后台自动安装了,页面上还提示手动安装。 查看log: 01-12 05:44:17.331: DEBUG/ZMKSMarket(988): as:灵客风健康宝典 01-12 05:44:17.691: ERROR/JavaBinder(988)

8975

Android Intent数据传递与对象序列化

通过Intent的putExtra()方法,我们可以传递各种类型的数据,包括基本类型和实现了Serializable接口的自定义对象。如果需要传递复杂的自定义对象,可以通过对象序列化来实现。这个方法接受一个键值对参数,其中键是一个字符串,值可以是任意基本类型或实现了Serializable接口的对象。在接收端,我们可以使用getXXXExtra()方法来获取传递过来的数据,其中XXX表示数据类型。在接收端,我们可以通过getSerializableExtra()方法来获取传递过来的序列化对象。

BitSlinger的博客 356

Android service aidl编写示例(Android Studio/eclipse)

这是我博客附带的工程,关于Android service aidl开发的

android对象序列化的方法,Android 进阶-两种序列化方式 Serializable 和 Parcelable

[导读]序列化 (Serialization)将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。什么是序列化我们总是说着或者听说着“序列化”,它的定义是什么呢?序列化 (Serialization)将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态...

weixin_29532075的博客 578

从零搭建PIC MCU开发环境:MPLAB IDE与HI-TECH编译器安装破解全攻略

本文详细介绍了从零搭建PIC MCU开发环境的完整流程,包括MPLAB IDE 8.85的安装配置、HI-TECH C编译器的安装与破解,以及PICkit3编程器的使用技巧。通过清晰的步骤说明和常见问题解决方案,帮助开发者快速搭建稳定的PIC单片机开发环境,适用于PIC16/18系列MCU开发。

weixin_29188997的博客 292

Android对象序列化

1、什么是对象序列化   我们知道字符串信息可以通过IO流实现数据到本地磁盘的持久化或者通过网络进行数据传输,而所有的基本数据类型都可以转换为字符串,所以说基本数据类型也具备这些功能;而对象作为数据的一种集合体,是否也能实现同样的功能,一个在堆中创建的对象可以存储到本地磁盘文件中或者传输给网络中某个客户端,并且可以通过某种方式把它再恢复为对象,这就是Java中关于序列化的作用:把对象持久化到存储

Noodles的博客 778

CE 7.4中文版实战:如何用自动汇编功能快速生成游戏修改器(附CT表保存技巧)

本文详细介绍了CE 7.4中文版中自动汇编功能的使用方法,帮助用户快速生成游戏修改器。从环境配置、内存地址定位到自动汇编代码注入,再到CT表保存和独立EXE生成,提供全流程实战指南,适合逆向工程爱好者和游戏修改器开发者。

keras9composer的博客 357

Java 大视界 -- Java+Spark MLlib 构建智能推荐系统:协同过滤算法实战与优化(441)

Java+Spark MLlib构建智能推荐系统实战 本文详细介绍了基于Java和Spark MLlib构建协同过滤推荐系统的全流程实践方案。主要内容包括: 架构设计:采用离线批处理为主、近实时更新为辅的架构,包含数据采集层、处理层、模型层、推荐生成层、存储层和应用层六大模块,可支撑千万级用户推荐需求。 技术实现: 使用Spark MLlib的ALS算法进行协同过滤模型训练 提供数据清洗、模型训练、推荐生成等核心代码实现 采用MySQL存储推荐结果,支持批量插入和事务保障 生产级优化: 版本化模型存储规避覆

【青云交】华为云云享专家 | 阿里云开发者社区专家博主 技术圈个人影响力前 9 | 博客之星 TOP12 优快云 首位四榜(原力榜 / 作者周榜 / 领军人物 / 综合热榜)榜首,破平台纪录! 苏州地区全榜霸榜,感恩全网十多万粉丝同行! 2530

Android对象序列化实现

Android序列化实现 那么什么是序列化?为什么需要使用序列化? 序列化可以简单地理解为将对象转化为流,它是一种用来处理对象流的机制。可以对流化后的对象持续化到存储设备上,也可将流化后的对象传输于网络之间。利用对象序列化可以进行对象的“深复制”,即复制对象本身及引用的对象本身。Android实现序列化的方法继承Serializable接口Serializable是Java提供的序列化接口,为对

章鱼鸽的博客 778

【IIC通信】Chap.1(I2C)IIC通信原理、IIC读写时序详解

IIC通信以两根连线SDA、SCL实现了完善的全双工同步数据传送, 可以方便地构成多机系统和外围器件扩展系统。

Jie_MSD的博客 2万+

android 对象序列化

android 对象序列化

xie__jin__cheng的博客 497

学Simulink--人形机器人控制场景实例:基于Simulink的多连杆人形机器人拉格朗日动力学建模仿真

matlab编辑% 连杆长度 (m)% 躯干(髋到CoM)% 大腿% 小腿% 质量 (kg)% 转动惯量(绕质心,简化为杆)matlab编辑% 躯干CoM(假设在髋上方L_body/2处)% 左大腿CoM% 左小腿CoM% (右腿类似,略)本文完成了基于Simulink的多连杆人形机器人拉格朗日动力学建模仿真,实现了:✅ 定义7-DOF平面人形结构✅ 使用Symbolic Math Toolbox自动推导动力学方程✅ 生成。

amy_mhd的博客 397

大数据领域数据采集的人工智能辅助技术

在大数据时代,“数据是石油”的隐喻早已深入人心,但采集高质量数据的过程却像“在大海里捞针”——数据源分散如繁星、格式杂乱如碎片、质量参差不齐如泥沙,传统采集方法(写死的脚本、固定的ETL流程)早已力不从心。这篇文章将带你走进AI辅助数据采集的世界:我们会用“智能捕鱼船”的比喻拆解AI如何解决传统采集的痛点,深入解析“数据源自动发现”“自适应数据解析”“实时质量管控”等核心技术的原理,用代码示例还原AI采集的真实流程,最后探讨多模态采集、联邦学习等未来趋势。

小程序开发 2354

android 内部序列化对象

android 内部序列化对象

xie__jin__cheng的博客 492

从零实现深度学习框架【实现自己的PyTorch(已完结)】

本着“凡我不能创造的,我就不能理解”的思想,此系列文章会基于纯Python以及NumPy从零创建自己的深度学习框架,该框架类似PyTorch能实现自动求导。

日积月累,天道酬勤 6862

android 对象内部序列化

android 对象内部序列化

xie__jin__cheng的博客 375

Isaac Sim 入门教程(四)强化学习策略训练宇树人形机器人四足机器人示例

isaac_sim_policy_example Extension 是在 Isaac Sim 中部署 Isaac Lab 强化学习策略的一个框架和一组辅助函数。有关在 Isaac Sim 中训练和构建策略的详细信息,请访问在 Isaac Sim 中部署策略。该扩展程序默认已启用。如果被禁用,可在扩展管理器中搜索 isaacsim.robot.policy.example 重新启用。要运行下面的示例,请激活 “视窗”>“示例”>“机器人示例”,这将打开 “机器人示例 ”选项卡。

机器人控制与仿真应用 3924

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

Android 序列化对象

1.Serializable接口 Serializable是Java提供的一个序列化接口,为对象提供标准的序列化和反序列化操作。 内部序列化对象的原理: 序列化过程: val shape = Shape(2,&quot;Rec&quot;) val out = ObjectOutputStream(FileOutputStream(&quot;serializable.txt&quot;)) out...

qq_34341338的博客 1115

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

Android实现Parcelable对象序列化的实例

bundle.putParcelable可以实现传递对象,但是这个对象的类必须实现Parcelable接口才能够使用。 下面是一个简单的在Activity之间传递对象的例子。 parcel实现参数的序列化,把类对象序列化为齐位的二进制流,提高传输的效率。 Parcelable类。最主要的类,也就是我们要传送的对象的类,需要实现Parcelable接口。 import

张勤一 4864

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

Android开发篇之对象序列化

Android开发篇之对象序列化什么是序列化?序列化是将对象状态转换为可保持或传输的格式的过程。与序列化相对的是反序列化,它将流转换为对象。而Serializable和Parcelable接口就可以完成对象的序列化过程,当我们需要通过Intent和Binder传输数据时就需要使用Parcelable或者Serializable。还有的时候我们需要把对象持久化到存储设备上或者通过网络传输给其他客户端,

小牧的博客 3310

android 序列化存储对象,android中對象序列化存儲

項目中要存儲一些數據為了提高不必要的網絡請求,提高效率,用到數據持久化的知識點,針對這個問題,解決辦法其實有很多,以前在項目中是服務獲取到webservice的xml,然后直接將xml保存在本地,之后解析本地xml的格式,這種辦法是針對無參數的一些webservice的數據,比如給一個連接,就可以獲取到xml的數據,比如就是這樣寫的:/*** 更新教練的信息和圖片* **/public boole...

weixin_34347959的博客 157
上一篇: Android-多进程
libolf
博客等级 码龄9年 0粉丝 4原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值