一、为什么要进行进程间的通信?
进程通信指的是两个不同的进程之间进行数据交换的过程,这个进程和线程是两种完全不同的概念。
线程指的是CPU调度的最小单元,是一种有限的系统资源;而进程是一种执行单元,应用至少要有一个进程才可以运行(一个应用也可以有多个进程)。
众所周知,Java是在JVM中运行的,在Android中,系统会为每个进程分配一个虚拟机,因此,在不同的进程中,内存里数据是不能直接进行交换的,所以就有了IPC机制(Inter-Process Communication)
除了不同应用之间需要进行进程间通信外,有时候一个应用本身内部也需要进行多进程的数据交换,比如应用和后台服务的数据交换。
二、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);