一、为什么要进行进程间的通信?
进程通信指的是两个不同的进程之间进行数据交换的过程,这个进程和线程是两种完全不同的概念。
线程指的是CPU调度的最小单元,是一种有限的系统资源;而进程是一种执行单元,应用至少要有一个进程才可以运行(一个应用也可以有多个进程)。
众所周知,Java是在JVM中运行的,在Android中,系统会为每个进程分配一个虚拟机,不同的虚拟机在内存分配上有不同的地址空间,导致在不同虚拟机中访问同一个类的对象时会产生多份副本,而且这些副本对象都是相互独立不受其它副本对象干扰的,这就意味着,你在一个进程中对某个类的对象作出了修改,其它进程中这个类的对象是不会产生改变的。
除了不同应用之间需要进行进程间通信外,有时候一个应用本身内部也需要进行多进程的数据交换,比如应用和后台服务的数据交换。
一般来说,使用多进程会造成以下几方面问题:
1、静态成员和单例模式完全失效。如上面所描述的,一个进程中的类发生改变,对其它进程的这个类不会产生影响。
2、线程同步机制完全失效。本质上还是和第一点类似,不管是在进程中锁对象还是锁全局类,都不会对其它进程中的这个类产生影响,因为它们在不同的内存空间中。
3、SharedPreferences可靠性下降。SharedPreferences底层通过读/写XML文件来实现,如果两个进程中同时对一个ShredPreferences进行写操作,很明显是会出问题的。
4、Application会多次创建。当一个组件跑在一个新的进程中时,系统要在创建新的进程的同时分配独立的虚拟机,这个过程就是启动一个应用的过程。相当于系统又把这个应用重新启动了一遍,因此会重新创建Application。
二、IPC要解决的问题
Android进程间通信需要解决以下几个问题:
- 通信目标:当前进程要调用的是哪个进程及该进程的哪个方法功能
- 数据交换:当前进程怎么把调用目标方法所需要的参数传递给目标进程,以及如何取回处理结果
- 简化操作:怎么屏蔽进程间的底层通信细节,使得当前进程调用远程方法像调用本地方法一样方便
解决方案:
- 使用远程通信类的唯一标识符(包名+类名)确定一个进程及其服务
- 使用Parcelable序列化对象进行数据交换
- 在被调用进程(服务端)定义一个类专门用于处理跨进程请求,这个类就是Binder类
三、Binder的使用
如Messenger,AIDL等几种进程间通信方式其实都是对Binder的封装和实现,而Binder本身也是可以直接使用的。
本文章不涉及Binder具体的理论知识(因为太难懂了...),仅示范Binder是如何使用的。
首先看Binder的运行过程
请求远程服务的进程为客户端,被请求的进程为服务端,进程之间通过Binder进行数据交换。
客户端发起远程请求并挂起(耗时操作),直至服务端返回处理结果唤醒。
Server进程:
首先来实现服务端进程
1、声明一个接口,在接口中定义服务要对外提供的方法
public interface IReport{
//对外提供的报告方法
int report(String values, int type);
}
2、创建Binder,实现上面的接口
public class Report extends Binder implements IReport{
@Override
protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {
//接到调用,解包
switch (code) {
case REPORT_CODE:
data.enforceInterface("reporter");
String values = data.readString();
Log.i("IReporter", "data is '" + values + "'");
//调用对外提供的方法处理客户端传入的参数
int type = data.readInt();
int result = report(values, type);
//处理结果回传
reply.writeInterfaceToken("reporter");
reply.writeInt(result);
return true;
}
return super.onTransact(code, data, reply, flags);
}
@Override
public int report(String values, int type) {
return type;
}
}
3、创建服务端,在服务端声明一个Binder,并在onBind方法中返回它
服务端完整代码如下(此处把接口和Binder都写在服务端代码中):
public class BindService extends Service {
public static final int REPORT_CODE = 0;
private Report mReport;
public interface IReport{
//对外提供的报告方法
int report(String values, int type);
}
//Binder
public final class Report extends Binder implements IReport{
@Override
protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {
//接到调用,解包
switch (code) {
case REPORT_CODE:
data.enforceInterface("reporter");
String values = data.readString();
Log.i("IReporter", "data is '" + values + "'");
//调用对外提供的方法处理客户端传入的参数
int type = data.readInt();
int result = report(values, type);
//处理结果回传
reply.writeInterfaceToken("reporter");
reply.writeInt(result);
return true;
}
return super.onTransact(code, data, reply, flags);
}
@Override
public int report(String values, int type) {
return type;
}
}
public BindService(){
mReport = new Report();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mReport;
}
}
Client进程:
Client的代码比较简单,创建一个ServiceConnection,在onCreate中用这个连接绑定服务就可以进行数据交换了
private class BindConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//Client组包:通过obtain获取发送包对象和应答包对象,写入数据
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken("reporter");
data.writeString("this is a test String");
data.writeInt(0);
try {
//调用IBinder的transact接口按序发送数据包
service.transact(BindService.REPORT_CODE, data, reply, 0);
//接收处理结果
reply.enforceInterface("reporter");//接口验证
int result = reply.readInt();
} catch (RemoteException e) {
e.printStackTrace();
}
//资源回收
data.recycle();
reply.recycle();
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//绑定服务
Intent intent = new Intent(this, BindService.class);
bindService(intent, new BindConnection(), BIND_AUTO_CREATE);
}
四、传输自定义对象
Binder传输的对象必须是可序列化的,如基本类型String,int等基本数据类型,以及Bundle所支持的数据类型
因此,要传输用户自定义的对象,必须在对象类中实现Parcelable接口,让对象可序列化,Parcelable实现如下所示:
import android.os.Parcel;
import android.os.Parcelable;
public class Book implements Parcelable {
public int code;
public String name;
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(this.code);
dest.writeString(this.name);
}
public Book() {
}
protected Book(Parcel in) {
this.code = in.readInt();
this.name = in.readString();
}
public static final Parcelable.Creator<Book> CREATOR = new Parcelable.Creator<Book>() {
@Override
public Book createFromParcel(Parcel source) {
return new Book(source);
}
@Override
public Book[] newArray(int size) {
return new Book[size];
}
};
}
之后就可以使用Bundle来进行数据交换了
Bundle bundle = new Bundle();
bundle.putParcelable("book",book);
data.writeBundle("book",bundle);