1 序列化
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 Java Object)或者 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.3 常见的序列化和反序列化协议
1.3.1 XML&SOAP
XML 是一种常用的序列化和反序列化协议,具有跨机器,跨语言等优点,SOAP(Simple Object Access protocol) 是一种被广泛应用的,基于 XML 为序列化和反序列化协议的结构化消息传递协议
1.3.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.3.3 Protobuf
Protobuf 具备了优秀的序列化协议的所需的众多典型特征。
- 标准的 IDL 和 IDL 编译器,这使得其对工程师非常友好。
- 序列化数据非常简洁,紧凑,与 XML 相比,其序列化之后的数据量约为 1/3 到 1/10。
- 解析速度非常快,比对应的 XML 快约 20-100 倍。
- 提供了非常友好的动态库,使用非常简介,反序列化只需要一行代码。
2 XML
2.1 定义
XML,即 extensible Markup Language ,是一种数据标记语言 & 传输格式
2.2 作用
对数据进行标记(结构化数据)、存储 & 传输
2.3 特性
-
灵活性: 可自定义标签,文档结构
-
自我描叙性
- XML文档即 一个纯文本文件,代码结构清晰,适合人类阅读
- 有文本处理能力的软件都可以处理XML
-
可扩展性: 可在不中断解析,应用程序的情况下进行扩展
-
可跨平台数据传输: 可以不兼容的系统间交换数据,降低了复杂性
-
数据共享: XML 以纯文本进行存储,独立于软硬件和应用程序的数据存储方式,使得不同的系统都能访问XML
2.4 语法
-
元素要关闭标签
-
对大小写敏感
-
必须要有根元素(父元素)
-
属性值必须加引号
-
XML元素命名规则
- 不能以数字或标点符号开头
- 不能包含空格
- 不能以xml开头
-
CDATA 不被解析器解析的文本数据,所有xml文档都会被解析器解析(cdata区段除外)
-
PCDATA 被解析的字符数据
2.5 XML树形结构
XML文档中的元素会形成一种树结构,从根部开始,然后拓展到每个树叶(节点),下面将以实例说明XML的树结构。
<?xml version="1.0" encoding="utf-8"?>
<classes><!--根节点 -->
<student id="0">
<name>Av</name>
<age>23</age>
<sax>男</sax>
<Courses>
<course name="语文" score="90"/>
<course name="数学" score="78"/>
</Courses>
</student>
<student id="1">
<name>Lance</name>
<age>22</age>
<sax>男</sax>
<Courses>
<course name="语文" score="59"/>
<course name="数学" score="38"/>
</Courses>
</student>
</classes>
树形结构
XML节点解释
XML文件是由节点构成的。它的第一个节点为“根节点”。一个XML文件必须有且只能有一个根节点,其他节点都必须是它的子节点,每个子节点又可以有自己的子节点。
2.6 解析方式
解析XML,即从XML中提取有用的信息 XML的解析方式主要分为2大类:
解析方式 | 原理 | 类型 |
---|---|---|
基于文档驱动 | 在解析XML文档前,需先将整个XML文档加载到内存中 | DOM方式 |
基于事件驱动 | 根据不同需求事件(检索,修改,删除等)去执行不同解析操作(不需要把整个XML 文档加载到内存中) | SAX方式,PULL方式 |
2.6.1 DOM方式
Document Object Model,即 文件对象模型,是 一种 基于树形结构节点 & 文档驱动 的XML解析方法,它定义了访问 & 操作xml文档元素的方法和接口
2.6.1.1 DOM解析原理
-
核心思想 基于文档驱动,在解析XML文档前,先将整个XML文档存储到内存中,然后再解析
-
解析过程
- 解析器读入整个XML文档到内存中
- 解析全部文件,并将文件分为独立的元素,属性等,以树结构的形式在内存中表示XML文件
- 然后通过DOM API去遍历XML树,根据需要搜索数据/修改文档
-
具体解析步骤
- 获取DOM解析器工厂实例(DocumentBuilderFactory.newInstance())
- 获取DOM解析器对象,调用解析器工厂实例类的newDocumentBuilder()
- 最后获取代表整个文档的Document对象
2.6.1.2 具体解析实例
public void domTest(Context context) {
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document document = db.parse(context.getResources().openRawResource(R.raw.students));
//通过Document对象的getElementsByTagName()返根节点的一个list集合
NodeList studentList = document.getElementsByTagName("student");
for (int i = 0; i < studentList.getLength(); i++) {
Student student = new Student();
//循环遍历获取每一个student
Node studentNode = studentList.item(i);
if (((Element) studentNode).hasAttribute("id")) {
student.setId(Integer.parseInt(((Element) studentNode).getAttribute("id")));
}
//解析student节点的子节点
NodeList childList = studentNode.getChildNodes();
for (int t = 0; t < childList.getLength(); t++) {
//区分出text类型的node以及element类型的node
if (childList.item(t).getNodeType() == Node.ELEMENT_NODE) {
if (childList.item(t).getNodeName().equalsIgnoreCase("Courses")) {
NodeList courses = childList.item(t).getChildNodes();
for (int j = 0; j < courses.getLength(); j++) {
Node courseNode = courses.item(j);
if (courseNode.getNodeType() != Node.ELEMENT_NODE) {
continue;
}
NamedNodeMap namedNodeMap = courseNode.getAttributes();
Course course = new Course();
student.addCourse(course);
for (int k = 0; k < namedNodeMap.getLength(); k++) {
Node courseAttr = namedNodeMap.item(k);
if (courseAttr.getNodeName().equals("name")) {
course.setName(courseAttr.getNodeValue());
} else if (courseAttr.getNodeName().equals("score")) {
course.setScore(Float.parseFloat(courseAttr.getNodeValue()));
}
}
}
} else {
Node child = childList.item(t);
if (child.getNodeName().equals("name")) {
student.setName(child.getTextContent());
} else if (child.getNodeName().equals("age")) {
student.setAge(Integer.parseInt(child.getTextContent()));
} else if (child.getNodeName().equals("sax")) {
student.setSax(child.getTextContent());
}
}
}
}
Log.i("Zero", "解析完毕: " + student);
}
} catch (ParserConfigurationException | SAXException | IOException e) {
Log.e("Zero", e.getMessage());
}
}
2.6.1.3 特点及应用场景
-
优点
- 操作整个XML文档的效率高
- 可随时,多次访问已解析的文档
-
缺点
- 耗内存,时间 应用场景
- 适合XML文档较小,需频繁操作 解析文档,多次访问文档的情况
- 对于移动端,内存资源非常宝贵,使用时需权衡利弊
2.6.2 SAX方式
即 Simple API for XML,一种 基于事件流驱动、通过接口方法解析 的XML解析方法
2.6.2.1 SAX解析原理
-
核心思想 基于事件流驱动,根据不同需求事件(检索,修改,删除等)去执行不同解析操作,不需要把整个XML 文档加载到内存中
-
解析过程
- 按顺序扫描XML文档
- 当扫描到(Document)文档的开始/结束标签,(Element)节点元素的开始/结束标签时,直接调用对应的方法,将状态信息以参数的方式传递到方法中
- 然后根据状态信息去执行相关的自定义操作 具体的操作
- 自定义Handler处理类,继承自DefaultHandler类
- 重写5个核心回调方法
startDocument()
startElement()
characters()
endElement()
endDocument()
2.6.2.2 具体解析实例
public void saxTest(Context context) throws Exception {
SAXParserFactory spf = SAXParserFactory.newInstance();
SAXParser sp = spf.newSAXParser();
sp.parse(context.getResources().openRawResource(R.raw.students), new DefaultHandler() {
String currentTag = null;
Student student = null;
/**
* 文档解析开始时被调用
* @throws SAXException
*/
@Override
public void startDocument() throws SAXException {
super.startDocument();
}
/**
* 文档解析结束时被调用
* @throws SAXException
*/
@Override
public void endDocument() throws SAXException {
super.endDocument();
}
/**
*
* @param uri 命名空间
* @param localName 不带命名空间前缀的标签名
* @param qName 带命名空间的标签名
* @param attributes 标签的属性集合 <student id="0"></student>
* @throws SAXException
*/
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
super.startElement(uri, localName, qName, attributes);
currentTag = localName;
if ("student".equals(currentTag)) {
student = new Student();
student.setId(Integer.parseInt(attributes.getValue("id")));
}
if ("course".equals(currentTag)) {
if (student != null) {
Course course = new Course();
course.setName(attributes.getValue("name"));
course.setScore(Float.parseFloat(attributes.getValue("score")));
student.addCourse(course);
}
}
}
/**
* 解析到结束标签时被调用 '/>'
* @param uri
* @param localName
* @param qName
* @throws SAXException
*/
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
super.endElement(uri, localName, qName);
if ("student".equals(localName)) {
Log.i(TAG, "endElement: student: " + student);
}
}
/**
*
* @param ch 内容
* @param start 起始位置
* @param length 长度
* @throws SAXException
*/
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
super.characters(ch, start, length);
String str = new String(ch, start, length).trim();
if (TextUtils.isEmpty(str))
return;
if ("name".equals(currentTag) && student != null) {
student.setName(str);
}
if ("age".equals(currentTag)&& student != null) {
student.setAge(Integer.parseInt(str));
}
if ("sax".equals(currentTag) && student != null) {
student.setSax(str);
}
}
});
}
2.6.2.3 特点及应用场景
-
优点
- 解析效率高
- 内存占用少
-
缺点
- 解析方法复杂,API接口方法复杂,代码量大
- 可扩展性差,无法修改XML树内容结构 应用场景
- 适合XML文档大,解析性能要求高,不需修改 多次访问解析的情况
2.6.3 PULL方式
一种 基于事件流驱动 的XML解析方法,是Android系统特有的解析方式
2.6.3.1 PULL解析原理
基于事件流驱动,根据不同需求事件(检索,修改,删除等)去执行不同解析操作,不需要把整个XML 文档加载到内存中
-
解析过程
- 首先按顺序扫描XML文档
- 解析器提供文档的开始/结束(START_DOCUMENT,END_DOCUMENT),元素的开始/结束(START_TAG,END_TAG)
- 当某个元素开始时,通过调用parser.nextText()从XML文档中提取所有字符数据
2.6.3.2 具体解析实例
public void pullTest(Context context) throws Exception {
XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser();
parser.setInput(context.getResources().openRawResource(R.raw.students), "utf-8");
//设置数据源编码
int eventCode = parser.getEventType();//获取事件类型
Student student = null;
while (eventCode != XmlPullParser.END_DOCUMENT) {
switch (eventCode) {
case XmlPullParser.START_DOCUMENT://开始读取XML文档
break;
case XmlPullParser.START_TAG://开始读取标签
String name = parser.getName();
if ("student".equals(name)) {
student = new Student();
student.setId(Integer.parseInt(parser.getAttributeValue(null, "id")));
}
if ("name".equals(name) && student != null) {
student.setName(parser.nextText());
}
if ("age".equals(name) && student != null) {
student.setAge(Integer.parseInt(parser.nextText().trim()));
}
if ("sax".equals(name) && student != null) {
student.setSax(parser.nextText());
}
if ("course".equals(name) && student != null) {
Course course = new Course();
course.setName(parser.getAttributeValue(null, "name"));
course.setScore(Float.parseFloat(parser.getAttributeValue(null, "score")));
student.addCourse(course);
}
break;
case XmlPullParser.END_TAG://结束原始事件
if ("student".equals(parser.getName())) {
Log.i(TAG, "pullTest: student: " + student);
}
break;
}
eventCode = parser.next();
}
}
2.6.3.3 特点及应用场景
-
优点
- 解析效率高
- 内存占用少
- 灵活性高 可控制事件处理结束的时机(与SAX最大的区别)
- 使用比SAX方式简单
-
缺点
- 可扩展性差,无法修改XML树内容结构 应用场景
- 适合XML文档大,解析性能要求高,不需修改 多次访问解析的情况
- Pull使用比SAX更加简单,在Android中推荐使用Pull方式
3 Json
4 Gson 原理
- Gson整体流程
- Gson 反射解析机制
5 Android研发该如何选择序列化方案
5.1 Serializable接口
是 Java 提供的序列化接口,它是一个空接口:
public interface Serializable {
}
Serializable 用来标识当前类可以被 ObjectOutputStream 序列化,以及被 ObjectInputStream 反序列化。
5.1.1 Serializable入门
public class Student implements Serializable {
//serialVersionUID唯一标识了一个可序列化的类
private static final long serialVersionUID = -2100492893943893602L;
private String name;
private String sax;
private Integer age;
//Course也需要实现Serializable接口
private List<Course> courses;
//用transient关键字标记的成员变量不参与序列化(在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null)
private transient Date createTime;
//静态成员变量属于类不属于对象,所以不会参与序列化(对象序列化保存的是对象的“状态”,也就是它的成员变量,因此序列化不会关注静态变量)
private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat();
public Student() {
System.out.println("Student: empty");
}
public Student(String name, String sax, Integer age) {
System.out.println("Student: " + name + " " + sax + " " + age);
this.name = name;
this.sax = sax;
this.age = age;
courses = new ArrayList<>();
createTime = new Date();
}
...
}
////Course也需要实现Serializable接口
public class Course implements Serializable {
private static final long serialVersionUID = 667279791530738499L;
private String name;
private float score;
...
}
Serializable 有以下几个特点:
- 可序列化类中,未实现 Serializable 的属性状态无法被序列化/反序列化
- 也就是说,反序列化一个类的过程中,它的非可序列化的属性将会调用无参构造函数重新创建
- 因此这个属性的无参构造函数必须可以访问,否者运行时会报错
- 一个实现序列化的类,它的子类也是可序列化的
5.1.2 serialVersionUID与兼容性
- serialVersionUID的作用 serialVersionUID 用来表明类的不同版本间的兼容性。如果你修改了此类, 要修改此值。否则以前用老版本的类序列化的类恢复时会报错: InvalidClassException
- 设置方式 在JDK中,可以利用JDK的bin目录下的serialver.exe工具产生这个serialVersionUID,对于Test.class,执行命令:serialver Test
- 兼容性问题 为了在反序列化时,确保类版本的兼容性,最好在每个要序列化的类中加入 private static final long serialVersionUID这个属性,具体数值自己定义。这样,即使某个类在与之对应的对象 已经序列化出去后做了修改,该对象依然可以被正确反序列化。否则,如果不显式定义该属性,这个属性值将由JVM根据类的相关信息计算,而修改后的类的计算 结果与修改前的类的计算结果往往不同,从而造成对象的反序列化因为类版本不兼容而失败。 不显式定义这个属性值的另一个坏处是,不利于程序在不同的JVM之间的移植。因为不同的编译器实现该属性值的计算策略可能不同,从而造成虽然类没有改变,但是因为JVM不同,出现因类版本不兼容而无法正确反序列化的现象出现
因此 JVM 规范强烈 建议我们手动声明一个版本号,这个数字可以是随机的,只要固定不变就可以。同时最好是 private 和 final 的,尽量保证不变。
5.1.3 Externalizable接口
public interface Externalizable extends Serializable {
void writeExternal(ObjectOutput var1) throws IOException;
void readExternal(ObjectInput var1) throws IOException, ClassNotFoundException;
}
简单使用
public class Course1 implements Externalizable {
private static final long serialVersionUID = 667279791530738499L;
private String name;
private float score;
...
@Override
public void writeExternal(ObjectOutput objectOutput) throws IOException {
System.out.println("writeExternal");
objectOutput.writeObject(name);
objectOutput.writeFloat(score);
}
@Override
public void readExternal(ObjectInput objectInput) throws IOException, ClassNotFoundException {
System.out.println("readExternal");
name = (String)objectInput.readObject();
score = objectInput.readFloat();
}
...
public static void main(String... args) throws Exception {
//TODO:
//TODO:
Course1 course = new Course1("英语", 12f);
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(out);
oos.writeObject(course);
course.setScore(78f);
);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bs));
Course1 course1 = (Course1) ois.readObject();
System.out.println("course1: " + course1);
}
5.1.4 序列化与反序列化 Serializable
Serializable 的序列化与反序列化分别通过 ObjectOutputStream 和 ObjectInputStream 进行
/**
* 序列化对象
*
* @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;
}
5.1.4 序列化步骤与数据结构分析
序列化算法一般会按步骤做如下事情:
- 将对象实例相关的类元数据输出。
- 递归地输出类的超类描述直到不再有超类。
- 类元数据完了以后,开始从最顶层的超类开始输出对象实例的实际数据值。
- 从上至下递归输出实例的数据
格式化后以二进制打开
aced 0005 7372 002e 636f 6d2e 7a65 726f
2e73 6572 6961 6c69 7a61 626c 6564 656d
6f2e 7365 7269 616c 697a 6162 6c65 2e53
7475 6465 6e74 e2d9 8cd7 833d f19e 0200
044c 0003 6167 6574 0013 4c6a 6176 612f
6c61 6e67 2f49 6e74 6567 6572 3b4c 0007
636f 7572 7365 7374 0010 4c6a 6176 612f
7574 696c 2f4c 6973 743b 4c00 046e 616d
6574 0012 4c6a 6176 612f 6c61 6e67 2f53
7472 696e 673b 4c00 0373 6178 7100 7e00
0378 7073 7200 116a 6176 612e 6c61 6e67
2e49 6e74 6567 6572 12e2 a0a4 f781 8738
0200 0149 0005 7661 6c75 6578 7200 106a
6176 612e 6c61 6e67 2e4e 756d 6265 7286
ac95 1d0b 94e0 8b02 0000 7870 0000 0012
7372 0013 6a61 7661 2e75 7469 6c2e 4172
7261 794c 6973 7478 81d2 1d99 c761 9d03
0001 4900 0473 697a 6578 7000 0000 0277
0400 0000 0273 7200 2d63 6f6d 2e7a 6572
6f2e 7365 7269 616c 697a 6162 6c65 6465
6d6f 2e73 6572 6961 6c69 7a61 626c 652e
436f 7572 7365 0942 a76f 5bfc 8343 0200
0246 0005 7363 6f72 654c 0004 6e61 6d65
7100 7e00 0378 7042 b466 6674 0006 e8af
ade6 9687 7371 007e 000a 42b2 999a 7400
06e6 95b0 e5ad a678 7400 045a 6572 6f74
0003 e794 b7
- AC ED: STREAM_MAGIC. 声明使用了序列化协议.
- 00 05: STREAM_VERSION. 序列化协议版本.
- 0x73: TC_OBJECT. 声明这是一个新的对象.
- 0x72: TC_CLASSDESC. 声明这里开始一个新Class。
- 00 2e: Class名字的长度.
5.1.5 readObject/writeObject原理分析
以oos.writeObject(obj)
为例分析
- ObjectOutputStream的构造函数设置enableOverride = false
public ObjectOutputStream(OutputStream out) throws IOException {
verifySubclass();
bout = new BlockDataOutputStream(out);
handles = new HandleTable(10, (float) 3.00);
subs = new ReplaceTable(10, (float) 3.00);
enableOverride = false;//enableOverride = false
...
}
2. 所以writeObject方法执行的是writeObject0(obj, false);
public final void writeObject(Object obj) throws IOException {
//enableOverride=false,不走这里
if (enableOverride) {
writeObjectOverride(obj);
return;
}
try {//一般情况都走这里
writeObject0(obj, false);
...
}
3. 在writeObject0方法中,代码非常多,看重点
/**
* Underlying writeObject/writeUnshared implementation.
*/
private void writeObject0(Object obj, boolean unshared)
throws IOException
...
// remaining cases
if (obj instanceof String) {
writeString((String) obj, unshared);
} else if (cl.isArray()) {
writeArray(obj, desc, unshared);
} else if (obj instanceof Enum) {
writeEnum((Enum<?>) obj, desc, unshared);
} else if (obj instanceof Serializable) {
//看这里
writeOrdinaryObject(obj, desc, unshared);
} else {//如果没有实现Serializable接口,会报NotSerializableException
if (extendedDebugInfo) {
throw new NotSerializableException(
cl.getName() + "\n" + debugInfoStack.toString());
} else {
throw new NotSerializableException(cl.getName());
...
}
4. 在writeOrdinaryObject(obj, desc, unshared)方法中
private void writeOrdinaryObject(Object obj,
ObjectStreamClass desc,
boolean unshared)
...
if (desc.isExternalizable() && !desc.isProxy()) {
//如果对象实现了Externalizable接口,那么执行writeExternalData((Externalizable) obj)方法
writeExternalData((Externalizable) obj);
} else {//如果对象实现的是Serializable接口,那么执行的是writeSerialData(obj, desc)
writeSerialData(obj, desc);
}
...
}
//这里我们看看writeExternalData
5. writeSerialData方法,主要执行方法:defaultWriteFields(obj, slotDesc)
/**
* Writes instance data for each serializable class of given object, from
* superclass to subclass.
* 最终写序列化的方法
*/
private void writeSerialData(Object obj, ObjectStreamClass desc)
throws IOException
{
...
if (slotDesc.hasWriteObjectMethod()) {
//如果writeObjectMethod != null(目标类中定义了私有的writeObject方法),那么将调用目标类中的writeObject方法
...
slotDesc.invokeWriteObject(obj, this);
...
} else {
//如果如果writeObjectMethod == null, 那么将调用默认的defaultWriteFields方法来读取目标类中的属性
defaultWriteFields(obj, slotDesc);
}
}
}
6. 在ObjectStreamClass中,ObjectOutputStream(ObjectInputStream)会寻找目标类中的私有的writeObject(readObject)方法,赋值给变量writeObjectMethod(readObjectMethod)
/**
* Creates local class descriptor representing given class.
*/
private ObjectStreamClass(final Class<?> cl) {
...
if (externalizable) {
cons = getExternalizableConstructor(cl);
} else {//,在序列化(反序列化)的时候,ObjectOutputStream(ObjectInputStream)
// 会寻找目标类中的私有的writeObject(readObject)方法,
// 赋值给变量writeObjectMethod(readObjectMethod)
cons = getSerializableConstructor(cl);
writeObjectMethod = getPrivateMethod(cl, "writeObject",
new Class<?>[] { ObjectOutputStream.class },
Void.TYPE);
readObjectMethod = getPrivateMethod(cl, "readObject",
new Class<?>[] { ObjectInputStream.class },
Void.TYPE);
readObjectNoDataMethod = getPrivateMethod(
cl, "readObjectNoData", null, Void.TYPE);
hasWriteObjectData = (writeObjectMethod != null);
}
domains = getProtectionDomains(cons, cl);
writeReplaceMethod = getInheritableMethod(
cl, "writeReplace", null, Object.class);
readResolveMethod = getInheritableMethod(
cl, "readResolve", null, Object.class);
return null;
}
});
...
}
//ObjectStreamClass类中的一个判断方法
boolean hasWriteObjectMethod() {
requireInitialized();
return (writeObjectMethod != null);
}
5.1.6 Serializable需要注意的坑
- 多引用写入
public class Course implements Serializable {
private static final long serialVersionUID = 667279791530738499L;
private String name;
private float score;
...
public static void main(String... args) throws Exception {
//TODO:
//TODO:
Course course = new Course("英语", 12f);
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(out);
oos.writeObject(course);
course.setScore(78f);
// oos.reset();
oos.writeUnshared(course);
// oos.writeObject(course);
byte[] bs = out.toByteArray();
oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bs));
Course course1 = (Course) ois.readObject();
Course course2 = (Course) ois.readObject();
System.out.println("course1: " + course1);
System.out.println("course2: " + course2);
}
}</pre>
执行结果:
<pre spellcheck="false" class="md-fences mock-cm md-end-block" lang="text" cid="n203" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: pre-wrap; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;"> course1: Course{name='英语', score=12.0}
course2: Course{name='英语', score=12.0}
在默认情况下, 对于一个实例的多个引用,为了节省空间,只会写入一次,后面会追加几个字节代表某个实例的引用。
- 子类实现序列化,父类不实现序列化/ 对象引用
public class Person {
private String name;
private String sax;
// public Person() {
// }
public Person(String name, String sax) {
this.name = name;
this.sax = sax;
}
}
public class Student1 extends Person implements Serializable {
private static final long serialVersionUID = -2100492893943893602L;
private Integer age;
private List<Course> courses;
public Student1(String name, String sax, Integer age) {
super(name,sax);
...
}
...
public static void main(String ... args) throws Exception{
//TODO:
Student1 student = new Student1("Zero", "男", 18);
student.addCourse(new Course("语文", 90.2f));
//序列化
byte[] bytes = SerializeableUtils.serialize(student);
System.out.println(Arrays.toString(bytes));
//反序列化
//在readObject时抛出java.io.NotSerializableException异常。
//需要Person添加一个无参数构造器
Student1 student1 = SerializeableUtils.deserialize(bytes);
System.out.println("Student: " + student1);
}
在readObject时抛出java.io.NotSerializableException异常。
- 类的演化
//反序列化目标类多一个字段(height)
public class Student implements Serializable {
private static final long serialVersionUID = -2100492893943893602L;
private String name;
private String sax;
private Integer age;
private List<Course> courses;
...
@Override
public String toString() {
return "Student{" +
"name='" + name + ''' +
", sax='" + sax + ''' +
", age=" + age +
// ", height=" + height +
", courses=" + courses +
'}';
}
//private float height;
public static void main(String ... args)throws Exception{
//TODO:
String path = System.getProperty("user.dir") +"/a.out";
// Student student = new Student("Zero", "男", 18);
// student.addCourse(new Course("语文", 90.2f));
// //序列化
// SerializeableUtils.saveObject(student,path);
//反序列化
Student student1 = SerializeableUtils.readObject(path);
System.out.println("Student: " + student1);
}
}
执行结果:
//序列化的时候
Student: Zero 男 18
Course: 语文 90.2
//反序列化的时候 添加一个float height
Student: Student{name='Zero', sax='男', age=18, height=0.0, courses=[Course{name='语文', score=90.2}]}
可以看出反序列化之后,并没有报错,只是height实赋成了默认值。类似的其它对象也会赋值为默认值。 还有 相反,如果写入的多一个字段,读出的少一个字段,也是不会报错的 其它演化,比如更改类型等,这种演化本身就有问题,没必再探讨
- 枚举类型
public enum Num {
TWO, ONE, THREE;
public void printValues() {
System.out.println(ONE + " ONE.ordinal " + ONE.ordinal());
System.out.println(TWO + " TWO.ordinal " + TWO.ordinal());
System.out.println(THREE + " THREE.ordinal " + THREE.ordinal());
}
public static void testSerializable() throws Exception {
File file = new File("p.dat");
// ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
// oos.writeObject(Num.ONE);
// oos.close();
Num.ONE.printValues();
System.out.println("=========反序列化后=======");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
Num s1 = (Num) ois.readObject();
s1.printValues();
ois.close();
}
public static void main(String... args) throws Exception {
//TODO:
testSerializable();
}
}
执行结果:
ONE ONE.ordinal 1
TWO TWO.ordinal 0
THREE THREE.ordinal 2
=========反序列化后=======
//调换(ONE,TWO)的位置: TWO, ONE, THREE; ->ONE, TWO, THREE;
ONE ONE.ordinal 0
TWO TWO.ordinal 1
THREE THREE.ordinal 2
可以看到ONE的值变成了0. 事实上序列化Enum对象时,并不会保存元素的值,只会保存元素的name。这样,在不依赖元素值的前提下,ENUM对象如何更改都会保持兼容性。
5.1.7 重写readObject,writeObject
“只有当你自行设计的自定义序列化形式与默认的序列化形式基本相同时,才能接受默认的序列化> 形式”.“当一个对象的物理表示方法与它的逻辑数据内容有实质性差别时,使用默认序列化形式有> > N种缺陷”.其实从effective java的角度来讲,是强烈建议我们重写的,这样有助于我们更好地把控> > 序列化过程,防范未知风险
public class Course3 implements Serializable {
private static final long serialVersionUID = 667279791530738499L;
private String name;
private float score;
...
private void readObject(ObjectInputStream inputStream) throws ClassNotFoundException, IOException {
System.out.println("readObject");
inputStream.defaultReadObject();
name = (String)inputStream.readObject();
score = inputStream.readFloat();
}
private void writeObject(ObjectOutputStream outputStream) throws IOException {
System.out.println("writeObject");
outputStream.defaultWriteObject();
outputStream.writeObject(name);
outputStream.writeFloat(score);
}
private Object readResolve() {
System.out.println("readResolve");
return new Course3(name, 85f);
}
private Object writeReplace(){
System.out.println("writeReplace");
return new Course3(name +"replace",score);
}
...
public static void main(String... args) throws Exception {
//TODO:
Course3 course = new Course3("英语", 12f);
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(out);
oos.writeObject(course);
byte[] bs = out.toByteArray();
oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bs));
Course3 course1 = (Course3) ois.readObject();
System.out.println("course1: " + course1);
}
}
执行结果:
Course: 英语 12.0
writeReplace
Course: 英语replace 12.0
writeObject
readObject
readResolve
Course: 英语replace 85.0
course1: Course{name='英语replace', score=85.0}
- writeReplace 先于writeObject
- readResolve后于readObject
5.1.8 单例模式的序列化问题/反射问题
- 单例
1.饿汉
如果应用程序总是创建并使用单例实例或在创建和运行时开销不大
class Single {
private Single(){}
private static Single single= new Single();
public static Single getInstance(){
return single;
}
2.懒汉
如果开销比较大,希望用到时才创建就要考虑延迟实例化
Singleton的初始化需要某些外部资源(比如网络或存储设备)
class Single {
private Single(){}
private static Single single= null;
public static Single getInstance(){
if ( single == null ) {
synchronized (Single.class) {
if ( single == null ) {
single = new Single();
}
}
}
return single;
}
}
3.静态内部类
class Single {
private Single(){}
private static class SingleHandler{
private static Single single = new Single();
}
public static Single getInstance(){
return Single.SingleHandler.single;
}
4.枚举
public class Single {
private Single(){}
public enum SingleEnum {
singleHandler;
private Single single;
private SingleEnum () {
single = new Single();
}
public Single getSingle() {
return single;
}
}
public static Single getInstacne() {
return SingleEnum.singleHandler.getSingle();
}
}
- 序列化问题
public class SingleTest {
static final String CurPath = System.getProperty("user.dir");
public static void main(String ... args) throws Exception {
//TODO:
Single instance = Single.getInstance();
System.out.println(instance.hashCode());
System.out.println(copyInstance(instance).hashCode());
System.out.println("=================反射======================");
//使用反射方式直接调用私有构造器
Class<Single> clazz = (Class<Single>)Class.forName("com.zero.serializabledemo.serializable.Single");
Constructor<Single> con = clazz.getDeclaredConstructor(null);
con.setAccessible(true);//绕过权限管理,即在true的情况下,可以通过构造函数新建对象
Single instance1 = con.newInstance();
Single instance2 = con.newInstance();
System.out.println(instance1.hashCode());
System.out.println(instance2.hashCode());
}
private static Single copyInstance(Single instance) throws Exception{
//序列化会导致单例失效
FileOutputStream fos = new FileOutputStream(CurPath+"/a.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(instance);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(CurPath+"/a.txt"));
Single single2 = (Single)ois.readObject();
oos.close();
ois.close();
return single2;
}
}
class Single implements Serializable {
private static final long serialVersionUID = 1L;
private static boolean flag = false;
private Single(){
synchronized (Single.class) {
if (!flag) {
// flag = true;
} else {
throw new RuntimeException("单例模式被侵犯!");
}
}
}
private static Single single;
public static Single getInstance(){
if ( single == null ) {
synchronized (Single.class) {
if ( single == null ) {
single = new Single();
}
}
}
return single;
}
//如果不重写readResolve,会导致单例模式在序列化->反序列化后失败
// private Object readResolve() {
// return single;
// }
}
5.1.9 相关问答
- 什么是 serialVersionUID ?如果你不定义这个, 会发生什么?
serialVersionUID 是一个 private static final long 型 ID, 当它被印在对象上时, 它通常是对象的哈希码,你可以使用 serialver 这个 JDK 工具来查看序列化对象的 serialVersionUID。SerialVerionUID 用于对象的版本控制。也可以在类文件中指定 serialVersionUID。不指定serialVersionUID的后果是,当你添加或修改类中的任何字段时, 则已序列化类将无法恢复, 因为为新类和旧序列化对象生成的 serialVersionUID 将有所不同。Java 序列化过程依赖于正确的序列化对象恢复状态的, ,并在序列化对象序列版本不匹配的情况下引发
-
假设你有一个类,它序列化并存储在持久性中, 然后修改了该类以添加新字段,如果对已序列化的对象进行反序列化, 会发生什么情况?
-
序列化时,你希望某些成员不要序列化?你如何实现它?
有时候也会变着形式问,比如问什么是瞬态 trasient 变量, 瞬态和静态变量会不会得到序列化等,所以,如果你不希望任何字段是对象的状态的一部分, 然后声明它静态或瞬态根据你的需要, 这样就不会是在 Java 序列化过程中被包含在内
- 如果类中的一个成员未实现可序列化接口, 会发生什么情况?
如果尝试序列化实现可序列化的类的对象,但该对象包含对不可序列化类的引用,则在运行时将引发不可序列化异常 NotSerializableException
- 如果类是可序列化的, 但其超类不是, 则反序列化后从超级类继承的实例变量的状态如何?
Java 序列化过程仅在对象层次都是可序列化结构中继续, 即实现 Java 中的可序列化接口, 并且从超级类继承的实例变量的值将通过调用构造函数初始化, 在反序列化过程中不可序列化的超级类
-
是否可以自定义序列化过程, 或者是否可以覆盖 Java 中的默认序列化过程?
-
假设新类的超级类实现可序列化接口, 如何避免新类被序列化?
对于序列化一个对象需调用ObjectOutputStream.writeObject(saveThisObject), 并用ObjectInputStream.readObject() 读取对象, 但 Java 虚拟机为你提供的还有一件事, 是定义这两个方法。如果在类中定义这两种方法, 则 JVM 将调用这两种方法, 而不是应用默认序列化机制。你可以在此处通过执行任何类型的预处理或后处理任务来自定义对象序列化和反序列化的行为。
- 在 Java 中的序列化和反序列化过程中使用哪些方法?
考察你是否熟悉 readObject() 的用法、writeObject()、readExternal() 和 writeExternal()。Java 序列化由java.io.ObjectOutputStream类完成。该类是一个筛选器流, 它封装在较低级别的字节流中, 以处理序列化机制。要通过序列化机制存储任何对象, 我们调用ObjectOutputStream.writeObject(savethisobject), 并反序列化该对象, 我们称之为ObjectInputStream.readObject()方法。调用以writeObject() 方法在 java 中触发序列化过程。关于 readObject() 方法, 需要注意的一点很重要一点是, 它用于从持久性读取字节, 并从这些字节创建对象, 并返回一个对象, 该对象需要类型强制转换为正确的类型
5.2 Parcelable接口
Parcelable是Android为我们提供的序列化的接口,Parcelable相对于Serializable的使用相对复杂一些,但Parcelable的效率相对Serializable也高很多,这一直是Google工程师引以为傲的,有时间的可以看一下Parcelable和Serializable的效率对比 Parcelable vs Serializable 号称快10倍的效率
Parcelable是Android SDK提供的,它是基于内存的,由于内存读写速度高于硬盘,因此Android中的跨进程对象的传递一般使用Parcelable
Binder是基于共享内存(shared memory)和内核驱动的组合架构
5.2.1 Parcelable入门
public class Course implements Parcelable {
private String name;
private float score;
...
/**
* 描述当前 Parcelable 实例的对象类型
* 比如说,如果对象中有文件描述符,这个方法就会返回上面的 CONTENTS_FILE_DESCRIPTOR
* 其他情况会返回一个位掩码
* @return
*/
@Override
public int describeContents() {
return 0;
}
/**
* 将对象转换成一个 Parcel 对象
* @param dest 表示要写入的 Parcel 对象
* @param flags 示这个对象将如何写入
*/
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.name);
dest.writeFloat(this.score);
}
protected Course(Parcel in) {
this.name = in.readString();
this.score = in.readFloat();
}
/**
* 实现类必须有一个 Creator 属性,用于反序列化,将 Parcel 对象转换为 Parcelable
* @param <T>
*/
public static final Parcelable.Creator<Course> CREATOR = new Parcelable.Creator<Course>() {
//反序列化的方法,将Parcel还原成Java对象
@Override
public Course createFromParcel(Parcel source) {
return new Course(source);
}
//提供给外部类反序列化这个数组使用。
@Override
public Course[] newArray(int size) {
return new Course[size];
}
};
}
5.2.2 Parcel的简介
在介绍之前我们需要先了解Parcel是什么?Parcel翻译过来是打包的意思,其实就是包装了我们需要传输的数据,然后在Binder中传输,也就是用于跨进程传输数据 简单来说,Parcel提供了一套机制,可以将序列化之后的数据写入到一个共享内存中,其他进程通过Parcel可以从这块共享内存中读出字节流,并反序列化成对象,下图是这个过程的模型。
Parcel可以包含原始数据类型(用各种对应的方法写入,比如writeInt(),writeFloat()等),可以包含Parcelable对象,它还包含了一个活动的IBinder对象的引用,这个引用导致另一端接收到一个指向这个IBinder的代理IBinder。
Parcelable通过Parcel实现了read和write的方法,从而实现序列化和反序列化,
5.3 Parcelable与Serializable的性能比较
5.3.1 Serializable性能分析
Serializable是Java中的序列化接口,其使用起来简单但开销较大(因为Serializable在序列化过程中使用了反射机制,故而会产生大量的临时变量,从而导致频繁的GC),并且在读写数据过程中,它是通过IO流的形式将数据写入到硬盘或者传输到网络上。
5.3.2 Parcelable性能分析
Parcelable则是以IBinder作为信息载体,在内存上开销比较小,因此在内存之间进行数据传递时,推荐使用Parcelable,而Parcelable对数据进行持久化或者网络传输时操作复杂,一般这个时候推荐使用Serializable。
5.3.3 性能比较总结描述
首先Parcelable的性能要强于Serializable的原因我需要简单的阐述一下
- 在内存的使用中,前者在性能方面要强于后者
- 后者在序列化操作的时候会产生大量的临时变量,(原因是使用了反射机制)从而导致GC的频繁调用,因此在性能上会稍微逊色
- Parcelable是以Ibinder作为信息载体的.在内存上的开销比较小,因此在内存之间进行数据传递的时候,Android推荐使用Parcelable,既然是内存方面比价有优势,那么自然就要优先选择.
- 在读写数据的时候,Parcelable是在内存中直接进行读写,而Serializable是通过使用IO流的形式将数据读写入在硬盘上. 但是:虽然Parcelable的性能要强于Serializable,但是仍然有特殊的情况需要使用Serializable,而不去使用Parcelable,因为Parcelable无法将数据进行持久化,因此在将数据保存在磁盘的时候,仍然需要使用后者,因为前者无法很好的将数据进行持久化.(原因是在不同的Android版本当中,Parcelable可能会不同,因此数据的持久化方面仍然是使用Serializable)
5.3.4 性能测试方法分析
-
- 通过将一个对象放到一个bundle里面然后调用Bundle#writeToParcel(Parcel, int)方法来模拟传递对象给一个activity的过程,然后再把这个对象取出来。
- 在一个循环里面运行1000 次。
- 两种方法分别运行10次来减少内存整理,cpu被其他应用占用等情况的干扰。
- 参与测试的对象就是上面的相关代码
- 在多种Android软硬件环境上进行测试
5.3.5 两种如何选择
- 在使用内存方面,Parcelable比Serializable性能高,所以推荐使用Parcelable。
- Serializable在序列化的时候会产生大量的临时变量,从而引起频繁的GC。
- Parcelable不能使用在要将数据存储在磁盘上的情况,因为Parcelable不能很好的保证数据的持续性,在外界有变化的情况下,建议使用Serializable
5.4 SQLite 与 SharedPreferences
- SQLite主要用于存储复杂的关系型数据,Android支持原生支持SQLite数据库相关操作(SQLiteOpenHelper),不过由于原生API接口并不友好,所以产生了不少封装了SQLite的ORM框架。
- SharedPreferences是Android平台上提供的一个轻量级存储API,一般用于存储常用的配置信息,其本质是一个键值对存储,支持常用的数据类型如boolean、float、int、long以及String的存储和读取。
5.5 最后附带的几个面试相关的问题
-
Android里面为什么要设计出Bundle而不是直接用Map结构
Bundle内部是由ArrayMap实现的,ArrayMap的内部实现是两个数组,一个int数组是存储对象数据对应下标,一个对象数组保存key和value,内部使用二分法对key进行排序,所以在添加、删除、查找数据的时候,都会使用二分法查找,只适合于小数据量操作,如果在数据量比较大的情况下,那么它的性能将退化。而HashMap内部则是数组+链表结构,所以在数据量较少的时候,HashMap的Entry Array比ArrayMap占用更多的内存。因为使用Bundle的场景大多数为小数据量,我没见过在两个Activity之间传递10个以上数据的场景,所以相比之下,在这种情况下使用ArrayMap保存数据,在操作速度和内存占用上都具有优势,因此使用Bundle来传递数据,可以保证更快的速度和更少的内存占用。 另外一个原因,则是在Android中如果使用Intent来携带数据的话,需要数据是基本类型或者是可序列化类型,HashMap使用Serializable进行序列化,而Bundle则是使用Parcelable进行序列化。而在Android平台中,更推荐使用Parcelable实现序列化,虽然写法复杂,但是开销更小,所以为了更加快速的进行数据的序列化和反序列化,系统封装了Bundle类,方便我们进行数据的传输。
-
Android中Intent/Bundle的通信原理及大小限制
Intent 中的 Bundle 是使用 Binder 机制进行数据传送的。能使用的 Binder 的缓冲区是有大小限制的(有些手机是 2 M),而一个进程默认有 16 个 Binder 线程,所以一个线程能占用的缓冲区就更小了( 有人以前做过测试,大约一个线程可以占用 128 KB)。所以当你看到 The Binder transaction failed because it was too large 这类 TransactionTooLargeException 异常时,你应该知道怎么解决了
-
为何Intent不能直接在组件间传递对象而要通过序列化机制?
Intent在启动其他组件时,会离开当前应用程序进程,进入ActivityManagerService进程(intent.prepareToLeaveProcess()),这也就意味着,Intent所携带的数据要能够在不同进程间传输。首先我们知道,Android是基于Linux系统,不同进程之间的java对象是无法传输,所以我们此处要对对象进行序列化,从而实现对象在 应用程序进程 和 ActivityManagerService进程 之间传输。 而Parcel或者Serializable都可以将对象序列化,其中,Serializable使用方便,但性能不如Parcel容器,后者也是Android系统专门推出的用于进程间通信等的接口