跨进程通信流程
通过上文可发现,要实现跨进程通信,需要客户端、服务端、客户端与服务端通信规约也就是通过aidl生成的java接口。下面用一个图来表述:
对于上图的调用过程,我们做一下解释:上图中列了几个对象的关联关系,其中有客户端aidl、binder驱动程序、server端aidl。
在客户端aidl文件中有asInterface方法,这个方法最终是客户端程序绑定服务端service返回的服务端代理对象,并派生出了Proxy对象给客户端调用。所以在这之前服务端需要向Binder驱动程序注册服务,并产生一个代理保存到binder中,等待客户端调用。所以需要在服务端建立一个Service的子类来完成getFile 与sendData方法的实现,这个过程就是实现接口方法并向客户端公开接口的过程。其中IMyservice 服务端的service,并在此service中实现getFile与sendData方法。
当客户端拿到Proxy代理对象后,会调用具体的方法比如getFile,调用getFile时,会向binder驱动程序中查找此方法,找到了则由binder驱动程序转发给服务端,服务端在Stub中的OnTransact方法中,根据方法code找到本地的binder线程池中的实体,并调用IMyservice中的getFile方法,调用完成后,并将结果返回binder驱动,再由binder驱动程序唤醒客户端接收服务端返回的结果。
到此,整个客户端和服务端的大概的通信过程就结束了。下面结合上图的分析,我们先来创建服务端示例。
服务端实现及向客户端公开接口
服务端的实现过程很简单,就是在服务端创建一个Serivce的派生类,如要实现 .aidl 生成的接口,就必须扩展生成的 Binder 接口(例如,IMyAidlInterface.Stub),并实现继承自 .aidl 文件的方法。
下面的案例,为了模拟跨进程的情况,我们先在同一个应用程序中开启一个新的进程来进行测试。
在Android studio中创建一个Service,并通过匿名的方式创建一个IMyAidlInterface.Stub实例,并将此实例返回给客户端了,以便客户端能与服务进行交互。代码如下所示:
public class MyAidlBinderService extends Service {
private static final String TAG = "MyAidlBinderService" ;
private static int count = 1 ;
private static int rcount = 1 ;
public MyAidlBinderService() {
}
private final IMyAidlInterface.Stub sBinder = new IMyAidlInterface.Stub() {
//此方法是创建aidl文件时自动生成的,这里可忽略。
@Override
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
}
@Override
public String getFile() throws RemoteException {
Log.i(TAG ,"客户端调用了getFile方法");
return "file - " + count;
}
@Override
public String sendData(String message) throws RemoteException {
Log.i(TAG ,"客户端调用了sendData方法,其中传来的信息为 "+message+ " 当前进程为"+ ProcessUtil.getCurrentProcessName(MyAidlBinderService.this));
return "服务端收到消息" + rcount + "次";
}
};
@Override
public IBinder onBind(Intent intent) {
return sBinder;
}
}
上述代码中,sBinder 是 Stub 类的一个实例(一个 Binder),其定义了服务的远程过程调用 (RPC) 接口。会向客户端公开此实例。在为服务实现接口后,您需要向客户端公开该接口,以便客户端进行绑定。为了您的服务公开该接口,扩展 Service 并实现 onBind(),从而返回实现生成的 Stub 的类实例(如前文所述)。
在实现 AIDL 接口时,您应注意遵守以下规则:
- 由于无法保证在主线程上执行传入调用,因此您一开始便需做好多线程处理的准备,并对您的服务进行适当构建,使其达到线程安全的标准。
- 默认情况下,RPC 调用是同步调用。如果您知道服务完成请求的时间不止几毫秒,则不应从 Activity 的主线程调用该服务,因为这可能会使应用挂起(Android 可能会显示“Application is Not Responding”对话框)— 通常,您应从客户端内的单独线程调用服务。
- 您引发的任何异常都不会回传给调用方。
服务注册
为了跨进程调用服务端,需要在AndroidMenifest清单文件中注册service
<service android:name=".MyAidlBinderService"
android:enabled="true"
android:exported="true"
android:process=":myAidl"/>
为了让service运行在不同的进程中,添加了process属性,进程名称为包名+myAidl 。
客户端调用
上述服务端代码已经写好了,现在要做的是在当前app中写一个客户端调用方法来远程调用服务端的方法:这个非常简单,创建一个测试activity即可,如下所示
public class MyAidlBinderActivity extends AppCompatActivity {
private IMyAidlInterface iMyAidlInterface;
private static final String TAG = MyAidlBinderActivity.class.getSimpleName();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_aidl_binder);
Button bindService = (Button)findViewById(R.id.aidlBindService);
bindService.setOnClickListener(binderService);
Button getFile = (Button)findViewById(R.id.aidl_getFile);
getFile.setOnClickListener(c_getFile);
Button sendData = (Button)findViewById(R.id.aidl_sendData);
sendData.setOnClickListener(c_sendData);
}
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
/**
* //当与服务建立连接时调用,为我们提供了可用于与服务交互的服务对象。
* // 我们通过AIDL接口与服务通信,因此从原始服务对象获得该接口的客户端表示。
*/
iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
/**
* 这里创建一个点击事件才触发绑定服务:
* 与服务建立一对连接,通过接口名绑定。这允许通过实现相同的接口来安装替代远程服务的其他应用程序。
*/
private View.OnClickListener binderService = new View.OnClickListener(){
@Override
public void onClick(View v) {
Intent intent = new Intent(MyAidlBinderActivity.this, MyAidlBinderService.class);
bindService(intent,mServiceConnection, Context.BIND_AUTO_CREATE);
}
};
/**
* 这里创建一个点击事件才触发绑定服务:
* 与服务建立一对连接,通过接口名绑定。这允许通过实现相同的接口来安装替代远程服务的其他应用程序。
*/
private View.OnClickListener c_getFile = new View.OnClickListener(){
@Override
public void onClick(View v) {
if (iMyAidlInterface!=null){
try {
String res = iMyAidlInterface.getFile();
Log.i(TAG,"服务端调用getFile返回的值为 " + res);
}catch (Exception e){
e.printStackTrace();
}
}
}
};
/**
* 这里创建一个点击事件才触发绑定服务:
* 与服务建立一对连接,通过接口名绑定。这允许通过实现相同的接口来安装替代远程服务的其他应用程序。
*/
private View.OnClickListener c_sendData = new View.OnClickListener(){
@Override
public void onClick(View v) {
if (iMyAidlInterface!=null){
try {
String res = iMyAidlInterface.sendData("Hello Sever!");
Log.i(TAG,"服务端调用sendData返回的值为 " + res + " 当前进程为"+ ProcessUtil.getCurrentProcessName(MyAidlBinderActivity.this)) ;
}catch (Exception e){
e.printStackTrace();
}
}
}
};
}
上述代码的调用过程非常简单,下面贴出来一个案例效果图:
日志打印效果,为了区分跨进程,打印日志中专门打印出了进程的名字,如下所示
2023-03-22 17:21:21.443 6883-6899/com.cop.ronghw.study_android_exact I/MyAidlBinderService: 客户端调用了getFile方法
2023-03-22 17:21:21.443 6830-6830/com.cop.ronghw.study_android_exact I/MyAidlBinderActivity: 服务端调用getFile返回的值为 file - 1
2023-03-22 17:21:28.894 6883-6899/com.cop.ronghw.study_android_exact I/MyAidlBinderService: 客户端调用了 sendData方法,其中传来的信息为 Hello Sever!当前进程com.cop.ronghw.study_android_exact:myAidl
2023-03-22 17:21:28.899 6830-6830/com.cop.ronghw.study_android_exact I/MyAidlBinderActivity: 服务端调用sendData返回的值为 服务端收到消息1次当前进程com.cop.ronghw.study_android_exact
到此,我们就完成了客户端与服务端的简单通信功能。对于上述代码我们来总结一下:
-
当客户端(如 Activity)调用 bindService() 以连接此服务时,客户端的 onServiceConnected() 回调会接收服务的 onBind() 方法所返回的 binder 实例。
-
客户端还必须拥有接口类的访问权限,因此如果客户端和服务在不同应用内,则客户端应用的 src/ 目录内必须包含 .aidl 文件(该文件会生成 android.os.Binder 接口,进而为客户端提供 AIDL 方法的访问权限)的副本。
-
当客户端在 onServiceConnected() 回调中收到 IBinder 时,它必须调用 IMyAidlInterface.Stub.asInterface(service),以将返回的参数转换成 IMyAidlInterface 类型。
通过 IPC 传递对象
内容:如何传递aidl自定义类型如对象实体,
客户端服务端对象的传递
上文的案例中,讲述的是在客户端服务器之间传递的数据都是aidl语法中的基本类型数据,那么如何跨进程传递自定义类型的数据呢?比如传递一个对象。假设现在有一个这样的场景,如果在客户端想把车辆的信息传输给服务端保存,客户端能查询到所有服务端车辆的信息列表,针对这两个方法我们来使用代码模拟一下。
具体步骤如下:
您可以通过 IPC 接口,将某个类从一个进程发送至另一个进程。不过,您必须确保 IPC 通道的另一端可使用该类的代码,并且该类必须支持 Parcelable 接口。支持 Parcelable 接口很重要,因为 Android 系统能通过该接口将对象分解成可编组至各进程的原语。
如要创建支持 Parcelable 协议的类,您必须执行以下操作:
-
让您的类实现 Parcelable 接口。
-
实现 writeToParcel,它会获取对象的当前状态并将其写入 Parcel。
-
为您的类添加 CREATOR 静态字段,该字段是实现 Parcelable.Creator 接口的对象。
-
最后,创建声明 Parcelable 类的 .aidl 文件。
如果您使用的是自定义编译进程,请勿在您的构建中添加 .aidl 文件。此 .aidl 文件与 C 语言中的头文件类似,并未经过编译。 -
AIDL 会在其生成的代码中使用这些方法和字段,以对您的对象进行编组和解编。
1、定义车辆实体类实现Parcelable接口
根据场景描述,我们需要定义一个类Car,来存放车辆的信息并实现Parcelable接口,这样做的目的就是为了能够序列化与反序列化。Car类如下所示:
public class Car implements Parcelable {
//定义车的属性(品牌、颜色、价格)
private String brand;
private String color;
private int price;
public Car(Parcel in) {
//一开始忘记写这句话,sendData后导致服务端接收的车辆信息为空
readFromParcel(in);
}
public Car(){}
public static final Creator<Car> CREATOR = new Creator<Car>() {
@Override
public Car createFromParcel(Parcel in) {
return new Car(in);
}
@Override
public Car[] newArray(int size) {
return new Car[size];
}
};
@Override
public int describeContents() {
return 0;
}
// 客户端或服务端序列化时使用
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeString(brand);
out.writeString(color);
out.writeInt(price);
}
//客户端或服务端反序列化使用
public void readFromParcel(Parcel in) {
brand=in.readString();
color=in.readString();
price=in.readInt();
}
public String getBrand() {
return brand;
}
public String getColor() {
return color;
}
public int getPrice() {
return price;
}
public void setBrand(String brand) {
this.brand = brand;
}
public void setColor(String color) {
this.color = color;
}
public void setPrice(int price) {
this.price = price;
}
@Override
public String toString() {
return "Car{" +
"brand='" + brand + '\'' +
", color='" + color + '\'' +
", price=" + price +
'}';
}
}
2、创建Car.aidl文件
ICar.aidl 文件的作用是将 Car 类引入使得其他的 AIDL 文件其中可以使用 Car 对象。
// Car.aidl
package com.cop.ronghw.study_android_exact;
//声明Car,这样AIDL就可以找到它并知道它实现了parcelable协议。
parcelable Car;
3、创建ICarInterface.aidl文件
定义接口方法的aidl文件,并在里面使用import
关键字引入Car类对象
// ICarInterface.aidl
package com.cop.ronghw.study_android_exact;
// Declare any non-default types here with import statements
import com.cop.ronghw.study_android_exact.Car;
interface ICarInterface {
//发送车辆信息给服务端
void sendData(in Car car);
//从服务端获取所有的车辆信息
List<Car> getCars();
}
4、编写服务端代码
上一步编写完aidl文件后,通过项目构建生成ICarInterface.java文件。接下来编写服务端代码,服务端代码与本文的第一个案例大同小异,基本处理逻辑是一致的,下面贴代码:
public class MyAidlCarBinderService extends Service {
private static final String TAG = MyAidlCarBinderService.class.getSimpleName();
private static List<Car> cars ;
public MyAidlCarBinderService() {
}
private final ICarInterface.Stub sBinder = new ICarInterface.Stub() {
@Override
public void sendData(Car car) throws RemoteException {
Log.i(TAG,"本次客户端存车=="+ car.toString());
if (cars == null){
cars = new ArrayList<>();
}
if (car != null) {
String brand = car.getBrand();
cars.add(car);
}
}
@Override
public List<Car> getCars() throws RemoteException {
Log.i(TAG,"客户端调用获取车辆信息列表,当前库存有"+cars.size()
+"车,车辆信息列表如下"+cars.toString() +
"当前进程为"+ ProcessUtil.getCurrentProcessName(MyAidlCarBinderService.this));
return cars;
}
};
@Override
public IBinder onBind(Intent intent) {
return sBinder;
}
}
5、编写客户端代码
代码写到了第一个案例的代码文件中了,下面贴一下代码案例
public class MyAidlBinderActivity extends AppCompatActivity {
//服务端进程1
private IMyAidlInterface iMyAidlInterface;
//服务端进程2
private ICarInterface iCarInterface;
private static final String TAG = MyAidlBinderActivity.class.getSimpleName();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_aidl_binder);
Button bindService = (Button)findViewById(R.id.aidlBindService);
bindService.setOnClickListener(binderService);
Button getFile = (Button)findViewById(R.id.aidl_getFile);
getFile.setOnClickListener(c_getFile);
Button sendData = (Button)findViewById(R.id.aidl_sendData);
sendData.setOnClickListener(c_sendData);
//测试通过IPC传递对象
Button sendCar = (Button)findViewById(R.id.aidl_Car_sendData);
sendCar.setOnClickListener(c_car_send);
Button getCar = (Button)findViewById(R.id.aidl_Car_getCars);
getCar.setOnClickListener(C_getCars);
}
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
/**
* //当与服务建立连接时调用,为我们提供了可用于与服务交互的服务对象。
* // 我们通过AIDL接口与服务通信,因此从原始服务对象获得该接口的客户端表示。
* //
*/
iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
iCarInterface = ICarInterface.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
/**
* 这里创建一个点击事件才触发绑定服务:
* 与服务建立一对连接,通过接口名绑定。这允许通过实现相同的接口来安装替代远程服务的其他应用程序。
* 同时会开启两个进程
*/
private View.OnClickListener binderService = new View.OnClickListener(){
@Override
public void onClick(View v) {
Intent intent = new Intent(MyAidlBinderActivity.this, MyAidlBinderService.class);
bindService(intent,mServiceConnection, Context.BIND_AUTO_CREATE);
Intent intentCar = new Intent(MyAidlBinderActivity.this, MyAidlCarBinderService.class);
bindService(intentCar,mServiceConnection, Context.BIND_AUTO_CREATE);
}
};
/**
* 这里创建一个点击事件才触发绑定服务:
* 与服务建立一对连接,通过接口名绑定。这允许通过实现相同的接口来安装替代远程服务的其他应用程序。
*/
private View.OnClickListener c_getFile = new View.OnClickListener(){
@Override
public void onClick(View v) {
if (iMyAidlInterface!=null){
try {
String res = iMyAidlInterface.getFile();
Log.i(TAG,"服务端调用getFile返回的值为 " + res);
}catch (Exception e){
e.printStackTrace();
}
}
}
};
/**
* 这里创建一个点击事件才触发绑定服务:
* 与服务建立一对连接,通过接口名绑定。这允许通过实现相同的接口来安装替代远程服务的其他应用程序。
*/
private View.OnClickListener c_sendData = new View.OnClickListener(){
@Override
public void onClick(View v) {
if (iMyAidlInterface!=null){
try {
String res = iMyAidlInterface.sendData("Hello Sever!");
Log.i(TAG,"服务端调用sendData返回的值为 " + res + " 当前进程为"+ ProcessUtil.getCurrentProcessName(MyAidlBinderActivity.this)) ;
}catch (Exception e){
e.printStackTrace();
}
}
}
};
/**
* 这里创建一个点击事件才触发绑定服务:
* 与服务建立一对连接,通过接口名绑定。这允许通过实现相同的接口来安装替代远程服务的其他应用程序。
* 存车
*/
private View.OnClickListener c_car_send = new View.OnClickListener(){
@Override
public void onClick(View v) {
if (iCarInterface!=null){
try {
Car car = new Car();
car.setBrand("xfl-tjz");
car.setColor("火山灰");
car.setPrice(200000);
iCarInterface.sendData(car);
Log.i(TAG,"客户端已发送数据"+car.toString()+"当前进程为"+ ProcessUtil.getCurrentProcessName(MyAidlBinderActivity.this)) ;
}catch (Exception e){
e.printStackTrace();
}
}
}
};
/**
* 这里创建一个点击事件才触发绑定服务:
* 与服务建立一对连接,通过接口名绑定。这允许通过实现相同的接口来安装替代远程服务的其他应用程序。
* 取车
*/
private View.OnClickListener C_getCars = new View.OnClickListener(){
@Override
public void onClick(View v) {
if (iCarInterface!=null){
try {
List<Car> res = iCarInterface.getCars();
Log.i(TAG,"服务端调用getCars返回的值为 " + res.size() + res.toString());
}catch (Exception e){
e.printStackTrace();
}
}
}
};
}
6、测试,并日志输出
点击并进行多次测试,日志输出如下:
23-03-23 17:23:37.469 17168-17198/com.cop.ronghw.study_android_exact I/MyAidlCarBinderService: 本次客户端存车==Car{brand='xfl-tjz', color='火山灰', price=200000}
2023-03-23 17:23:37.472 17100-17100/com.cop.ronghw.study_android_exact I/MyAidlBinderActivity: 客户端已发送数据Car{brand='xfl-tjz', color='火山灰', price=200000}当前进程为com.cop.ronghw.study_android_exact
2023-03-23 17:24:14.200 17168-17198/com.cop.ronghw.study_android_exact I/MyAidlCarBinderService: 客户端调用获取车辆信息列表,当前库存有1车,车辆信息列表如下[Car{brand='xfl-tjz', color='火山灰', price=200000}]当前进程为com.cop.ronghw.study_android_exact:carAidl
2023-03-23 17:24:14.201 17100-17100/com.cop.ronghw.study_android_exact I/MyAidlBinderActivity: 服务端调用getCars返回的值为 1[Car{brand='xfl-tjz', color='火山灰', price=200000}]
2023-03-23 17:24:14.202 17100-17100/com.cop.ronghw.study_android_exact I/Choreographer: Skipped 184 frames! The application may be doing too much work on its main thread.
2023-03-23 17:24:21.129 17168-17198/com.cop.ronghw.study_android_exact I/MyAidlCarBinderService: 本次客户端存车==Car{brand='xfl-tjz', color='火山灰', price=200000}
2023-03-23 17:24:21.130 17100-17100/com.cop.ronghw.study_android_exact I/MyAidlBinderActivity: 客户端已发送数据Car{brand='xfl-tjz', color='火山灰', price=200000}当前进程为com.cop.ronghw.study_android_exact
2023-03-23 17:24:23.425 17168-17198/com.cop.ronghw.study_android_exact I/MyAidlCarBinderService: 本次客户端存车==Car{brand='xfl-tjz', color='火山灰', price=200000}
2023-03-23 17:24:23.428 17100-17100/com.cop.ronghw.study_android_exact I/MyAidlBinderActivity: 客户端已发送数据Car{brand='xfl-tjz', color='火山灰', price=200000}当前进程为com.cop.ronghw.study_android_exact
2023-03-23 17:24:24.302 17168-17198/com.cop.ronghw.study_android_exact I/MyAidlCarBinderService: 本次客户端存车==Car{brand='xfl-tjz', color='火山灰', price=200000}
2023-03-23 17:24:24.303 17100-17100/com.cop.ronghw.study_android_exact I/MyAidlBinderActivity: 客户端已发送数据Car{brand='xfl-tjz', color='火山灰', price=200000}当前进程为com.cop.ronghw.study_android_exact
2023-03-23 17:24:24.540 17168-17198/com.cop.ronghw.study_android_exact I/MyAidlCarBinderService: 本次客户端存车==Car{brand='xfl-tjz', color='火山灰', price=200000}
2023-03-23 17:24:24.547 17100-17100/com.cop.ronghw.study_android_exact I/MyAidlBinderActivity: 客户端已发送数据Car{brand='xfl-tjz', color='火山灰', price=200000}当前进程为com.cop.ronghw.study_android_exact
2023-03-23 17:24:25.406 17168-17198/com.cop.ronghw.study_android_exact I/MyAidlCarBinderService: 客户端调用获取车辆信息列表,当前库存有5车,车辆信息列表如下[Car{brand='xfl-tjz', color='火山灰', price=200000}, Car{brand='xfl-tjz', color='火山灰', price=200000}, Car{brand='xfl-tjz', color='火山灰', price=200000}, Car{brand='xfl-tjz', color='火山灰', price=200000}, Car{brand='xfl-tjz', color='火山灰', price=200000}]当前进程为com.cop.ronghw.study_android_exact:carAidl
2023-03-23 17:24:25.408 17100-17100/com.cop.ronghw.study_android_exact I/MyAidlBinderActivity: 服务端调用getCars返回的值为 5[Car{brand='xfl-tjz', color='火山灰', price=200000}, Car{brand='xfl-tjz', color='火山灰', price=200000}, Car{brand='xfl-tjz', color='火山灰', price=200000}, Car{brand='xfl-tjz', color='火山灰', price=200000}, Car{brand='xfl-tjz', color='火山灰', price=200000}]
总结
至此,通过IPC传递对象的案例就演示完成了,在尝试的过程中需要注意的问题:
- 创建Car类及Car.aidl文件时,要保证二者的路径一致。
- Car.aidl 及Car.java二者的名字必须一致,即不能出现Car.java和Car1.aidl配对方式的命名,否则会出现Import 时找不到Car类的错误。
本篇总结
本篇文章主要通过案例代码的形式来模拟客户端和服务端的通信过程。到目前为止,大家看了几篇文章可能基本上了解了如何编写一个aidl文件,并完成客户端与服务端的通信,但是内部的binder原理我们并没有介绍,只是知道都是binder驱动在起决定性的作用。那么binder的分层是如何的?在java中如何是调用到native层实现的客户端与服务端的通信的?服务端又是如何将信息返回给客户端的?在下一篇文章中,我们会通过源码的形式来为大家解读,让大家争取搞清楚。