Serializable和Parcelable接口可以完成对象的序列化过程,通过Intent和Binder传输数据时就需要使用它们。有时需要把对象持久化到存储设备上或者通过网络传输给其他客户端,此时也可以使用它们。
1.Serializable接口
Serializable是Java提供的一个序列化接口,为对象提供标准的序列化和反序列化操作。
想让一个对象实现序列化,只需要这个类实现Serializable接口并声明一个serialVersionUID即可。实际上,甚至这个serialVersionUID也不是必需的,但此时将会对反序列化过程产生影响,原则上序列化后数据中的serialVersionUID只有和当前类的serialVersionUID相同才能够正常地反序列化。
serialVersionUID的详细工作机制:
序列化的时候系统会把当前类的serialVersionUID写入序列化的文件中(也可能是其他中介),当反序列化时系统会检测文件中的serialVersionUID,看它是否和当前类的serialVersionUID一致,如果一致就说明序列化的类版本和当前类的版本是相同的,这个时候可以成功反序列化;否则说明当前类和序列化的类相比发生了某些变换,比如成员变量的数量、类型可能发生了改变,这个时候是无法正常反序列化的。
静态成员变量属于类不属于对象,所以不参与序列化过程;
用transient关键字标记的成员变量也不参与序列化过程。
采用ObjectOutputStream和ObjectInputStream即可轻松实现对象的序列化和反序列化。
例子:序列化和反序列化一个User对象
public class User implements Serializable {
private static final long serialVersionUID = 0L;
public int userId;
public String userName;
public User(int id, String name) {
userId = id;
userName = name;
}
@Override
public String toString() {
return "userId:" + userId + ", userName:" + userName;
}
}
实现:
public class MainActivity extends Activity {
private String mPath;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mPath = getFilesDir() + "/cache.txt";
File file = new File(mPath);
if (!file.exists()) {
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
protected void onResume() {
super.onResume();
// 序列化过程
try {
User user = new User(0, "zhangsan");
ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream(mPath));
oos.writeObject(user);
oos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected void onPause() {
super.onPause();
// 反序列化过程
try {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
mPath));
User newUser = (User) ois.readObject();
ois.close();
System.out.println(newUser.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
上述把实现了Serializable接口的User对象写到文件中就可以快速恢复了,恢复后的对象newUser和user的内容完全一样,但是两者并不是同一个对象。
2.Parcelable接口
先说下Parcel,Parcel内部包装了可序列化的数据,可以在Binder中自由传输。
在序列化过程中需要实现的功能有序列化、反序列化和内容描述。
序列化功能:由writeToParcel方法来完成,最终是通过Parcel中的一系列write方法来完成的。
反序列化功能:由CREATOR来完成,其内部标明了如何创建序列化对象和数组,并通过Parcel的一系列read方法来完成反序列化过程。
内容描述功能:由describeContents方法来完成,几乎所有情况下这个方法都返回0,只有当当前对象中存在文件描述符时才返回1.
public class User implements Parcelable {
public int userId;
public String userName;
public Book book;
public User(Parcel in) {
userId = in.readInt();
userName = in.readString();
book = in.readParcelable(Thread.currentThread().getContextClassLoader());
}
public User(int id, String name) {
userId = id;
userName = name;
}
// 描述功能
@Override
public int describeContents() {
return 0;
}
// 序列化功能
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(userId);
dest.writeString(userName);
dest.writeParcelable(book, 0);
}
// 反序列化功能
public static final Parcelable.Creator<User> CREATOR = new Parcelable.Creator<User>() {
@Override
public User createFromParcel(Parcel source) {
return new User(source);
}
@Override
public User[] newArray(int size) {
return new User[size];
}
};
}
需要注意的是,在User(Parcel in)方法中,由于book是另一个可序列化对象(Book类也需要实现Parcelable接口),所以它的反序列化过程需要传递当前线程的上下文类加载器。
Serializable是Java提供的接口,使用简单但是开销很大,序列化和反序列化过程需要大量的I/O操作。
而Parcelable是Android中的序列化方式,使用稍微麻烦但是效率很高。
3.Binder
直观来说,Binder是Android中的一个类,它实现了IBinder接口。
从IPC角度来说,Binder是Android中的一种跨进程通信方式,Binder还可以理解为一种虚拟的物理设备,它的设备驱动是/dev/binder。
从framework角度来说,Binder是ServiceManager连接各种Manager和相应ManagerService的桥梁。
从Android应用层来说,Binder是客户端和服务端进行通信的媒介。
根据系统生成的Binder类来分析Binder的工作原理:
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: D:\\WorkSpace\\Aidl4\\src\\com\\example\\aidl4\\IPersionBinder.aidl
*/
package com.example.aidl4;
public interface IPersionBinder extends android.os.IInterface {
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements
com.example.aidl4.IPersionBinder {
private static final java.lang.String DESCRIPTOR = "com.example.aidl4.IPersionBinder";
/** Construct the stub at attach it to the interface. */
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.example.aidl4.IPersionBinder
* interface, generating a proxy if needed.
*/
public static com.example.aidl4.IPersionBinder asInterface(
android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.example.aidl4.IPersionBinder))) {
return ((com.example.aidl4.IPersionBinder) iin);
}
return new com.example.aidl4.IPersionBinder.Stub.Proxy(obj);
}
@Override
public android.os.IBinder asBinder() {
return this;
}
@Override
public boolean onTransact(int code, android.os.Parcel data,
android.os.Parcel reply, int flags)
throws android.os.RemoteException {
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_getName: {
data.enforceInterface(DESCRIPTOR);
com.example.aidl4.PersionParcel _arg0;
if ((0 != data.readInt())) {
_arg0 = com.example.aidl4.PersionParcel.CREATOR
.createFromParcel(data);
} else {
_arg0 = null;
}
java.lang.String _result = this.getName(_arg0);
reply.writeNoException();
reply.writeString(_result);
return true;
}
case TRANSACTION_getAllPersions: {
data.enforceInterface(DESCRIPTOR);
java.util.List<com.example.aidl4.PersionParcel> _result = this
.getAllPersions();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.example.aidl4.IPersionBinder {
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
@Override
public android.os.IBinder asBinder() {
return mRemote;
}
public java.lang.String getInterfaceDescriptor() {
return DESCRIPTOR;
}
@Override
public java.lang.String getName(com.example.aidl4.PersionParcel p)
throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.lang.String _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((p != null)) {
_data.writeInt(1);
p.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_getName, _data, _reply, 0);
_reply.readException();
_result = _reply.readString();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override
public java.util.List<com.example.aidl4.PersionParcel> getAllPersions()
throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.example.aidl4.PersionParcel> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getAllPersions, _data,
_reply, 0);
_reply.readException();
_result = _reply
.createTypedArrayList(com.example.aidl4.PersionParcel.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
static final int TRANSACTION_getName = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_getAllPersions = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
public java.lang.String getName(com.example.aidl4.PersionParcel p)
throws android.os.RemoteException;
public java.util.List<com.example.aidl4.PersionParcel> getAllPersions()
throws android.os.RemoteException;
}
上述代码是系统生成的,为了方便查看做了格式化调整。
在gen目录下,根据IPersionBinder.aidl系统生成了IPersionBinder.java类,它是一个实现了IInterface接口的接口,所有可以在Binder中传输的接口都需要实现IInterface接口。通过这个类我们可以清楚地了解到Binder的工作机制。
首先,它声明了两个方法getName和getAllPersions,显然这就是在aidl中声明的方法。
接着,它声明了一个内部类Stub,这个Stub就是一个Binder类,类中声明了两个整形的id分别用于标识这两个方法,这两个id用于标识在transact过程中客户端所请求的到底是哪个方法。
当客户端和服务端都位于同一个进程时,方法调用不会走跨进程的transact过程,而当两者位于不同进程时,方法调用需要走transact过程,这个逻辑由Stub的内部代理类Proxy来完成。这个接口的核心实现就是它的内部类Stub和Stub的内部代理类Proxy,下面详细介绍这两个类中每个方法的含义。
DESCRIPTOR
Binder的唯一标识,一般用当前Binder的类名标识。
asInterface(android.os.IBinder obj)
用于将服务端的Binder对象转换成客户端所需的AIDL接口类型的对象,这种转换过程是区分进程的。如果客户端和服务端位于同一进程,则此方法返回服务端的Stub对象本身,否则返回的是系统封装后的Stub.Proxy对象。
asBinder
此方法用于返回当前Binder对象。
onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)
这个方法运行在服务端中的Binder线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交给此方法来处理。
服务端通过code可以确定客户端所请求的目标方法是什么,接着从data中取出目标方法所需要的参数,然后执行目标方法。当目标方法执行完毕后就向reply中写入返回值。需要注意的是,如果此方法返回false,那么客户端的请求会失败,因此可以利用这个特性来做权限验证。
Proxy#getName、getAllPersions
这两个方法运行在客户端,当客户端远程调用时,它的内部实现是这样的:
首先创建方法所需要的输入型Parcel对象_data、输出型Parcel对象_reply和返回值_result;然后把该方法的参数信息写入_data中;接着调用transact方法来发起RPC(远程过程调用)请求,同时当前线程挂起;然后服务端的onTransact方法被调用,直到RPC过程返回后,当前线程继续执行,并从_reply中取出RPC过程的返回结果;最后返回_reply中的数据。
还有两点需要注意:
首先,当客户端发起远程请求时,由于当前线程会被挂起直至服务端进程返回数据,所以如果一个远程方法是很耗时的,那么不能在UI线程中发起此远程请求;
其次,由于服务端的Binder方法运行在Binder线程池中,所以Binder方法不管是否耗时都应该采用同步的方式实现,因为它已经运行在一个线程中了。