AIDL 是什么
AIDL(Android Interface Definition Language)安卓接口定义语言用于定义客户端与服务器端的通信接口,目的是为了实现跨进程通信。
AIDL 语法
- aidl 文件以 .aidl 为后缀
- aidl 支持的数据类型:
- 8种基本数据类型:short、int、long、float、double、byte、char、boolean
- CharSequence、String
- List 类型。List 中的所有元素必须是这里列出来的类型,或者是其他 AIDL 生成的接口或实现了 Parcelabe 的类型。
- Map 类型。 Map 所持有的参数类型和 List 一样。泛型的 Map (如 Map<String,String>)是不支持的。
- AIDL 文件分为两类:
- 用于声明实现了 Parcelable 接口的数据类型。上面讲到 List 和 Map 是可以持有实现了 Parcelable 接口的数据类型。该类型并不能直接被 aidl 文件使用,而是要创建一个同名的 AIDL 文件,然后声明一下,后面会具体讲
- 用于定义进程间通信的接口。我们把需要给客户端调用的方法在该文件中暴露出来。
- 定向 Tag。它决定了跨进程通信中数据的流向。作用于上面 AIDL 文件中定义的方法中的参数,有 in、out、inout 三种。由于方法是由服务器端定义的,所以 in 代表数据只能由客户端流入服务器端,out 则相反,inout 是双向流通。基本数据类型、CharSequence、String 和其他 AIDL文件定义的方法接口的定向 Tag 的默认值为 in 且不能修改。其余的类型需要明确标注定向 Tag。
- AIDL 文件需要明确标明引用到的数据类型所在的包名。
Sample
服务器端
1.首先创建一个项目, 如 aidlServer,包名为 com.example.allen.aidlserver 。
2.创建一个实现了 Parcelable 的数据类,为了统一管理,我创建了一个 aidl 目录用于存放所有 aidl 使用到的数据类:
package com.example.allen.aidlserver.aidl;
/**
* AIDL 文件是如何使用 Parcelable 类型的数据的
*/
public class Avenger implements Parcelable {
public String name;
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.name);
}
//注意要手动添加 readFromParcel 方法,否则后面调用的时候会报错
public void readFromParcel(Parcel in) {
this.name = in.readString();
}
public Avenger() {
}
protected Avenger(Parcel in) {
this.name = in.readString();
}
public static final Creator<Avenger> CREATOR = new Creator<Avenger>() {
@Override
public Avenger createFromParcel(Parcel source) {
return new Avenger(source);
}
@Override
public Avenger[] newArray(int size) {
return new Avenger[size];
}
};
}
3.由于 Parcelable 数据类不能被 aidl 文件直接引用,要创建一个与上面 Avenger 数据类同名的 aidl 文件将该数据类声明进去。 右键 module -> New -> AIDL -> AIDL File
然后命名为 Avenger。
注意到我们之前创建的 Avenger.java 数据类在我们自定义的一个 aidl 目录中,而要声明该数据类的 aidl 文件除了要与之同名外,地址也必须相同,所以我们也要创建一个 aidl 目录,然后将刚才创建的 Avenger.aidl 文件移进去。 我们将该类中的所有东西全部删掉,然后将该数据类的包名和类名声明一下:
package com.example.allen.aidlserver.aidl;
parcelable Avenger;
这样 AIDL 类中的方法就可以直接使用该 Parcelable 类型的数据了。
4.这时我们就可以真正开始定义客户端与服务器端进行交互的 aidl 接口了。同样创建一个 aidl 文件:
package com.example.allen.aidlserver;
//要将所引用的的数据类型手动 import 进来,基本类型之类的数据则不用
import com.example.allen.aidlserver.aidl.Avenger;
interface IAvengerAidlInterface {
List<String> getAvengers();
void addAvengerInOut(inout Avenger avenger);
void addAvengerIn(in Avenger avenger);
void addAvengerOut(out Avenger avenger);
}
然后 clean 下就会生成相应的 IAvengerAidlInterface.java 文件 5.通信接口定义好后就需要创建一个 Service 来给客户端远程绑定。
class AidlService : Service() {
//前面讲过 AIDL 只支持 ArrayList,为什么这里可以用 CopyOnWriteArrayList
//以为 AIDL 中支持的是抽象的 List 而 List 只是一个接口。虽然返回的是 CopyOnWriteArrayList
//但 Binder 会根据 List 的规范去访问数据并返回 ArrayList 给客户端
private var avengerList: MutableList<Avenger> = CopyOnWriteArrayList<Avenger>()
override fun onCreate() {
super.onCreate()
initAvengers()
}
private fun initAvengers() {
for (i in 0..4) {
val avenger = Avenger()
avenger.name = "avenger$i"
avengerList.add(avenger)
}
}
//aidl 接口在编译后所生成的 java 同名文件中会有一个 Stud 的内部类
private val stub = object : IAvengerAidlInterface.Stub() {
override fun getAvengers(): MutableList<Avenger> {
return avengerList
}
override fun addAvengerInOut(avenger: Avenger?) {
if (avenger == null)
Log.e("AidlService", "addAvengerInOut:null")
else {
avengerList.add(avenger)
Log.e("AidlService", "addAvengerInOut:${avenger.name}")
}
}
override fun addAvengerIn(avenger: Avenger?) {
if (avenger == null)
Log.e("AidlService", "addAvengerIn:null")
else {
avengerList.add(avenger)
Log.e("AidlService", "addAvengerIn:${avenger.name}")
}
}
override fun addAvengerOut(avenger: Avenger?) {
if (avenger == null)
Log.e("AidlService", "addAvengerOut:null")
else {
avengerList.add(avenger)
Log.e("AidlService", "addAvengerOut:${avenger.name}")
}
}
}
// 直接返回 IAvengerAidlInterface.Stub 给客户端来调用我们定义的方法
override fun onBind(intent: Intent): IBinder? {
return stub
}
}
Service 完成后就需要去 Manifest 中声明下。客户端可以直接通过指定 Service 类名的方式来绑定,也可以通过先指定包名,然后配置 Action 的方式来绑定(这种方式需要定义 action)。
<service android:name=".service.AidlService"
android:exported="true">
<intent-filter>
<action android:name="com.example.allen.aidlserver.avengeraction"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</service>
总结:服务器端做的就是定义与服务器端通信的接口,然后创建 Service 并实现所定义的 aidl 接口里的方法供客户端调用。
客户端
创建一个项目 aidlclien,包名为 com.example.allen.aidlclient。然后将服务器端的 aidl 目录复制过来到 java 的同级目录。 然后创建一个与服务器端的 Avenger.java 文件所在相同的包名来存放该类。
然后我们就可以绑定 Service 并调用服务器端接口所定义的方法了。
class MainActivity : AppCompatActivity() {
private var avengerAidlInterface: IAvengerAidlInterface ?= null
private var isConnected = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
bindService()
initView()
}
override fun onDestroy() {
super.onDestroy()
if (isConnected) unbindService(serviceConnection)
}
//--------------绑定服务器的服务,拿到 IAvengerAidlInterface-----------------
private fun bindService() {
val intent = Intent();
intent.`package` = "com.example.allen.aidlserver";
intent.action = "com.example.allen.aidlserver.avengeraction";
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
}
private val serviceConnection = object : ServiceConnection {
override fun onServiceDisconnected(name: ComponentName?) {
isConnected = false
}
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
avengerAidlInterface = IAvengerAidlInterface.Stub.asInterface(service)
isConnected = true
}
}
//-----------------------------------------------------------------------
private fun initView() {
inoutButton.setOnClickListener {
if (isConnected) {
try {
val avenger = Avenger()
avenger.name = "Black Panther"
avengerAidlInterface?.addAvengerInOut(avenger)
} catch (e: RemoteException) {
Log.e("MainActivity", "Oh man! What's going on? Black Panther will come for U")
}
}
}
inButton.setOnClickListener {
if (isConnected) {
try {
val avenger = Avenger()
avenger.name = "Black Widow"
avengerAidlInterface?.addAvengerInOut(avenger)
} catch (e: RemoteException) {
Log.e("MainActivity", "Oh man! What's going on? Black Widow will come for U.")
}
}
}
outButton.setOnClickListener {
if (isConnected) {
try {
val avenger = Avenger()
avenger.name = "Captain America"
avengerAidlInterface?.addAvengerInOut(avenger)
} catch (e: RemoteException) {
Log.e("MainActivity", "Oh man! What's going on? Captain America will come for U.")
}
}
}
getAvengersButton.setOnClickListener {
if (isConnected) {
try {
val avenger = Avenger()
avenger.name = "Black Panther"
val avengers = avengerAidlInterface?.avengers
} catch (e: RemoteException) {
Log.e("MainActivity", "Give me the list of avengers or U will be in trouble.")
}
}
}
}
}
这样,一个完整的进程间通信就完成了。
定向 Tag
in: 数据只能有客户端传给服务器,服务器对数据的修改不会影响到客户端。如客户端调用 addAvengerIn(in Avenger) 方法将 Avenger 对象传给服务器,服务器拿到后,修改 avenger 的名字,客户端是不会发生改变的。
out: 数据只能由服务器传给客户端,若客户端调用 addAvengerIn(out Avenger) 方法,服务器端能接受到这个对象,但该对象内的属性值为空。服务器端拿到该对象后,修改其属性值会同步到客户端。
inout: 所有修改都会同步。
与之相关的还有死亡代理,Binder 连接池,下面的文章比较详细,就不赘述了