IPC中的一些基础概念,主要包括三方面的内容:Serializable接口、Parcelable接口以及Binder,只有熟悉这三方面的内容后,我们才能更好的理解跨进程通信的各种方式。
1.1 Serializable接口
Serializable 是Java提供的一个序列化的接口,他是一个空接口,为对象提供标准的序列化和反序列化操作。
1.1.1、序列化是干什么的?
简单说就是为了保存在内存中的各种对象的状态(也就是实例变量,不是方法),并且可以把保存的对象状态再读出来。虽然你可以用你自己的各种各样的方法来保存object states,但是Java给你提供一种应该比你自己好的保存对象状态的机制,那就是序列化。
1.1.2、什么情况下需要序列化
(1)当你想把的内存中的对象状态保存到一个文件中或者数据库中时候
(2)当你想用套接字在网络上传送对象的时候
(3)当你想通过RMI传输对象的时候
1.1.3、Serializable的使用方法
使用Serializable来实现序列化的方法特别简单,只需要在类的声明中指定一个类似下面的标识即可自动实现默认的序列化过程:
public class User implements Serializable{
private static final long serialVersionUID=1L;
public String userId;
private String usereName;
public User(String userId, String usereName) {
this.userId = userId;
this.usereName = usereName;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getUsereName() {
return usereName;
}
public void setUsereName(String usereName) {
this.usereName = usereName;
}
}
使用Serializable方式来实现对象的序列化,实现起来非常简单,几乎所有的都被系统自动完成了。如何进行对象的系列化和反序列化也非常简单,只需要采用ObjectOutputStream和ObjectInputStream来实现既可以。
(1)序列化过程
User user=new User(0,"james");
ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("cache.txt"));
out.writeobject(user);
out.close();
(2)反序列化过程
ObjectInputStream in=new ObjectInputStream(new FileInputStream("cache.txt"));
User user=(User) in.readObject();
in.close();
上面的方法演示了如何采用Serializable方式去实现序列化的经典用法,只需要把实现了Serializable接口的user对象写到文件中就可以恢复了。恢复后的对象newUser和User的内容一致,但不是同一个对象。
- [注意]serialVersionUID的作用:
其实即使不指定serialVersionUID也可以实现对象的序列化,但是序列化的时候系统会把当前类的serialVersionUID写入序列化的文件中(也可能是其他中介),当反序列化的时候系统会去检测文件中的serialVersionUID,看他是否是那和当前类的serialVersionUID相同,如果一致就说明序列化的版本和当前类的版本是相同的,这个时候就可以被成功反序列化.
1.2 Parcelable接口
Parcelable也是一个接口,只要实现这个接口,一个类的对象就可以实现序列化并通过Intent和Binder传递。此种方式在Android中适用,其过程比较比较麻烦。
使用方式:
public class MyParUser implements Parcelable {
public int id;
public String userName;
public MyParUser(int id, String userName) {
this.id = id;
this.userName = userName;
}
protected MyParUser(Parcel in) {
id = in.readInt();
userName = in.readString();
}
public static final Creator<MyParUser> CREATOR = new Creator<MyParUser>() {
@Override
public MyParUser createFromParcel(Parcel in) {
return new MyParUser(in);
}
@Override
public MyParUser[] newArray(int size) {
return new MyParUser[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(id);
dest.writeString(userName);
}
}
1.2.1Parcel的解释:
Parcel内部包装了可序列化的数据,可以在Binder中自由传递。从上面的代码中可以看出在序列化的过程中需要实现的功能有序列化和反序列化还有内容描述。序列化的功由writeToPaecel()方法来实现最终是通过Parcel中一系列的write方法来完成的。反序列化是由Creator完成的,其内部标明了如何创建序列化的对象和数组,并通过Parcel一系列的read方法来完成反序列化。内容描述是由describeContents,几乎所有情况下都是返回0,仅当当前的对象存在文件描述符是返回1;
++==Serializable和Parcelable的比较:==++
两者都可以实现序列化并且都可用于本Intent间的数据传递,那么二者该如何选择呢,前者是Java中的序列化接口,其使用简单但是开销巨大,序列化和反序列化都需要大量的Io操作,而后者是Android中的序列化的方式,更适用于Android,但是其最大的缺点是使用起来比较麻烦,但是其的效率很高,其主要用在内存序列化上,通过它将对象序列化到存储设备中或者将对象序列化后通过网络传输也都是可以的。
1.3 Binder
1.3.1 Binder的使用和上层原理
Android开发中,Binder主要用于Service中,包括AIDL和Messager,其中普通的服务中的Binder不涉及进程间通信,而Messager底层其实是AIDL,故而只要了解AIDL就能大致了解AIDL的机制。
理解角度 | 理解 |
---|---|
IPC角度 | Android中的一种跨进程通信地一种方式,可以理解为一种虚拟设备,他的设备驱动为/dev/binder,这种方式在Linux中没有 |
Android Framework角度 | 是ServerManager连接各种Manager(ActivityManager、WindowManager)和相应ManagerService的桥梁 |
Android 应用层角度 | 是客户端和服务端进行通信的媒介,当binderService的时候,服务端会返回一个包含了服务端调用的Binder对象,通过它客户端就可以获取服务端提供的服务或者数据,这里的服务指的是普通服务和基于AIDL的服务。 |
1.3.2 AIDL的创建
创建一个普通的项目,在项目中创建一个你想创建的实体类比如:User,让其实现序列化接口Parcelable,点击右键创建AIDL文件,系统会在java平级的地方生成aidl目录并生成,创建Book.aidl ( 这里的命名是可以任意修改的,这里我们修改为User.aidl,和我们的User.java类保持一致。User.aidl是User类在ALDL中的声明,默认生成的.aidl都是interface,这里要改成序列化的对象 ),以及IUserManager.aidl(创建一个AIDL接口,里边包含两个方法:getUserList和addUser尽管User类和IUserManager位于同一个包中,但是仍需手动在IUserManager中导入User类,这是AIDL的特殊之处),
具体代码如下:
User.java
package com.byd.aidl;
import android.os.Parcel;
import android.os.Parcelable;
/**
* 作者:byd666 on 2017/9/20 15:09
* 邮箱:sdkjdxbyd@163.com
*/
public class User implements Parcelable{
public int bookId;
public String bookName;
public User(int bookId, String bookName) {
this.bookId = bookId;
this.bookName = bookName;
}
protected User(Parcel in) {
bookId = in.readInt();
bookName = in.readString();
}
public static final Creator<User> CREATOR = new Creator<User>() {
@Override
public User createFromParcel(Parcel in) {
return new User(in);
}
@Override
public User[] newArray(int size) {
return new User[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(bookId);
dest.writeString(bookName);
}
}
User.aidl
// User.aidl
package com.byd.aidl;
// Declare any non-default types here with import statements
parcelable User;
IUserManager.aidl
// IUserManager.aidl
package com.byd.aidl;
// Declare any non-default types here with import statements
import com.byd.aidl.User;
interface IUserManager {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
List <User> getUserList();
void addUser(in User user);
}
创建完成后,点击build中rebuild一下项目,在app-build-generated-source-aidl-debug找到系统为我们生成的IuserManager.java这个类。
1.3.3 IuserManager.java 分析
可以看到系统根据IUserManager.aidl为我们生成了IUserManager.java这个接口类,他继承了android.os.IInterface这个接口,所有在Binder中传输的接口都需要继承它,看它整体结构很清晰:
内部类:Stub,他就是一个Binder类,当客户端和服务端位于同一个进程时方法调用不会走跨进程的transact过程,反之则需要,这个逻辑是由Stub的内部代理类Proxy来完成的,其核心就在于Stub以及Stub的内部代理类Proxy。
两个方法:getUserList() 以及addUser(com.byd.aidl.User user) 很明显就是我们在IUserManager.aidl中声明的方法。
1.3.4 IuserManager.java 中的核心内部类Stub分析
DESCRIPTOR:Binder的唯一标识,一般用当前Binder的类名表示,
Stub:默认构造方法
asInterface:将服务端的Binder对象转化成客户端所需的AIDL接口类型的对象,这种转化区分进程,当客户端和服务端在同一进程时返回的是服务端的Stub对象本身,否则返回系统封装后的Stub.proxy对象。
asBinder:返回当前Binder对象
onTransact:这个方法运行在Server端的Binder线程池中,当客户端发起跨进程通信请求时,远程请求会通过系统底层封装后交由此方法来处理。
Proxy:这个代理类在客户端执行操作、
TRANSACTION_getUserList ,TRANSACTION_addUser :这两个id用来标识在transact过程中客户端所请求的的到底是那个方法。