目录
- 一、序列化
- 1.1 基本概念
- 1.1.1 序列化
- 1.1.2 反序列化
- 1.1.3 数据结构、对象与二进制串
- 1.1.4 序列化/反序列化的目的
- 1.2 几种常见的序列化和反序列化协议
- 1.2.1 XML&SOAP
- 1.2.2 JSON(Javascript Object Notation)
- 1.2.3 Protobuf
- 二、安卓下的序列化方案
- 2.1 Serializable接口
- 2.2 使用示例
- 2.2.1 Serializable基础使用
- 2.2.2 Externalizable基础使用
- 2.2.3 serialVersionUID的使用
- 2.2.4 瞬态 trasient 变量的作用
- 2.2.5 类中的成员未实现可序列化接口
- 2.2.6 父类未序列化,子类实现了序列化的情况
- 2.2.7 父类实现序列化,子类控制自己是否实现序列化
- 2.2.8 序列化枚举类
- 2.2.9 序列化单例
- 2.3 序列化流程
- 2.4 安卓Parcelable接口
- 2.4.1 重要概念和用法
- 2.4.1.1 实现 Parcelable 接口
- 2.4.1.2 内置数据类型支持
- 2.4.1.3 性能优化
- 2.4.1.4 使用场景
- 2.4.1.5 注意事项
- 2.4.2 Parcelable和Serializable的区别
- 2.4.3 Parcelable基础使用
一、序列化
1.1 基本概念
- 由于在系统底层,数据的传输形式是简单的字节序列形式传递,即在底层,系统不认识对象,只认识字节序列,而为了达到进程通讯的目的,需要先将数据序列化,而序列化就是将对象转化字节序列的过程。相反地,当字节序列被运到相应的进程的时候,进程为了识别这些数据,就要将其反序列化,即把字节序列转化为对象
- 无论是在进程间通信、本地数据存储又或者是网络数据传输都离不开序列化的支持。而针对不同场景选择合适的序列化方案对于应用的性能有着极大的影响。
- 从广义上讲,数据序列化就是将数据结构或者是对象转换成我们可以存储或者传输的数据格式的一个过程,在序列化的过程中,数据结构或者对象将其状态信息写入到临时或者持久性的存储区中,而在对应的反序列化过程中,则可以说是生成的数据被还原成数据结构或对象的过程。
- 数据序列化相当于是将我们原先的对象序列化概念做出了扩展,在对象序列化和反序列化中,我们熟知的有两种方法,其一是Java语言中提供的Serializable接口,其二是Android提供的Parcelable接口。而在这里,因为我们对这个概念做出了扩展,因此也需要考虑几种专门针对数据结构进行序列化的方法,如现在那些个开放API一般返回的数据都是JSON格式的,又或者是我们Android原生的SQLite数据库来实现数据的本地存储,从广义上来说,这些都可以算做是数据的序列化。
1.1.1 序列化
将数据结构或对象转换成二进制串的过程
1.1.2 反序列化
将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程
1.1.3 数据结构、对象与二进制串
不同的计算机语言中,数据结构,对象以及二进制串的表示方式并不相同。
数据结构和对象:对于类似 Java 这种完全面向对象的语言,工程师所操作的一切都是对象(Object),来自于类的实例化。在 Java 语言中最接近数据结构的概念,就是 POJO(Plain Old JavaObject)或者 Javabean--那些只有 setter/getter 方法的类。而在 C 二进制串:序列化所生成的二进制串指的是存储在内存中的一块数据。C 语言的字符串可以直接被传输层使用,因为其本质上就是以’0’结尾的存储在内存中的二进制串。在 Java 语言里面,二进制串的概念容易和 String 混淆。实际上String 是 Java 的一等公民,是一种特殊对象(Object)。对于跨语言间的通讯,序列化后的数据当然不能是某种语言的特殊数据类型。二进制串在 Java 里面所指的是 byte[ ],byte 是 Java 的 8 中原生数据类型之一(Primitive data types)
1.1.4 序列化/反序列化的目的
- 序列化: 主要用于网络传输,数据持久化,一般序列化也称为编码(Encode)
- 反序列化: 主要用于从网络,磁盘上读取字节数组还原成原始对象,一般反序列化也称为解码
(Decode)
具体的讲: - 永久的保存对象数据(将对象数据保存在文件当中,或者是磁盘中)
- 通过序列化操作将对象数据在网络上进行传输(由于网络传输是以字节流的方式对数据进行传输的,因此序列化的目的是将对象数据转换成字节流的形式)
- 将对象数据在进程之间进行传递(Activity之间传递对象数据时,需要在当前的Activity中对对象数据进行序列化操作,在另一个Activity中需要进行反序列化操作讲数据取出)
Java平台允许我们在内存中创建可复用的Java对象,但一般情况下,只有当JVM处于运行时,这些对象才可能存在,即这些对象的生命周期不会比JVM的生命周期更长(即每个对象都在JVM中),但在现实应用中,就可能要停止JVM运行,但有要保存某些指定的对象,并在将来重新读取被保存的对象。这时Java对象序列化就能够实现该功能。(可选择入数据库、或文件的形式保存) - 序列化对象的时候只是针对变量进行序列化,不针对方法进行序列化。
- 在Intent之间,基本的数据类型直接进行相关传递即可,但是一旦数据类型比较复杂的时候,就需要进行序列化操作了。
1.2 几种常见的序列化和反序列化协议
1.2.1 XML&SOAP
XML 是一种常用的序列化和反序列化协议,具有跨机器,跨语言等优点,SOAP(Simple Object Access protocol) 是一种被广泛应用的,基于 XML 为序列化和反序列化协议的结构化消息传递协议
1.2.2 JSON(Javascript Object Notation)
JSON 起源于弱类型语言 Javascript, 它的产生来自于一种称之为"Associative array"的概念,其本质是就是采用"Attribute-value"的方式来描述对象。实际上在 Javascript 和 PHP 等弱类型语言中,类的描述方式就是 Associative array。JSON 的如下优点,使得它快速成为最广泛使用的序列化协议之一。
- 这种 Associative array 格式非常符合工程师对对象的理解。
- 它保持了 XML 的人眼可读(Human-readable)的优点。
- 相对于 XML 而言,序列化后的数据更加简洁。 研究表明:XML 所产生序列化之后文件的大小接近 JSON 的两倍
- 它具备 Javascript 的先天性支持,所以被广泛应用于 Web browser 的应用常景中,是 Ajax 的事实标准协议。
- 与 XML 相比,其协议比较简单,解析速度比较快。
- 松散的 Associative array 使得其具有良好的可扩展性和兼容性
1.2.3 Protobuf
Protobuf 具备了优秀的序列化协议的所需的众多典型特征。
- 标准的 IDL 和 IDL 编译器,这使得其对工程师非常友好。
- 序列化数据非常简洁,紧凑,与 XML 相比,其序列化之后的数据量约为 1/3 到 1/10。
- 解析速度非常快,比对应的 XML 快约 20-100 倍。
- 提供了非常友好的动态库,使用非常方便,反序列化只需要一行代码。
二、安卓下的序列化方案
2.1 Serializable接口
public interface Serializable {
}
public interface Externalizable extends java.io.Serializable {void writeExternal(ObjectOutput out) throws IOException;void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}
2.2 使用示例
2.2.1 Serializable基础使用
public class demo01 {static class User implements Serializable {public User(String name, int age) {this.name = name;this.age = age;}public String name;public int age;public String nickName;@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", age=" + age +", nickName=" + nickName +'}';}}static class User1 implements Serializable {private static final long serialVersionUID = 2;public User1(String name, int age) {this.name = name;this.age = age;}public String name;public int age;public String nickName;@Overridepublic String toString() {return "User1{" +"name='" + name + '\'' +", age=" + age +", nickName=" + nickName +'}';}}public static String basePath = System.getProperty("user.dir") + "\\";public static String tmp = "D:\\henryTest\\";public static void main(String[] args) {NoSerialIdTest();// HasSerialIdTest();}private static void NoSerialIdTest() {User user = new User("zero", 20);SerializeableUtils.saveObject(user,tmp+"a.out");System.out.println("1: " + user);user = SerializeableUtils.readObject(tmp + "a.out");System.out.println("反序列化: 2: " + user);}private static void HasSerialIdTest() {User1 user = new User1("zero", 18);
// SerializeableUtils.saveObject(user,tmp+"b.out");
// System.out.println("1: " + user);user = SerializeableUtils.readObject(tmp + "b.out");System.out.println("2: " + user);}
}
public class SerializeableUtils {public static <T> byte[] serialize(T t) throws Exception{ByteArrayOutputStream out = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(out);oos.writeObject(t);return out.toByteArray();}public static <T> T deserialize(byte[] bytes)throws Exception{//TODO:ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));T t = (T)ois.readObject();return t;}/**** @param obj* @param path* @return*/synchronized public static boolean saveObject(Object obj, String path) {//持久化if (obj == null) {return false;}ObjectOutputStream oos = null;try {oos = new ObjectOutputStream(new FileOutputStream(path));// 创建序列化流对象oos.writeObject(obj);oos.close();return true;} catch (IOException e) {e.printStackTrace();} finally {if (oos != null) {try {oos.close(); // 释放资源} catch (IOException e) {e.printStackTrace();}}}return false;}/*** 反序列化对象** @param path* @param <T>* @return*/@SuppressWarnings("unchecked ")synchronized public static <T> T readObject(String path) {ObjectInputStream ojs = null;try {ojs = new ObjectInputStream(new FileInputStream(path));// 创建反序列化对象return (T) ojs.readObject();// 还原对象} catch (IOException | ClassNotFoundException e) {e.printStackTrace();} finally {if(ojs!=null){try {ojs.close();// 释放资源} catch (IOException e) {e.printStackTrace();}}}return null;}}
输出:
生成文件:
2.2.2 Externalizable基础使用
writeExternal(ObjectOutput out)
readExternal(ObjectOutput out)
注意点:
读写顺序要求一致
读写的成员变量的个数
必须要有一个public的无参构造函数
public class demo06 {static class User implements Externalizable {//必须要一个public的无参构造函数public User() {}public User(String name, int age) {this.name = name;this.age = age;}public String name;public int age;@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", age=" + age +'}';}@Overridepublic void writeExternal(ObjectOutput out) throws IOException {out.writeObject(name);out.writeInt(age);}@Overridepublic void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {name = (String) in.readObject();age = in.readInt();}}public static String basePath = System.getProperty("user.dir") + "\\";public static String tmp = "D:\\henryTest\\";public static void main(String[] args) {ExternalableTest();}private static void ExternalableTest() {User user = new User("zero", 18);System.out.println("1: " + user);ByteArrayOutputStream out = new ByteArrayOutputStream();ObjectOutputStream oos = null;byte[] userData = null;try {oos = new ObjectOutputStream(out);oos.writeObject(user);userData = out.toByteArray();} catch (IOException e) {e.printStackTrace();}ObjectInputStream ois = null;try {ois = new ObjectInputStream(new ByteArrayInputStream(userData));user = (User) ois.readObject();System.out.println("反序列化后 2: " + user);} catch (Exception e) {e.printStackTrace();}}}
输出:
2.2.3 serialVersionUID的使用
serialVersionUID 是一个 private static final long 型 ID, 当它被印在对象上时, 它通常是对象的哈希码,你可以使用 serialver 这个 JDK 工具来查看序列化对象的 serialVersionUID。SerialVerionUID 用于对象的版本控制。 也可以在类文件中指定 serialVersionUID。 不指定serialVersionUID的后果是,当你添加或修改类中的任何字段时, 则已序列化类将无法恢复, 因为为新类和旧序列化对象生成的serialVersionUID 将有所不同。
与2.2.1 基础使用对比:
不加序列化版本ID测试项,去掉nickName属性,去掉写入操作,直接读取报错。
......static class User implements Serializable {public User(String name, int age) {this.name = name;this.age = age;}public String name;public int age;// public String nickName;@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", age=" + age +
// ", nickName=" + nickName +'}';}}
.......private static void NoSerialIdTest() {User user = new User("zero", 20);
// SerializeableUtils.saveObject(user,tmp+"a.out");
// System.out.println("1: " + user);user = SerializeableUtils.readObject(tmp + "a.out");System.out.println("反序列化: 2: " + user);}
报错:流对象和本地对象版本id不同
添加序列化版本ID测试项,去掉nickName属性,只要 serialVersionUID
值不变不会发生此类报错 。这里不再添加代码和测试结果。
2.2.4 瞬态 trasient 变量的作用
在序列化过程中,有时候我们希望某些对象的字段不被序列化,这时可以使用关键字
transient
来标记这些字段。
当一个字段被标记为 transient 时,该字段的值不会被序列化,即在将对象转换为字节流时,这些字段的值不会被包含在序列化数据中。在反序列化时,这些字段会被赋予默认值
/*** 瞬态transient的作用*/
public class demo02 {static class User implements Serializable {public User() {System.out.println("=============");}public User(String name, int age) {System.out.println("==============");this.name = name;this.age = age;}public String name;public int age;public transient String nickName;@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", age=" + age +", nickName=" + nickName +'}';}}public static String basePath = System.getProperty("user.dir") + "\\";public static String tmp = "D:\\henryTest\\";public static void main(String[] args) {trasientTest();}private static void trasientTest() {User user = new User("zero", 18);user.nickName = "Zero老师";System.out.println("1: " + user);ByteArrayOutputStream out = new ByteArrayOutputStream();ObjectOutputStream oos = null;byte[] userData = null;try {oos = new ObjectOutputStream(out);oos.writeObject(user);userData = out.toByteArray();} catch (IOException e) {e.printStackTrace();}ObjectInputStream ois = null;try {ois = new ObjectInputStream(new ByteArrayInputStream(userData));user = (User) ois.readObject();System.out.println("反序列化后 2: " + user);} catch (Exception e) {e.printStackTrace();}}}
输出:
2.2.5 类中的成员未实现可序列化接口
/*** 类中的一个成员未实现可序列化接口*/
public class demo03 {static class NickName {private String firstName;private String lastName;public NickName(String firstName, String lastName) {this.firstName = firstName;this.lastName = lastName;}public String getFirstName() {return firstName;}public void setFirstName(String firstName) {this.firstName = firstName;}public String getLastName() {return lastName;}public void setLastName(String lastName) {this.lastName = lastName;}@Overridepublic String toString() {return "NickName{" +"firstName='" + firstName + '\'' +", lastName='" + lastName + '\'' +'}';}}static class User implements Serializable {public User(String name, int age) {this.name = name;this.age = age;}public User(String name, int age, NickName nickName) {this.name = name;this.age = age;this.nickName = nickName;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public NickName getNickName() {return nickName;}public void setNickName(NickName nickName) {this.nickName = nickName;}private String name;private int age;private NickName nickName;@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", age=" + age +", nickName=" + nickName +'}';}}public static void main(String[] args) {NotSerializeTest();}private static void NotSerializeTest() {User user = new User("zero", 18, new NickName("Henry", "老师"));System.out.println("1: " + user);ByteArrayOutputStream out = new ByteArrayOutputStream();ObjectOutputStream oos = null;byte[] userData = null;try {oos = new ObjectOutputStream(out);oos.writeObject(user);userData = out.toByteArray();} catch (IOException e) {e.printStackTrace();}ObjectInputStream ois = null;try {ois = new ObjectInputStream(new ByteArrayInputStream(userData));user = (User) ois.readObject();System.out.println("反序列化后 2: " + user);} catch (Exception e) {e.printStackTrace();}}}
会报未实现序列化异常:
2.2.6 父类未序列化,子类实现了序列化的情况
默认父类属性不会被序列化
/*** 1. 如果不需要保存父类的值,那么没什么问题,只不过序列化会丢失父类的值* 2. 如果在子类保存父类的值,则需要在父类提供一个无参构造,不然报错InvalidClassException* 子类在序列化的时候需要额外的序列化父类的域(如果有这个需要的话)。那么在反序列的时候,* 由于构建User实例的时候需要先调用父类的构造函数,然后才是自己的构造函数。反序列化时,为了构造父对象,只能调用父类的无参构造函数作为默认的父对象,因此当我们取父对象的变量值时,* 它的值是调用父类无参构造函数后的值。如果你考虑到这种序列化的情况,在父类无参构造函数中对变量进行初始化。或者在readObject方法中进行赋值。* 我们只需要在Person中添加一个空的构造函数即可* 3. 自定义序列化过程*/
public class demo04 {static class Person{private String sex;private int id;public Person() {}public Person(String sex, int id) {this.sex = sex;this.id = id;}public String getSex() {return sex;}public void setSex(String sex) {this.sex = sex;}public int getId() {return id;}public void setId(int id) {this.id = id;}@Overridepublic String toString() {return "Person{" +"sex='" + sex + '\'' +", id=" + id +'}';}}static class User extends Person implements Serializable {public User(String name, int age,String sex,int id) {super(sex,id);this.name = name;this.age = age;}public User(){super();};public String name;public int age;// private void writeObject(ObjectOutputStream out) throws IOException {//不是重写父类的方案
// out.defaultWriteObject();
// out.writeObject(getSex());
// out.writeInt(getId());
// }
//
// private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
// in.defaultReadObject();
// setSex((String)in.readObject());
// setId(in.readInt());
// }@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", age=" + age +"} " + super.toString();}}public static String basePath = System.getProperty("user.dir") + "\\";public static String tmp = "D:\\henryTest\\";public static void main(String[] args) {parentNotSerializeTest();}private static void parentNotSerializeTest() {User user = new User("zero", 18,"男",1);System.out.println("1: " + user);ByteArrayOutputStream out = new ByteArrayOutputStream();ObjectOutputStream oos = null;byte[] userData = null;try {oos = new ObjectOutputStream(out);oos.writeObject(user);userData = out.toByteArray();} catch (IOException e) {e.printStackTrace();}ObjectInputStream ois = null;try {ois = new ObjectInputStream(new ByteArrayInputStream(userData));user = (User)ois.readObject();System.out.println("反序列化后 2: " + user);} catch (Exception e) {e.printStackTrace();}}}
输出:
如果想要父类属性写入序列化过程:子类重写方法并调用父类set方法设置父类值
private void writeObject(ObjectOutputStream out) throws IOException {//不是重写父类的方案out.defaultWriteObject();out.writeObject(getSex());out.writeInt(getId());}private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {in.defaultReadObject();setSex((String)in.readObject());setId(in.readInt());}
2.2.7 父类实现序列化,子类控制自己是否实现序列化
public class demo05 {static class Person implements Serializable {private static final long serialVersionUID = 5850510148907441688L;private String sex;private int id;public Person() {}public Person(String sex, int id) {this.sex = sex;this.id = id;}public String getSex() {return sex;}public void setSex(String sex) {this.sex = sex;}public int getId() {return id;}public void setId(int id) {this.id = id;}@Overridepublic String toString() {return "Person{" +"sex='" + sex + '\'' +", id=" + id +'}';}}static class User extends Person {public User(String name, int age, String sex, int id) {super(sex, id);this.name = name;this.age = age;}public String name;public int age;@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", age=" + age +"} " + super.toString();}}static class User1 extends Person {public User1(String name, int age, String sex, int id) {super(sex, id);this.name = name;this.age = age;}public String name;public int age;private void writeObject(ObjectOutputStream out) throws IOException {throw new NotSerializableException("Can not serialize this class");}private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {throw new NotSerializableException("Can not serialize this class");}private void readObjectNoData() throws ObjectStreamException {throw new NotSerializableException("Can not serialize this class");}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", age=" + age +"} " + super.toString();}}public static String basePath = System.getProperty("user.dir") + "\\";public static String tmp = "D:\\henryTest\\";public static void main(String[] args) {ChildNotSerializeTest();
// ChildNotSerializeTest1();}private static void ChildNotSerializeTest() {User user = new User("zero", 18, "男", 1);System.out.println("1: " + user);ByteArrayOutputStream out = new ByteArrayOutputStream();ObjectOutputStream oos = null;byte[] userData = null;try {oos = new ObjectOutputStream(out);oos.writeObject(user);userData = out.toByteArray();} catch (IOException e) {e.printStackTrace();}ObjectInputStream ois = null;try {ois = new ObjectInputStream(new ByteArrayInputStream(userData));user = (User) ois.readObject();System.out.println("反序列化后 2: " + user);} catch (Exception e) {e.printStackTrace();}}private static void ChildNotSerializeTest1() {User1 user = new User1("zero", 18, "男", 1);System.out.println("1: " + user);ByteArrayOutputStream out = new ByteArrayOutputStream();ObjectOutputStream oos = null;byte[] userData = null;try {oos = new ObjectOutputStream(out);oos.writeObject(user);userData = out.toByteArray();} catch (IOException e) {e.printStackTrace();}ObjectInputStream ois = null;try {ois = new ObjectInputStream(new ByteArrayInputStream(userData));user = (User1) ois.readObject();System.out.println("反序列化后 2: " + user);} catch (Exception e) {e.printStackTrace();}}}
输出:子类属性默认实现序列化
如何控制子类是否实现序列化?
重写writeObject
readObject
方法
......public static void main(String[] args) {
// ChildNotSerializeTest();ChildNotSerializeTest1();}
......
输出:
2.2.8 序列化枚举类
Java 的序列化机制对枚举类型进行了特殊处理,主要是为了保证枚举类的序列化和反序列化的安全性和一致性:
- 枚举常量的序列化:在序列化枚举类时,Java 只会序列化枚举常量的名称,而不是整个枚举对象。这是因为枚举常量在 JVM 中是单例的,可以通过枚举常量的名称来获取对应的枚举对象,所以只序列化名称即可。
- 枚举类的特殊处理:在反序列化枚举类时,Java 会检查反序列化的对象是否是枚举类,并且会根据反序列化的名称来获取对应的枚举常量。这样可以确保反序列化后的对象仍然是枚举类的常量之一。
- 枚举类的安全性:由于枚举常量是在编译时确定的,枚举类的序列化和反序列化过程中不会受到外部影响,从而确保了枚举类的安全性和一致性。
- 枚举类的性能优化:由于枚举常量是单例的,序列化和反序列化时只需要处理枚举常量的名称,而不需要保存和还原整个对象,这样可以提高序列化和反序列化的性能。
package com.MyStudy.SerializationTest;
enum Num1{ONE,TWO,THREE;public void printValues(){System.out.println("ONE: "+ ONE.ordinal() + ", TWO: " + TWO.ordinal() + ", THREE: " + THREE.ordinal());}
}/*** Java的序列化机制针对枚举类型是特殊处理的。简单来讲,在序列化枚举类型时,只会存储枚举类的引用和枚举常量的名称。随后的反序列化的过程中,* 这些信息被用来在运行时环境中查找存在的枚举类型对象。*/
public class EnumSerializableTest {
//public static void main(String[] args) throws Exception {byte[] bs =SerializeableUtils.serialize(Num1.THREE);Num1.THREE.printValues();System.out.println("hashCode: " + Num1.THREE.hashCode());System.out.println("反序列化后");Num1 s1 = SerializeableUtils.deserialize(bs);s1.printValues();System.out.println("hashCode: " + s1.hashCode());System.out.println("== " + (Num1.THREE == s1));}}
输出:
2.2.9 序列化单例
在 Java 中,单例模式是一种常见的设计模式,用于确保一个类只有一个实例,并提供一个全局访问点。在某些情况下,需要将单例对象序列化和反序列化,以便在不同的 JVM 实例间传输或保存状态。在这种情况下,需要特殊处理单例对象的序列化,以确保单例模式的特性不受影响。
1.序列化单例对象:在序列化单例对象时,需要注意以下几点:
- 序列化过程中,只序列化单例对象的状态数据,而不序列化单例对象的类信息或构造方法。
- 序列化过程中,需要实现 writeReplace() 方法,返回一个代理对象,以确保反序列化后仍然是同一个单例对象。
- 序列化过程中,可以通过实现 readResolve() 方法,在反序列化时返回原始单例对象,以确保反序列化后仍然是同一个单例对象。
2.保护单例模式的特性:在序列化和反序列化过程中,需要确保单例对象的特性不受影响,例如确保只有一个实例存在、全局访问点等。通过特殊处理序列化和反序列化过程,可以保护单例模式的特性。
3.避免多次实例化:在反序列化时,需要确保只有一个实例被还原,并且不会创建额外的实例。通过特殊处理反序列化过程,可以避免多次实例化单例对象。
4.实现序列化接口:单例类需要实现 Serializable 接口,以便支持序列化和反序列化操作。
private void writeObject0(Object obj, boolean unshared)throws IOException{
......Class repCl;desc = ObjectStreamClass.lookup(cl, true);if (desc.hasWriteReplaceMethod() &&//是否有WriteReplace方法(obj = desc.invokeWriteReplace(obj)) != null &&//通过反射调用(repCl = obj.getClass()) != cl){
......
private Object readOrdinaryObject(boolean unshared)throws IOException......if (obj != null &&handles.lookupException(passHandle) == null &&desc.hasReadResolveMethod())//是否有readresolve方法{Object rep = desc.invokeReadResolve(obj);//通过反射调用readresolve方法if (unshared && rep.getClass().isArray()) {rep = cloneArray(rep);}......}
示例:
class Singleton implements Serializable{public static Singleton INSTANCE = new Singleton();private Singleton(){}private Object readResolve(){return INSTANCE;}
}
2.3 序列化流程
上述的writeObject
readObject
方法是如何调用的?这就涉及到了序列化的流程
简单阐述writeObject
具体调用的关键点,readObject
同理。
private void writeSerialData(Object obj, ObjectStreamClass desc)throws IOException{ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();//获取ObjectStreamClass对象for (int i = 0; i < slots.length; i++) {ObjectStreamClass slotDesc = slots[i].desc;if (slotDesc.hasWriteObjectMethod()) {//类是否有WriteObjectMethod方法?PutFieldImpl oldPut = curPut;curPut = null;SerialCallbackContext oldContext = curContext;if (extendedDebugInfo) {debugInfoStack.push("custom writeObject data (class \"" +slotDesc.getName() + "\")");}try {curContext = new SerialCallbackContext(obj, slotDesc);bout.setBlockDataMode(true);slotDesc.invokeWriteObject(obj, this);//貌似通过反射调用了WriteObject方法bout.setBlockDataMode(false);bout.writeByte(TC_ENDBLOCKDATA);} finally {curContext.setUsed();curContext = oldContext;if (extendedDebugInfo) {debugInfoStack.pop();}}curPut = oldPut;} else {defaultWriteFields(obj, slotDesc);}}}
2.4 安卓Parcelable接口
在安卓开发中,Parcelable 是一种用于在不同组件之间传递复杂对象的接口。通过实现 Parcelable 接口,可以将对象序列化为字节流,以便在不同组件之间进行高效传输,如在 Activity 之间传递数据或在 Intent 中传递对象等。
- 高效传输对象:相比实现 Serializable 接口,实现 Parcelable 接口可以实现更高效的对象传输。Parcelable 的实现方式更为轻量,不会像 Serializable 那样产生大量的临时对象,因此在传输大量数据时更加高效。
- 自定义序列化:通过实现 Parcelable 接口,可以完全控制对象的序列化和反序列化过程。开发者可以精确地定义对象的序列化方式,提高了灵活性和性能。
- 性能优化:在安卓开发中,频繁地在组件之间传递大量数据会影响性能。使用 Parcelable 可以减少序列化和反序列化的开销,提升应用的性能。
- 支持内置数据类型:Parcelable 接口支持传递内置数据类型(如整型、字符串等)以及自定义对象,使得在安卓应用中传递复杂对象变得更加方便。
- 适用范围:Parcelable 接口通常用于在安卓应用内部传递对象,如在 Activity 之间传递数据、在 Fragment 之间传递参数等。对于需要跨进程传输的情况,也可以使用 Parcelable 接口。
2.4.1 重要概念和用法
2.4.1.1 实现 Parcelable 接口
要使一个类可序列化,需要让该类实现 Parcelable 接口,并实现以下方法:
describeContents():返回对象的特殊标识符,一般返回0即可。
writeToParcel(Parcel dest, int flags):将对象的数据写入 Parcel 对象,以便在不同组件之间传输。
Creator 接口:用于反序列化对象,包含 createFromParcel(Parcel source) 和 newArray(int size) 方法。
2.4.1.2 内置数据类型支持
Parcelable 支持传递内置数据类型(如整型、字符串等)和自定义对象。内置数据类型可以直接通过 Parcel 的写入和读取方法进行操作,而对于自定义对象,需要在 writeToParcel() 方法中逐个写入对象的字段,然后在 Creator 的 createFromParcel() 方法中逐个读取。
2.4.1.3 性能优化
实现 Parcelable 接口可以减少序列化和反序列化的开销,提高传输对象的效率。这对于在 Android 应用中频繁传输大量数据的场景尤为重要,可以提升应用的性能和用户体验。
2.4.1.4 使用场景
在不同 Activity 或 Fragment 之间传递数据。
在 Service 和 Activity 之间传递数据。
在 Intent 中传递自定义对象。
2.4.1.5 注意事项
应确保自定义对象中的每个字段都能正确序列化和反序列化。
序列化和反序列化的顺序应该保持一致。
避免在 Parcelable 中传递大量数据,以免影响性能。
2.4.2 Parcelable和Serializable的区别
Serializable | Parcelable |
---|---|
通过对IO硬盘操作,速度较慢 | 直接在内存操作,效率高,性能好 |
大小不受限制 | 一般不超过1M,修改内核也只能4M |
大量使用反射,产生内存碎片 |
2.4.3 Parcelable基础使用
import android.os.Parcel;
import android.os.Parcelable;public class User implements Parcelable {private String name;private int age;public User(String name, int age) {this.name = name;this.age = age;}protected User(Parcel in) {name = in.readString();age = in.readInt();}public static final Creator<User> CREATOR = new Creator<User>() {@Overridepublic User createFromParcel(Parcel in) {return new User(in);}@Overridepublic User[] newArray(int size) {return new User[size];}};@Overridepublic int describeContents() {return 0;}@Overridepublic void writeToParcel(Parcel dest, int flags) {dest.writeString(name);dest.writeInt(age);}public String getName() {return name;}public int getAge() {return age;}
}
// 创建一个User对象
User user = new User("Alice", 25);// 将User对象放入Intent中传递给另一个Activity
Intent intent = new Intent(this, SecondActivity.class);
intent.putExtra("user", user);
startActivity(intent);// 在另一个Activity中获取传递的User对象
User receivedUser = getIntent().getParcelableExtra("user");
String userName = receivedUser.getName();
int userAge = receivedUser.getAge();