AndroidIPC-AIDL
一、概述
AIDL意思即Android Interface Definition Language,翻译过来就是android接口定义语言,是用于定义服务器和客户端通信接口的一种描述语言,可以拿来生成用于IPC的代码。从某种意义上说AIDL其实是一个模板,因为在使用过程中,实际起作用的并不是AIDL文件,而是据此生成的一个Interface的实例代码,AIDL其实是为了避免我们重复编写代码而出现的一个模板。
通过AIDL,可以在一个进程中获取另一个进程的数据和调用其暴露出来的方法,从而满足进程间通信的需求。通常,暴露方法给其他应用进行调用的应用称为服务端,调用其他应用的方法的应用称为客户端,客户端通过绑定服务端的Service来进行交互。
二、语法
AIDL的语法十分简单,与Java语言基本保持一致,需要记住的规则有以下几点:
1.AIDL文件以.aidl为后缀名
2.AIDL支持的数据类型分为以下几种
- 八种基本数据类型
- String,CharSequence
- 实现了Parcelable接口的数据类型
- List类型,List承载的数据必须是AIDL支持的类型,或者是其他生命的AIDL对象
- Map类型,Map承载的数据必须是AIDL支持的类型,或者是其他生命的AIDL对象
3.AIDL文件可以分为两类。一类用来声明实现了Parcelable接口的数据类型,以供其他AIDL文件使用那些非默认支持的数据类型。还有一类用来定义接口方法,声明要暴露哪写接口给客户端调用,定向Tag用来标注这些方法的参数
4.定向Tag。定向Tag表示在跨进程通信中数据的流向,用于标注方法的参数值,分别为in,out,inout三种。其中in表示数据只能从客户端流向服务端,out表示数据只能由服务端流向客户端,而inout则表示数据可以在服务端口和客户端之间双向流通。此外,如果AIDL方法接口的参数值类型是基本数据类型,那么这些参数的定向Tag默认且只能是in
5.明确导包。在AIDL文件中需要明确标明引用到的数据类型所在的包名,即使两个文件处在同一个包下。
三、服务端编码
新建一个工程,包名为com.example.aidlserver
首先,在应用中需要用到一个Student类,而Student类是两个应用间都需要用到的,所以也需要在AIDL中声明Student类,为了避免出现类名重复导致无法创建文件的错误,这里需要先建立StudentAidl文件,之后再创建Student类
右键点击新建一个AIDL文件,命名为Student。创建完成之后,系统会默认创建一个aidl文件夹
可以删掉文件中自动生成的接口和方法,因为这个aidl文件主要是为了声明Student类
接下来定义Student类,并且实现Parcelable接口
public class Student implements Parcelable {
private String name;
private int age;
private boolean isAdult;
public Student(){}
public Student(String name, int age, boolean isAdult) {
this.name = name;
this.age = age;
this.isAdult = isAdult;
}
protected Student(Parcel in) {
name = in.readString();
age = in.readInt();
isAdult = in.readByte() != 0;
}
public static final Creator<Student> CREATOR = new Creator<Student>() {
@Override
public Student createFromParcel(Parcel in) {
return new Student(in);
}
@Override
public Student[] newArray(int size) {
return new Student[size];
}
};
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public boolean isAdult() {
return isAdult;
}
public void setAdult(boolean adult) {
isAdult = adult;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeInt(age);
dest.writeByte((byte) (isAdult ? 1 : 0));
}
public void readFromParcel(Parcel parcel){
name = parcel.readString();
age = parcel.readInt();
isAdult = parcel.readByte()==1;
}
}
然后,定义接口aidl文件,我们提供四个方法
interface IMyAidlInterface {
String getName();
void sendStudent(inout Student student);
void sendStudentIn(in Student student);
void sendStudentOut(out Student student);
}
第一个方法用于向客户端返回数据
第二,三,四个方法用于客户端向服务端提交数据,分别验证三个定向Tag的区别
之前说过,在进程间通信中真正起作用的并不是AIDL文件,而是系统据此而生成的文件,可以在以下目录查看系统生成的文件。
创建或者修改AIDL文件后,需要Make Project,这样系统才能生成我们需要的文件
接下来需要创建一个Service供客户端远程绑定了
public class MyService extends Service {
private IMyAidlInterface iMyAidlInterface = new IMyAidlInterface.Stub() {
@Override
public String getName() throws RemoteException {
return "test";
}
@Override
public void sendStudent(Student student) throws RemoteException {
Log.d("InOut", "name = " +student.getName());
Log.d("InOut", "age = "+student.getAge());
Log.d("InOut", "isAdult = " +student.isAdult());
student.setName("服务端修改了student的name InOut");
}
@Override
public void sendStudentIn(Student student) throws RemoteException {
Log.d("In", "name = " +student.getName());
Log.d("In", "age = "+student.getAge());
Log.d("In", "isAdult = " +student.isAdult());
student.setName("服务端修改了student的name In");
}
@Override
public void sendStudentOut(Student student) throws RemoteException {
Log.d("Out", "name = " +student.getName());
Log.d("Out", "age = "+student.getAge());
Log.d("Out", "isAdult = " +student.isAdult());
student.setName("服务端修改了student的name Out");
}
};
public MyService() {
}
@Override
public IBinder onBind(Intent intent) {
return iMyAidlInterface.asBinder();
}
}
可以看到,我们声明了一个IMyAidlInterface的实例,并重写了它的四个方法。
在onBind方法中我们返回的是iMyAidlInterface.asBinder();
最后,服务端还有一个地方需要注意
因为服务端的Service需要被客户端远程绑定,所以客户端要能找到这个Service,可以通过先指定包名,之后再配置action的值来找到Service,需要在manifest文件中修改Service的注册。
<service>
android:name=".MyService"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="serverService"/>
</intent-filter>
</service>
四、客户端编码
客户端需要再新建一个工程,包名为com.example.aidlclient
将服务端中的AIDL文件以及Student类复制过来,将aidl文件夹整个复制到和java文件夹同一层级下,不需要改动任何代码
AIDLServer
AIDLClient
修改布局文件,添加三个按钮
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="vertical">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="InOut"
android:id="@+id/inout"
android:onClick="onClick"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="In"
android:id="@+id/in"
android:onClick="onClick"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Out"
android:id="@+id/out"
android:onClick="onClick"/>
</LinearLayout>
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private IMyAidlInterface iMyAidlInterface;
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent();
intent.setAction("serverService");
intent.setPackage("com.example.aidlserver");
bindService(intent,connection,BIND_AUTO_CREATE);
}
public void onClick(View view) {
switch(view.getId()){
case R.id.inout:
Student student = new Student("inout student",20,true);
try {
iMyAidlInterface.sendStudent(student);
Log.d(TAG, "新名字 = "+student.getName());
Toast.makeText(this, iMyAidlInterface.getName(), Toast.LENGTH_SHORT).show();
} catch (RemoteException e) {
e.printStackTrace();
}
break;
case R.id.out:
Student studentOut = new Student("out student",20,true);
try {
iMyAidlInterface.sendStudentOut(studentOut);
Log.d(TAG, "新名字 = "+studentOut.getName());
} catch (RemoteException e) {
e.printStackTrace();
}
break;
case R.id.in:
Student studentIn = new Student("in student",20,true);
try {
iMyAidlInterface.sendStudentIn(studentIn);
Log.d(TAG, "新名字 = "+studentIn.getName());
} catch (RemoteException e) {
e.printStackTrace();
}
break;
}
}
}
先绑定ServiceiMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
,通过这行代码获取到接口的实例,通过这个调用那四个方法。
我们按顺序分别点击了InOut,In,Out
结果
InOut
AIDLClient
AIDLServer
可以看到,student的信息可以传到服务端中,而且服务端中对name的修改也会同步到客户端
In
AIDLClient
AIDLServer
可以看到,数据可以从客户端传到服务端,但是服务端对name的修改并没有同步到客户端
也就是说数据只能从客户端传到服务端
Out
AIDLClient
AIDLServer
可以看到,客户端的数据并没有传入到服务端中,而服务端中对name的修改却同步到了客户端
也就是说数据只能从服务端传到客户端。