一、简介
这将是一个围绕AIDL的系列文章,内容含AIDL简单使用、进阶使用、AIDL源码探索,希望从简单开始再到复杂,在这个过程中使大家既能掌握AIDL的使用方法和需要注意的细节,同时也能通过对AIDL源码的探索,使各位理解AIDL的处理原理。这篇文章为第一篇,主要简述AIDL的简单使用和使用细节。
- Android IPC之AIDL使用(一)
- Android IPC之AIDL使用(二)
- Android IPC之AIDL源码探索(三) 文章词汇解释:
- AS:AndroidStudio开发工具
- AIDL文件:指在aidl目录下创建的aidl接口
- AIDL类:指由aidl文件Build生成的类
二、AIDL是什么?
Android系统是基于Linux内核实现的,而在Linux系统中每个进程的内存都是相互隔离的,所以需要一种手段来满足不同进程之间的数据传输,这就是常说的跨进程通讯IPC(Inter-Process Communication)。在Android系统中IPC是采用Binder实现的,至于Google为什么会选择Binder来做为Android系统的IPC,这里不做研究,网上有很多文章,我们将重点研究 Android Interface definition language,简称AIDL,而AIDL就是一种对Binder的应用封装。
三、AIDL能干什么?
当我们想在后台做耗时操作或需要占用内存较大时,通常会启动一个服务,并指定为新的进程,或者调用别的应用服务。这个时候便存在跨进程,那进程间通讯AIDL就该出场了。
四、使用
1.定义AIDL接口
在项目包名上右击展开菜单,选中AIDL项,并输入文件名,详细见下图。
AS将会在main目录下创建一个同包名的文件目录,并生成一个AIDL文件,详细见下图。
注:图中的AIDL文件内容不是默认样式,这是例子中添加的一些函数。
当文件创建之后,就可以删除默认函数,根据业务编写代码;不过有几点需要注意:
- 方法参数只支持Java基本数据类型、String、CharSequence、List(存储对象同样需遵守)、Map(存储对象同样需遵守)、实现Parcelable的类、AIDL类;
- 参数为对象时(AIDL类除外),需要添加修饰前缀,有下面三种:
- in 会把数据对象传递过去,但是在服务端的修改是不会影响到客户端;
- out 不会把数据对象传递过去,但是在服务端的修改会被同步到客户端;
- inout 是上两种的结合,既能传递数据对象到服务端,又能使服务端的修改同步到客户端;
- 对象类需要实现Parcelable进行序列化,同时在aidl同目录下创建AIDL文件,并在文件中注明它为Parcelable,见下图;
4. 实现Parcelable进行序列化的类,当有作为参数修饰前缀为out时,记得实现默认构造函数;作为参数修饰前缀为out/inout时,还得实现readFromParcel(Parcel parcel)函数,这些都是因为AIDL类源码中会需要通过这些,进行对象的创建或内容替换,将在Android IPC之AIDL源码探索(三)中详细讲解,先简单看一个示例类。
public class MethodObject implements Parcelable {
private String name;
public MethodObject(){//有作为参数修饰前缀为out,所以需要实现默认构造函数
}
protected MethodObject(Parcel in) {
name = in.readString();
}
public static final Creator<MethodObject> CREATOR = new Creator<MethodObject>() {
@Override
public MethodObject createFromParcel(Parcel in) {
return new MethodObject(in);
}
@Override
public MethodObject[] newArray(int size) {
return new MethodObject[size];
}
};
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
}
public void readFromParcel(Parcel parcel){//有作为参数修饰前缀为out/inout,所以需要实现这个方法。
this.name = parcel.readString();
}
}
定义完成后,进行Make Build构建,AS会在Build目录下生成最终的AIDL类,详细见下图。
这个时候真正要使用的类就生成了,另外需要重点说明一下,如果客户端和服务端是在同一个项目中,AIDL接口只需要定义一次,如果客服端和服务端是不同的项目,那AIDL接口就需要分别定义同样的一份,并且要格外注意客户端和服务端包名目录要一模一样。下面是本例子的详细结构图。
客户端:
服务端:
前期AIDL类都生成之后,就可以使用了。
2.在Service中的使用
1.客户端与服务端通信
这里将讲述从服务端创建到客服端调用的完整过程,首先服务端创建一个类继承Service类,在onBind()方法中返回上面通过AS构建的AIDL类中MyAIDLInterface.Stub对象,Stub类继承了Binder类,Binder类实现了IBinder接口。
public class MyService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return stub;
}
MyAIDLInterface.Stub stub = new MyAIDLInterface.Stub() {...};
}
客户端绑定Service。
public class MainActivity extends AppCompatActivity {
private MyAIDLInterface myAIDLInterface = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bind();
}
private void bind() {
Intent intent = new Intent();
intent.setAction("com.zhukai.aidlservice.startService");
intent.setComponent(new ComponentName("com.zhukai.aidlservice","com.zhukai.aidlservice.MyService"));
bindService(intent, new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
myAIDLInterface = MyAIDLInterface.Stub.asInterface(service);//返回的是另一个进程Service中传入的MyAIDLInterface.Stub类的代理对象
try {
if (null != myAIDLInterface){
myAIDLInterface.commonMethod();//后续就可以任意调用AIDL接口中定义的函数了
myAIDLInterface.setStringText("test");
...
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
myAIDLInterface = null;
}
}, Service.BIND_AUTO_CREATE);
}
}
核心在ServiceConnection接口的onServiceConnected()回调函数中,首先通过Stub.asInterface()函数得到客户端操作对象myAIDLInterface,然后通过它就能调用服务端函数。
2.服务端与客服端通信
看到这里可能有个疑惑,客户端可以跟服务端通信,那如果业务需求需要服务端,不定时回调给客户端;也就是服务端如何能主动跟客户端通信呢?其实这个问题很好解决,前面有说到,在AIDL定义规则中参数是支持AIDL类的,通过从客户端注册一个AIDL类对象到服务端,服务端再通过这个AIDL类对象主动与客户端通信便能实现。
首先也需要按上面讲述流程,创建一个AIDL文件。
进行Make Build构建,生成AIDL类。
客户端调用注册函数,把AIDL类对象传入到服务端。
private void bind() {
//因为是不同的应用,所以这里只能采用隐式绑定。
Intent intent = new Intent();
intent.setAction("com.zhukai.aidlservice.startService");
intent.setComponent(new ComponentName("com.zhukai.aidlservice","com.zhukai.aidlservice.MyService"));
bindService(intent, new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
myAIDLInterface = MyAIDLInterface.Stub.asInterface(service);
try {
if (null != myAIDLInterface){
myAIDLInterface.commonMethod();
myAIDLInterface.setStringText("test");
myAIDLInterface.register(callBackAIDLInterface);//进行注册
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
myAIDLInterface = null;
}
}, Service.BIND_AUTO_CREATE);
}
//创建用于服务端回调的AIDL接口对象
CallBackAIDLInterface callBackAIDLInterface = new CallBackAIDLInterface.Stub() {
@Override
public void callBack() throws RemoteException {
}
};
看服务端的处理。
public class MyService extends Service {
private static final String TAG = MyService.class.getSimpleName();
private List<CallBackAIDLInterface> interfaces = new ArrayList<>();
@Nullable
@Override
public IBinder onBind(Intent intent) {
return stub;
}
MyAIDLInterface.Stub stub = new MyAIDLInterface.Stub() {
... //无关内容隐藏
@Override
public void register(CallBackAIDLInterface aidl) throws RemoteException {
if (!interfaces.contains(aidl)) {
interfaces.add(aidl);
Log.d(TAG, "register: 注销成功");
}
}
@Override
public void unregister(CallBackAIDLInterface aidl) throws RemoteException {
if (interfaces.contains(aidl)) {
interfaces.remove(aidl);
Log.d(TAG, "unregister: 注销成功");
} else {
Log.d(TAG, "unregister: 注销失败");
}
}
};
}
因为存在多个客户端同时与服务端通信可能,服务端直接在interfaces中存储了各个aidl类对象,通过这些对象便可以主动与客户端进行通信了。
五、总结
此篇希望能让大家在AIDL的使用以及一些注意事项上有所帮助,这篇文章只是关于AIDL的简单使用,AIDL其实也有一些进阶使用,欢迎关注点赞,继续阅读AIDL系列文章,后续将努力学习输出更高质量文章。