1. Serializable 接口介绍
- Serializable 是 Java 提供的一个序列化接口,它是一个空接口(标记接口),为对象提供标准的序列化和反序列化操作
- 一个对象要实现序列化,只需要这个类实现 Serializable 接口并声明一个 serialVersionUID 常量即可,系统会自动完成序列化操作
- 实际上,serialVersionUID 不是必须的,不声明 serialVersionUID 同样可以实现序列化,但是这会对反序列化过程产生影响(类结构没有发生改变或即使改变但能兼容时就没有影响)
2. serialVersionUID 的作用及工作机制
作用
- serialVersionUID 是用来辅助序列化和反序列过程的
- 原则上序列化后的数据中的 serialVersionUID 只有和当前类的 serialVersionUID 相同才能够正常地被反序列化
工作机制
序列化的时候系统会把当前类的 serialVersionUID 写入序列化的文件中(也可能是其他中介)
反序列化的时候系统会去检测文件中的 serialVersionUID 是否和当前类的 serialVersionUID 一致
- 如果一致就说明序列化的类的版本和当前类的版本是相同的,此时可以成功进行反序列化操作
- 如果不一致就说明当前类和之前序列化的类相比发生了某些改变,此时反序列化会失败报错:
java.io.InvalidClassException
发生 carsh
一般来说,我们应该手动指定 serialVersionUID 的值
- 比如 1L,也可通过 IDE 根据当前类的结构自动生成它的 hash 值,两者效果一样
- 当我们手动指定之后,就可以在很大程度上避免反序列化的失败
特殊情况
- 如果类的结构发生了非常规性改变,比如修改了类名、修改了成员变量的类型,这个时候尽管 serialVersionUID 验证通过了,但是反序列化过程还是会失败
- 因为类的结构发生了根本性的改变,系统无法从老版本的数据中还原出一个新的类结构的对象
需要注意的两点
- 静态成员变量属于类不属于对象,所以不会参与序列化过程
- 使用 transient 关键字标记的成员变量不会参与序列化过程
默认序列化机制
- 系统的默认序列化过程是可以改变的,通过重写
writeObject()
和readObject()
两个方法即可实现自定义的序列化过程 - 大部分情况下我们不需要重写这两个方法,看具体需求实际情况决定是否自定义序列化过程
- 系统的默认序列化过程是可以改变的,通过重写
3. Serializable 接口使用 Demo
1 | public class User implements Serializable { |
1 | // 序列化 |
1 | // 反序列化 |
4. Parcelable 接口介绍
- 在序列化过程中需要实现的功能有序列化、反序列化和内容描述
- 序列化功能:由
writeToParcel()
方法完成,最终是通过 Parcel 中的一系列write()
方法来完成 - 反序列化功能:由 CREATOR 完成,其内部标明了如何创建序列化对象和数组,并通过 Parcel 的一系列
read()
方法来完成反序列化过程 - 内容描述功能:由
describeContents()
方法完成,几乎在所有情况下这个方法都应该返回 0,仅当当前对象中存在文件描述符时返回 1 - 系统已经为我们提供了许多实现了 Parcelable 接口的类,它们都是可以直接序列化的。比如 Intent、Bundle、Bitmap 等,同时 List 和 Map 也可以序列化,前提是它们里面的每个元素都是可序列化的
5. Parcelable 中的方法说明
方法 | 功能 | 标记位 |
---|---|---|
createFromParcel(Parcel in) |
从序列化后的对象中创建原始对象 | |
newArray(int size) |
创建指定长度的原始对象数组 | |
User(Parcel in) |
从序列化后的对象中创建原始对象 | |
writeToParcel(Parcel out, int flags) |
将当前对象写入序列化结构中,其中 flags 标识有两种值:0 或 1(参见右侧标记位)。为 1 时标识当前对象需要作为返回值返回,不能立即释放资源,几乎所有情况都为 0 | PARCELABLE_WRITE_RETURN_VALUE |
describeContents() |
返回当前对象的内容描述。如果含有文件描述符,返回 1(参见右侧标记位),否则返回 0,几乎所有情况都返回 0 | CONTENTS_FILE_DESCRIPTOR |
6. Parcelable 接口使用 Demo
1 | public class User implements Parcelable { |
7. Serializable 和 Parcelable 两种序列化方式的对比
- Serializable: Java 中的序列化接口,其使用起来简单但是开销很大,因为序列化和反序列化过程需要大量 IO 操作
- Parcelable: 是 Android 中的序列化方式,因此更适合用在 Android 平台上,缺点是使用起来稍微麻烦,但效率很高,是 Android 推荐的序列化方式
8. Binder 简介
- 直观来说:Binder 是 Android 中的一个类,它实现了 IBinder 接口
- 从 IPC 角度:Binder 是 Android 中的一种跨进程通信方式,还可以把 Binder 理解为一种虚拟的物理设备,它的设备驱动是 /dev/binder,该通信方式在 Linux 中没有
- 从 Android Framework 角度:Binder 是 ServiceManager 连接各种 Manager(ActivityManager、WindowManager 等)和相应 ManagerService 的桥梁
- 从 Android 应用层角度:Binder 是客户端和服务端进行通信的媒介
- 当 bindService 的时候,服务端会返回一个包含了服务端业务调用的 Binder 对象
- 通过这个 Binder 对象,客户端就可以获取服务端提供的服务或者数据,这里的服务包括普通服务和基于 AIDL 的服务
9. Binder 的工作机制简介
- 首先,当客户端发起远程请求时,由于当前线程会被挂起直至服务端进程返回数据,所以如果一个远程方法是很耗时的,那么不能在 UI 线程中发起此远程请求
- 其次,由于服务端的 Binder 运行在 Binder 线程池中,所以 Binder 方法不管是否耗时都应该采用同步的方式去实现,因为它已经运行在一个线程中了
- 流程图
10. 手动实现一个 Binder 步骤
- 声明一个 AIDL 性质的接口,只需要继承 IInterface 接口即可
- 实现内部类 Stub 和 Stub 类中的 Proxy 代理类,这段代码我们可以自己写,但是写出来后会发现和系统自动生成的代码是一样的,因此这个 Stub 类我们只需参考系统生成的代码即可,只是结构上需要做一下调整
- 实际开发中完全可以通过 AIDL 文件让系统去自动生成 Binder,AIDL 文件的本质是系统为我们提供了一种快速实现 Binder 的工具,仅此而已
11. Binder 中的 方法 linkToDeath() 和 unlinkToDeath() 介绍
原因
- Binder 运行在服务端进程,如果服务端进程由于某种原因异常终止,这个时候我们到服务端的 Binder 连接断裂(称之为 Binder 死亡),会导致我们的远程调用失败
- 更为关键的是,如果我们不知道 Binder 连接已经断裂,那么客户端的功能就会受到影响
解决
- 为了解决 Binder 死亡的问题,Binder 中提供了两个配对的方法
linkToDeath()
和unlinkToDeath()
- 通过方法
linkToDeath()
,我们可以给 Binder 设置一个死亡代理,当 Binder 死亡时,我们就会收到通知,这个时候就可以重新发起连接请求从而恢复连接
- 为了解决 Binder 死亡的问题,Binder 中提供了两个配对的方法
给 Binder 设置死亡代理的步骤
声明一个 DeathRecipient 对象。DeathRecipient 是一个接口,其内部只有一个方法
binderDied()
,我们需要实现这个方法,当 Binder 死亡的时候,系统就会回调binderDied()
方法,然后就可以移出之前绑定的 binder 代理并重新绑定远程服务:1
2
3
4
5
6
7
8
9
10
11private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient () {
public void binderDied () {
if (mBookManager == null) {
return;
}
mBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
mBookManager = null;
// TODO: 这里重新绑定远程 service
}
}在客户端绑定远程服务成功后,给 binder 设置死亡代理:
1
2mService = IMessageBoxManager.Stub.asInterface(binder);
binder.linkToDeath(mDeathRecipient, 0); // 第二个参数是标记位,直接设为 0 即可也可以通过 Binder 的
isBinderAlive()
方法判断 Binder 是否死亡