本博文其实是Android 深入浅出AIDL(一)的转载版,但添加了自己的思考
Android IPC机制之 - AIDL
A[Android]
I[Interface]
D[Definition]
L[Language]
Android接口定义语言
它的作用是,方便系统为我们生成代码从而实现跨进程通讯,仅此而已。也就是说这个AIDL就是个可以快速实现跨进程通讯的工具
1.1 快速实现
- 构造MessageBean作为传输数据的格式,构造MessageBean.aidl用于声明MessageBean,构造IDemandManager.aidl它是一个接口,规定了客户端与服务端数据传输的接口方法【声明方法】
- 在服务端创建Service,并实现IDemandManager接口【创建服务】
- 客户端绑定Service【绑定服务】
- 客户端获得服务端IDemandManager接口实例,通过它调用服务端接口方法
1.2 实例目录
在TestAIDL中创建两个可以单独运行的实例
其中aidlClient是客户端
aidlService是服务端服务端
客户端
1.3 运行效果
同时运行客户端和服务端,点击客户端上的绑定Service按钮时打印日志:
E/MainActivity: connection: 【(内容==首先,看到我要敬礼),(级别==1)】
1.4 服务端
在main目录下创建aidl文件存放目录
创建了aidl文件还需要在build.gradle文件中android{}方法内添加aidl路径
sourceSets {
main {
java.srcDirs = ['src/main/java', 'src/main/aidl']
}
}
- 创建MessageBean.java实现了Parcelable接口,它是客户端与服务端通讯的数据格式
public class MessageBean implements Parcelable {
public MessageBean() {
}
private String content;//需求内容
private int level;//重要等级
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public int getLevel() {
return level;
}
public void setLevel(int level) {
this.level = level;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.content);
dest.writeInt(this.level);
}
public void readFromParcel(Parcel dest) {
//注意,此处的读值顺序应当是和writeToParcel()方法中一致的
this.content = dest.readString();
this.level = dest.readInt();
}
public MessageBean(String content, int level) {
this.content = content;
this.level = level;
}
protected MessageBean(Parcel in) {
this.content = in.readString();
this.level = in.readInt();
}
public static final Creator<MessageBean> CREATOR = new Creator<MessageBean>() {
@Override
public MessageBean createFromParcel(Parcel source) {
return new MessageBean(source);
}
@Override
public MessageBean[] newArray(int size) {
return new MessageBean[size];
}
};
@Override
public String toString() {
return "【(内容==" + content + "),(级别==" + level + ")】";
}
}
- 创建MessageBean.aidl,它用于声明MessageBean.java
因为AIDL语言的规范就是aidl文件,所以我们必须将MessageBean转为aidl文件,供其它aidl的调用与交互。
package com.example.cxy.testbinder;
parcelable MessageBean; // parcelable是小写
- 创建IDemandManager.aidl,它是一个接口,规定了客户端与服务端数据传输的接口方法
package com.example.cxy.testbinder;
import com.example.cxy.testbinder.MessageBean;
interface IDemandManager {
MessageBean getDemand();
void setDemandIn(in MessageBean msg); // 客户端->服务端
void setDemandOut(out MessageBean msg); // 服务端->客户端
void setDemandInOut(inout MessageBean msg); // 客户端<->服务端
}
AIDL文件与java文件的区别:
- AIDL文件的后缀是 .aidl,java文件的后缀是 .java。
- 自定义的Parcelable对象和AIDL对象必须要显式import进来,就算目标文件与当前正在编写的 .aidl 文件在同一个包下,在 Java 中,这种情况是不需要导包的。
AIDL支持的数据类型:
- Java中的八种基本数据类型:byte,short,int,long,float,double,boolean,char
- String和CharSequence
- List:只支持ArrayList,里面每个元素都必须能够被AIDL支持
- Map:只支持HashMap,里面的每个元素都必须被AIDL支持,包括key和value
- Parcelable:所有实现了Parcelable接口的对象
- AIDL:所有的AIDL接口本身也可以在AIDL文件中使用
我们创建IDemandManager接口用来实现传递方法,另外方法内如果有传输载体,就必须指明定向tag(in,out,inout)
- in : 客户端数据对象流向服务端。服务端对该数据对象的修改不会影响到客户端。
- out : 数据对象由服务端流向客户端。(客户端传递的数据对象此时服务端收到的对象内容为空,服务端可以对该数据对象修改,并传给客户端)
- inout : 以上两种数据流向的结合体。(但是不建议用此tag,会增加开销)
注意
- xxx.aidl文件中不能存在同方法名不同参数的方法。
- xxx.aidl文件中方法内如果有传输载体,就必须指明定向tag(in,out,inout)
- 必须在build.gradle文件中android{}方法内指明java和aidl文件路径
sourceSets { main { java.srcDirs = ['src/main/java', 'src/main/aidl'] } }
创建AIDLService,它继承了Service,通过new IDemandManager.Stub(){}实现了IDemandManager接口,作为向客户端委托的Binder实例
public class AIDLService extends Service {
private final String TAG = AIDLService.class.getSimpleName();
@Nullable
@Override
public IBinder onBind(Intent intent) {
return demandManager;
}
// Stub内部继承Binder,具有跨进程传输能力
IDemandManager.Stub demandManager = new IDemandManager.Stub() {
@Override
public MessageBean getDemand() throws RemoteException {
MessageBean demand = new MessageBean("首先,看到我要敬礼",1);
return demand;
}
@Override
public void setDemandIn(MessageBean msg) throws RemoteException { // 客户端数据流向服务端
Log.e(TAG,"程序员:"+msg.toString());
}
@Override
public void setDemandOut(MessageBean msg) throws RemoteException { // 服务端数据流向客户端
Log.e(TAG,"程序员:"+msg.toString()); // msg内容一定为空
msg.setContent("我不想听解释,下班前把所有工作都搞好!");
msg.setLevel(5);
}
@Override
public void setDemandInOut(MessageBean msg) throws RemoteException {
Log.e(TAG,"程序员:"+msg.toString());
msg.setContent("把用户交互颜色都改成粉色");
msg.setLevel(3);
}
};
}
注意IBinder onBind(Intent intent){}
一定要返回向客户端委托的Binder实例
- 最后我们在AndroidManifest.xml文件中注册服务
<service
android:name=".AIDLService"
android:exported="true">
<intent-filter>
<action android:name="com.example.cxy.testbinder.AIDLService" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</service>
设置了action和category过滤条件用于隐式绑定服务
在魅族的手机上,系统禁止了隐式方法启动服务的权限,所以务必在手机管家/权限管理/ 中,开启该项目的自启权限。
1.5 客户端
- 拷贝服务端整个aidl文件夹
将服务端创建的aidl文件夹拷贝至客户端app/src/main 目录下,并且在gradle.build中关联aidl路径。
sourceSets {
main {
java.srcDirs = ['src/main/java', 'src/main/aidl']
}
}
- 客户端绑定Service
public void BindService(View view) {
Intent intent = new Intent();
intent.setAction("com.example.cxy.testbinder.AIDLService");
intent.setPackage("com.example.cxy.testbinder"); // aidl文件夹里面aidl文件的包名
bindService(intent,connection, Context.BIND_AUTO_CREATE);
}
- 关联对象,获得*.aidl接口实例,通过它调用服务端接口方法
服务绑定成功之后,我们通过IDemandManager.Stub.asInterface()方法,将IBinder接口数据转化为我们客户端需要的AIDL接口类型对象。
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
iDemandManager = IDemandManager.Stub.asInterface(service); // 得到该对象后,我们就可以用来进行进程间的方法调用和传输了
try {
Log.e(TAG, String.format("connection: %s", iDemandManager.getDemand().toString()));
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
至此我们就可以通过AIDL来进行跨进程通讯了。