学习IPC机制离不开对IBinder的了解,好久没有用过AIDL了,本篇是记录一下,免得自己忘记。
场景
1.aidl_service 提供简单计算服务
2.aidl_client 请求计算服务,并显示结果
AIDL文档
Google文档如下:
1.新建aidl文件
2.实现aidl定义的接口
3.将定义好的接口文件给客户端
新建AIDL文件
aidl文件格式最基本的定义接口,写法和java基本一样,如下:
// Query.aidl
package my.service.calc;
// Declare any non-default types here with import statements
interface Query {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
}
aidl语言支持的数据类型,Google文档如下:
支持:
1.基本java的数据类型
2.String, Charsequence, List, Map
3.支持实现了Parcelable接口的对象,必须添加对象的aidl定义, 如下:
// Expression.aidl
package my.service.calc;
parcelable Expression;
在相应的包下定义正确的Java类,如下:
package my.service.calc;
/**
* 代表要计算的表达式
*/
public class Expression implements Parcelable {
...
}
使用android studio2.2开发的,新建工程MyAidl_server
然后新建AIDL文件,如下:
写上ICalcService,然后就会新建出AIDL文件,修改接口定义,提供一个设置表达式Expression的方法,一个计算并且返回结果的方法,这里有个知识点, 如下:
AIDL所有的非基本参数都需要一个定向tag来指出数据流通的方式,否则编译不过
in 代表这个参数的数据是由客户端流向给服务端
out 代表这个参数的数据是由服务端流向给客户端
inout 代表双向
后面会结合AIDL生成的Java代表分析,in,out,inout到底具体怎样体现的
我们生成基本的文件,ICalcService.aidl,如下:
// ICalcService.aidl
package my.service.calc;
// Declare any non-default types here with import statements
import my.service.calc.Expression;
import my.service.calc.Result;
interface ICalcService {
void set(in Expression exp);//输入表达式
void calc(out Result result);//输出计算结果
void test(inout Result result);//测试inout的空方法
}
// Expression.aidl
package my.service.calc;
// 代表表达式,包含计算符号,左右2边int数据
parcelable Expression;
// Result.aidl
package my.service.calc;
// 代表最近一次表达式的计算结果
parcelable Result;
然后实现对应的Java类:
表达式类
public class Expression implements Parcelable {
public static final int TYPE_ADD = 1;
public static final int TYPE_SUB = 2;
public int left;
public int right;
public int opt;
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(this.left);
dest.writeInt(this.right);
dest.writeInt(this.opt);
}
public Expression() {
}
protected Expression(Parcel in) {
this.left = in.readInt();
this.right = in.readInt();
this.opt = in.readInt();
}
public static final Parcelable.Creator<Expression> CREATOR = new Parcelable.Creator<Expression>() {
@Override
public Expression createFromParcel(Parcel source) {
return new Expression(source);
}
@Override
public Expression[] newArray(int size) {
return new Expression[size];
}
};
}
计算结果类
public class Result implements Parcelable {
private boolean vaild;
private int result;
public void set(int result) {
this.result = result;
this.vaild = true;
}
@Override
public String toString() {
return String.format("%d [%s]", result, vaild ? "有效" : "无效");
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeByte(this.vaild ? (byte) 1 : (byte) 0);
dest.writeInt(this.result);
}
public void readFromParcel(Parcel dest) {
this.vaild = dest.readByte() != 0;
this.result = dest.readInt();
}
public Result() {
}
protected Result(Parcel in) {
this.vaild = in.readByte() != 0;
this.result = in.readInt();
}
public static final Parcelable.Creator<Result> CREATOR = new Parcelable.Creator<Result>() {
@Override
public Result createFromParcel(Parcel source) {
return new Result(source);
}
@Override
public Result[] newArray(int size) {
return new Result[size];
}
};
}
IDE帮我们通过AIDL文件,生成了 ICalcService.java文件,格式化后如下:
package my.service.calc;
public interface ICalcService extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
我们自己的服务必须实现这个Stub class类以提供服务,它是继承的Binder
*/
public static abstract class Stub extends android.os.Binder implements ICalcService {
private static final String DESCRIPTOR = "my.service.calc.ICalcService";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* 这里转换为正真的ICalcService接口
*/
public static ICalcService asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
//如果是进程内通信的话,我们应该是直接拥有这个服务对象的,可以直接返回使用
if (((iin != null) && (iin instanceof ICalcService))) {
return ((ICalcService) iin);
}
//如果是2个进程之间相互通信,我们只能拥有remote对象的IBinder句柄,然后通过IBinder机制去交互
return new Proxy(obj);
}
@Override
public android.os.IBinder asBinder() {
return this;
}
/*
* 这里是真正交互的地方,code代表需要调用那个方法,在文件最后定义了代表相关方法的int值,data是输入参数,reply是输出参数,flags代表的是0代表一次普通的RPC,1代表one_way RPC */
@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;
}
//第1个方法是in
case TRANSACTION_set: {
data.enforceInterface(DESCRIPTOR);
Expression _arg0;
//检查参数,如果有将会从客户端来的数据中读取一个输入参数对象
if ((0 != data.readInt())) {
_arg0 = Expression.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
//服务调用处理
this.set(_arg0);
reply.writeNoException();
//服务端不会有任何回写操作
return true;
}
//第2个方法是out
case TRANSACTION_calc: {
data.enforceInterface(DESCRIPTOR);
//这里明显区别,不从客户端读取参数,直接new一个新的参数对象
Result _arg0;
_arg0 = new Result();
//服务调用处理
this.calc(_arg0);
reply.writeNoException();
//将结果回写给客户端,数据流向了客户端
if ((_arg0 != null)) {
reply.writeInt(1);
_arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
} else {
reply.writeInt(0);
}
return true;
}
//第3个方法inout,按照上面分析应该是 1,2情况的合并
case TRANSACTION_test: {
data.enforceInterface(DESCRIPTOR);
///检查参数,如果有将会从客户端来的数据中读取一个输入参数对象
Result _arg0;
if ((0 != data.readInt())) {
_arg0 = Result.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
//服务调用处理
this.test(_arg0);
reply.writeNoException();
//将结果回写给客户端,数据流向了客户端
if ((_arg0 != null)) {
reply.writeInt(1);
_arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
} else {
reply.writeInt(0);
}
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
//代理实现,主要还是调用远程的remote对象,代码自己查看本地的
private static class Proxy implements ICalcService {
...
}
static final int TRANSACTION_set = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_calc = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
static final int TRANSACTION_test = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
}
public void set(Expression exp) throws android.os.RemoteException;
public void calc(Result result) throws android.os.RemoteException;
public void test(Result result) throws android.os.RemoteException;
}
我在代码注释了一些,包括in out inout不同,体现在onTransact函数中对参数的不同处理方式,以及结果的处理。
我们只要服务端继承ICalcService.Stub进行抽血方法实现,然后通过service的onBind接口返回,就完成了服务端的编写,如下:
@Nullable
@Override
public IBinder onBind(Intent intent) {
return new CalcBinder();
}
private class CalcBinder extends ICalcService.Stub {
private Expression mExp;
@Override
public void set(Expression exp) throws RemoteException {
mExp = exp;
}
@Override
public void calc(Result result) throws RemoteException {
//计算结果设置给result对象
if (mExp != null) {
result.set(ExpressionFactory.create(mExp).getValue());
}
}
@Override
public void test(Result result) throws RemoteException {
//empty
}
}
这里有个坑,如果有out标记的参数对象,编译时候会报错,必须给参数对象类添加readFromParcel(Parcel dest)以读取来自服务端的结果,如下:
public void readFromParcel(Parcel dest) {
this.vaild = dest.readByte() != 0;
this.result = dest.readInt();
}
以上服务端就完成了。
客户端工程只要将服务端的AIDL文件,Expression.java, Result.java拷贝过去,就可以了,生成同样的文件,然后bindService即可链接计算服务,如下:
绑定服务->请求求和->请求计算结果,完成上面set calc方法的调用。
小记: onServiceDisconnected只会在服务进程被意外杀死才会调用,unbindservice并不会回调这个方法
对于进程间通信来说,具体的流程就分为如下几步:
1.Client 发起远程调用请求 也就是RPC 到Binder。同时将自己挂起,挂起的原因是要等待RPC调用结束以后返回的结果
2.Binder 收到RPC请求以后 把参数收集一下,调用transact方法,把RPC请求转发给service端。
3.service端 收到rpc请求以后 就去线程池里 找一个空闲的线程去走service端的 onTransact方法 ,实际上也就是真正在运行service端的 方法了,等方法运行结束 就把结果 写回到binder中。
4.Binder 收到返回数据以后 就唤醒原来的Client 线程,返回结果。至此,一次进程间通信 的过程就结束了
关于IBinder线程问题:
1.客户端请求的时候,会被挂起,等待直到结果返回,所以耗时的请求通信过程不能放在主线程里面
2.经测试服务端方法是binder开启的线程调用的,每次调用线程不一样,所以服务端多个客户端的情况下,要考虑并发
参考大神们博客:
Android 手写Binder 教你理解android中的进程间通信
你真的理解Android AIDL中的in,out,inout么
Android aidl Binder框架浅析