上一篇文章,写了 Binder 在 native 和 Framework 如何使用,今天来看看 Android 提供的跨进程通信语言 - AIDL。
目录:
- 什么是 AIDL
- AIDL 语法
- 使用 AIDL 步骤
- AIDL 原理
1. 什么是 AIDL
AIDL 全称是 Android Interface Definition Language,也就是 Android 接口定义语言。
设计这门语言的目的是为了实现进程间通信,尤其是在涉及多进程并发情况下的进程间通信。
2. AIDL 语法
既然是它一门语言,必然有它的语法。
- 2.1 文件类型
用 AIDL 书写的文件的后缀是 .aidl。
- 2.2 数据类型
AIDL 默认支持一些数据类型,在使用这些数据类型的时候是不需要导包的,但是除了这些类型之外的数据类型,在使用之前必须导包,就算目标文件与当前正在编写的 .aidl 文件在同一个包下。而在 Java 中,这种情况是不需要导包的。比如,现在我们编写了两个文件,一个叫做 Person.aidl ,另一个叫做 PersonManager.aidl,它们都在 io.kzw.aidl 包下 ,现在我们需要在 .aidl 文件里使用 Person 对象,那么我们就必须在 .aidl 文件里面写上 import io.kzw.aidl.Person。
默认支持的数据类型包括:
- Java 中的八种基本数据类型,包括 byte,short,int,long,float,double,boolean,char。
- String 类型。
- CharSequence 类型。
- List 类型:List中的所有元素必须是 AIDL 支持的类型之一,或者是一个其他 AIDL 生成的接口,或者是定义的parcelable。List 可以使用泛型。
- Map 类型:Map 中的所有元素必须是 AIDL 支持的类型之一,或者是一个其他 AIDL 生成的接口,或者是定义的parcelable。Map 是不支持泛型的。
- 2.3 定向 tag
AIDL 中的定向 tag 表示了在跨进程通信中数据的流向,其中 in 表示数据只能由客户端流向服务端,out 表示数据只能由服务端流向客户端,而 inout 则表示数据可在服务端与客户端之间双向流通。其中,数据流向是针对在客户端中的那个传入方法的对象而言的。
- in 为定向 tag 的话表现为服务端将会接收到一个那个对象的完整数据,但是客户端的那个对象不会因为服务端对传参的修改而发生变动;
- out 的话表现为服务端将会接收到那个对象的的空对象,但是在服务端对接收到的空对象有任何修改之后客户端将会同步变动;
- inout 为定向 tag 的情况下,服务端将会接收到客户端传来对象的完整信息,并且客户端将会同步服务端对该对象的任何变动。
另外,Java 中的基本类型和 String,CharSequence 的定向 tag 默认且只能是 in。还有,注意不要滥用定向 tag,而是要根据需要选取合适的。要是不管三七二十一,全都一上来就用 inout,等工程大了系统的开销就会大很多,因为排列整理参数的开销是很昂贵的。
- 2.4 两种 AIDL 文件
所有的 AIDL 文件大致可以分为两类:
- 1. 用来定义 parcelable 对象,以供其他 AIDL 文件使用 AIDL 中非默认支持的数据类型的。
- 2. 用来定义方法接口,以供系统使用来完成跨进程通信的。
可以看到,两类文件都是在 "定义" 些什么,而不涉及具体的实现,这就是为什么它叫做 "Android接口定义语言"。
注:所有的非默认支持数据类型必须通过第一类 AIDL 文件定义才能被使用。
3. 使用 AIDL 步骤
- 3.1 修改 app build.gradle
android {} 中加入:
sourceSets {
main {
java.srcDirs = ['src/main/java', 'src/main/aidl']
}
}
- 3.2 使数据类实现 Parcelable 接口
public class Person implements Parcelable {
private String name;
private int age;
private int sex;
public Person() {
}
public Person(Parcel in) {
name = in.readString();
age = in.readInt();
sex = in.readInt();
}
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 int getSex() {
return sex;
}
public void setSex(int sex) {
this.sex = sex;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeInt(age);
dest.writeInt(sex);
}
public static final Creator<Person> CREATOR = new Creator<Person>() {
@Override
public Person createFromParcel(Parcel in) {
return new Person(in);
}
@Override
public Person[] newArray(int size) {
return new Person[size];
}
};
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("Person{");
sb.append("name='").append(name).append('\'');
sb.append(", age=").append(age);
sb.append(", sex=").append(sex);
sb.append('}');
return sb.toString();
}
}
- 3.3 编写 AIDL 文件
在 java 同级目录,新建 aidl 文件夹,并且包命和 java 的一样:
// Person.aidl
// 第一类 AIDL 文件
// 这个文件的作用是引入了一个序列化对象,Person 供其他的 AIDL 文件使用
// 注意:Person.aidl 与 Person.java 的包名应当是一样的
package io.kzw.aidl;
parcelable Person;
// PersonManager.aidl
// 第二类 AIDL 文件,作用是定义方法接口
package io.kzw.aidl;
// 导入所需要使用的非默认支持数据类型的包
import io.kzw.aidl.Person;
interface PersonManager {
void addPerson(in Person person);
List<Person> getPersons();
}
- 3.4 rebuild project 生成方法接口 Java 类
Android studio rebuild 工程,会生成一个 PersonManager.java 类。
其实是 IDE 内部调用了 aidl 命令工具,将 aidl 文件编译生成 .java 文件,最后编译成 .class 文件,打包成 dex。
来看看生成的 PersonManager.java:
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: /Users/kuangzhongwen/Desktop/project/BinderAIDLDemo/app/src/main/aidl/io/kzw/aidl/PersonManager.aidl
*/
package io.kzw.aidl;
public interface PersonManager extends android.os.IInterface {
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements io.kzw.aidl.PersonManager {
private static final java.lang.String DESCRIPTOR = "io.kzw.aidl.PersonManager";
/** Construct the stub at attach it to the interface. */
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an io.kzw.aidl.PersonManager interface,
* generating a proxy if needed.
*/
public static io.kzw.aidl.PersonManager asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof io.kzw.aidl.PersonManager))) {
return ((io.kzw.aidl.PersonManager) iin);
}
return new io.kzw.aidl.PersonManager.Stub.Proxy(obj);
}
@Override
public android.os.IBinder asBinder() {
return this;
}
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
java.lang.String descriptor = DESCRIPTOR;
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(descriptor);
return true;
}
case TRANSACTION_addPerson: {
data.enforceInterface(descriptor);
io.kzw.aidl.Person _arg0;
if ((0 != data.readInt())) {
_arg0 = io.kzw.aidl.Person.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
this.addPerson(_arg0);
reply.writeNoException();
return true;
}
case TRANSACTION_getPersons: {
data.enforceInterface(descriptor);
java.util.List<io.kzw.aidl.Person> _result = this.getPersons();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
default: {
return super.onTransact(code, data, reply, flags);
}
}
}
private static class Proxy implements io.kzw.aidl.PersonManager {
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
@Override
public android.os.IBinder asBinder() {
return mRemote;
}
public java.lang.String getInterfaceDescriptor() {
return DESCRIPTOR;
}
@Override
public void addPerson(io.kzw.aidl.Person person) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((person != null)) {
_data.writeInt(1);
person.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_addPerson, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
@Override
public java.util.List<io.kzw.aidl.Person> getPersons() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<io.kzw.aidl.Person> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getPersons, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(io.kzw.aidl.Person.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
static final int TRANSACTION_addPerson = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_getPersons = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
public void addPerson(io.kzw.aidl.Person person) throws android.os.RemoteException;
public java.util.List<io.kzw.aidl.Person> getPersons() throws android.os.RemoteException;
}
是不是很熟悉,还是使用了 Binder 来进行跨进程通信。代码结构如下:
- PersonManager 本身是一个接口,继承于 IInterface 接口,IInterface 有一个方法:IBinder asBinder()。
- PersonManager 内部有个 Stub 抽象类,作为服务器端。它继承于 Binder 类,实现 PersonManager 接口。
- Stub 类中有一个描述符:DESCRIPTOR = "io.kzw.aidl.PersonManager"。
- Stub 类提供了 asInterface() 方法,传入一个 IBinder 对象,返回一个 PersonManager 对象,实现是用 Stub 的内部类 Proxy。
- Stub 类重写了 asBinder() 方法,返回它对象本身。
- Stub 类重写了 onTransact() 方法,来接收处理客户端发来的请求,内部会调用 addPerson() 和 getPersons()。
- Stub 类内部有个 Proxy 类,作为客户端。它需要传入一个远程的 IBinder 对象,同时它实现了 PersonManager 接口的两个方法:addPerson() 和 getPersons(),内部调用了远程的 IBinder 对象 transact() 提交事务。
- 3.5 编写服务器端代码
package io.kzw.aidl;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import io.kzw.aidl.PersonManager.Stub;
import java.util.ArrayList;
import java.util.List;
public class PersonService extends Service {
public final String TAG = PersonService.class.getSimpleName();
private List<Person> persons = new ArrayList<>();
// 由 AIDL 文件生成的 PersonManager 的 stub 实现类
private final PersonManager.Stub personManager = new Stub() {
@Override
public void addPerson(Person person) throws RemoteException {
synchronized (this) {
person.setName("aaa");
if (!persons.contains(person)) {
persons.add(person);
}
// 打印mBooks列表,观察客户端传过来的值
Log.e(TAG, "invoking addBooks() method , now the list is : " + persons.toString());
}
}
@Override
public List<Person> getPersons() throws RemoteException {
synchronized (this) {
Log.e(TAG, "invoking getBooks() method , now the list is : " + persons.toString());
return persons;
}
}
};
@Override
public void onCreate() {
super.onCreate();
Person person = new Person();
person.setName("kzw");
person.setAge(28);
persons.add(person);
}
@Override
public IBinder onBind(Intent intent) {
return personManager;
}
}
注册服务,由于是跨进程调用,所以把 service 配置成 android:process=":remote",进程也可以命名成其他的。
<service
android:name=".PersonService"
android:process=":remote" />
- 3.6 编写客户端代码
package io.kzw.aidl;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Toast;
import io.kzw.aidl.R;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private PersonManager personManager = null;
/**
* 标志当前与服务端连接状况的布尔值,false 为未连接,true 为已链接
*/
private boolean isConnected = false;
private List<Person> persons;
/**
* 需要使用 ServiceConnection 类
*/
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.e(getLocalClassName(), "service connected");
// 将服务器端 Binder 转换成 客户端 Binder (proxy)
personManager = PersonManager.Stub.asInterface(service);
isConnected = true;
if (personManager != null) {
try {
persons = personManager.getPersons();
Log.e(getLocalClassName(), persons.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.e(getLocalClassName(), "service disconnected");
isConnected = false;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.add_person).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
Log.i(getLocalClassName(), "click add person button");
if (personManager == null)
return;
Person person = new Person();
person.setName("ad");
person.setAge(30);
try {
personManager.addPerson(person);
// 在服务器端的修改是否会影响客户端的对象属性?
Log.e(getLocalClassName(), person.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
}
@Override
protected void onStart() {
super.onStart();
if (!isConnected) {
Intent intent = new Intent(this, PersonService.class);
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
}
}
@Override
protected void onStop() {
super.onStop();
if (isConnected) {
unbindService(serviceConnection);
}
}
}
- 3.7 执行
主进程打印:
io.kzw.aidl E/MainActivity: service connected
io.kzw.aidl E/MainActivity: [Person{name='kzw', age=28, sex=0}]
io.kzw.aidl I/MainActivity: click add person button
io.kzw.aidl E/MainActivity: Person{name='ad', age=30, sex=0}
远端进程打印:
io.kzw.aidl:remote E/PersonService: invoking getBooks() method , now the list is : [Person{name='kzw', age=28, sex=0}]
io.kzw.aidl:remote E/PersonService: invoking addBooks() method , now the list is : [Person{name='kzw', age=28, sex=0}, Person{name='aaa', age=30, sex=0}]
其中也可以验证上面讲到的 in tag 定向,服务器的修改不会影响客户端对象。
4. AIDL 原理
采用 AIDL 技术,原理还是利用 Framework Binder 的架构。