一、简介
AIDL 即 Android Interface Definition Language,翻译就是Android接口定义语言,用于定义服务器和客户端通信接口的一种描述语言,可以拿来生成用于IPC的代码。从某种意义上说AIDL其实是一个模板,因为在使用过程中,实际起作用的并不是AIDL文件,而根据AIDL生成的一个IInterface的实例代码,AIDL其实是为了避免我们重复编写代码而出现的一个模板,AS根据AIDL生成我们需要的代码。
设计AIDL这门语言的目的就是为了实现进程间通信。在Android系统中,每个进程都运行在一块独立的内存中,在其中完成自己的各项活动,与其他进程都分隔开来。可是有时候我们又有应用间进行互动的需求,比较传递数据或者任务委托等,AIDL就是为了满足这种需求而诞生的。通过AIDL,可以在一个进程中获取另一个进程的数据和调用其暴露出来的方法,从而满足进程间通信的需求。通常,暴露方法给其他应用进行调用的应用称为服务端,调用其他应用的方法的应用称为客户端,客户端通过绑定服务端的Service来进行交互。
二、AIDL的语法
AIDL的语法十分简单,与Java语言基本保持一致,需要记住的规则有以下几点:
1、AIDL文件以 .aidl 为后缀名
2、AIDL支持的数据类型分为如下几种:
1)、八种基本数据类型:byte、char、short、int、long、float、double、boolean
String,CharSequence
2)、实现了Parcelable接口的数据类型
3)、List 类型。List承载的数据必须是AIDL支持的类型,或者是其它声明的AIDL对象
4)、Map类型。Map承载的数据必须是AIDL支持的类型,或者是其它声明的AIDL对象。注意AIDL中接口定义时不能指定Map的泛型类型。
3、AIDL文件可以分为两类
1)、用来声明实现了Parcelable接口的数据类型,以供其他AIDL文件使用那些非默认支持的数据类型
2)、用来定义接口方法,声明要暴露哪些接口给客户端调用,定向Tag就是用来标注这些方法的参数值
4、定向Tag
修饰方法中的形参。所有非基本数据类型的参数在传递的时候都需要指定一个方向tag来指明数据的流向,可以是in、out或者inout,in 表示数据只能由客户端流向服务端, out 表示数据只能由服务端流向客户端,而 inout 则表示数据可在服务端与客户端之间双向流通。基本数据类型默认是in并且只能是in,这三个修饰符被称为定向tag用于在跨进程通信时指定数据流向。
1)、in:在服务端目标方法可以得到一个与客户端调用方法实参值相同的对象(注意不是同一个对象,它们在不同虚拟机实例中),简而言之,客户端经序列化后传递服务端,服务端反序列化得到一个与之值相同的新的对象
2)、out:在发起远程调用时传入的实参不会传到到服务端,而是在服务端新建一个对象传给目标方法,待目标方法返回后将这个对象传回客户端(传回的意思是服务端更改了形参,客户端这个形参对应对象值也会改变)。另外,服务端是调用无参的构造方法来新建对象,这意味着你的参数所对应的类必须有有无参的构造方法。简而言之,客户端不会序列化该参数,而是服务端调用无参构造方法新建了一个对象,待目标方法返回后,将参数写入reply返回给客户端
3)、inout:可以看成是定向tag inout基本上算是in、out的并集,inout服务端通过反序列化客户端传过来的数据得到一个新的对象,不会new一个了,然后就这个对象传回到客户端(服务端更改了形参,客户端这个形参对应对象值也会改变)
具体原理请看探索AIDL定向tag in out inout原理
例子:
服务端更改add方法传入的行参值,代码如下:
private UserManager.Stub stub = new UserManager.Stub() {
@Override
public void add(User user) throws RemoteException {
Log.e(TAG, "UserManager.Stub.add>>user:" + user.toString());
// 更改add方法传入的行参值
user.setName("服务端更改了名字");
users.add(user);
}
}
客户端新建user,传入到服务端,代码如下:
User user = new User("小海", 18);
userManager.add(user);
// 打印服务端更改后的user值
Log.e(TAG, "btn_remote_add_user>>user:" + user.toString());
a、指定为in代码如下:
interface UserManager {
void add(in User user);
List<User> getUserList();
}
日志如下,客户端将行参传入到服务端。服务端更改了user的name但是没有传到客户端,客户端形参user的值不会改变
b、指定为out代码如下:
interface UserManager {
void add(out User user);
List<User> getUserList();
}
日志如下,客户端不能将行参传入到服务端,所以服务端打印是空。服务端会new一个User对象,更改了user的name后会传到了客户端,即客户端形参user会变成服务端new 的那个User对象。
c、指定为inout代码如下:
interface UserManager {
void add(inout User user);
List<User> getUserList();
}
日志如下,客户端将行参传入到服务端。服务端更改了user的name并且传到了客户端,客户端的形参user也改变
5、明确导包
在AIDL文件中需要明确标明引用到的数据类型所在的包名,即使两个文件处在同个包名下
三、服务端代码编写
该demo实现功能如下:客户端应用可以向服务器端应用添加用户和获取用户列表。
1、新建名为“remoteaidlserver”的工程
2、选中“com.demo.aidlserver”点击右键新建AIDL文件,命名为“User”
3、新建一个User.java实体类,要继承Parcelable。注意是放在java目录下,不是放在aidl目录下。注意java文件的包路径和aidl中的要一样,因为aidl生成的文件中会使用User,而使用User时指定的包路径和aidl中指定的一样,但是这个User又必须使用java中的User.java。所以java中和aidl中的User包路径要一样,否则在aidl生成的文件中会报找不到User。 代码如下:
public class User implements Parcelable {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.name);
dest.writeInt(this.age);
}
/**
* 参数是一个Parcel,用它来存储与传输数据。该方法在AIDL生成的代码中会调用
* 该方法在AIDL文件void add(inout User user); 参数声明为out时才有用
* @param dest
*/
public void readFromParcel(Parcel dest) {
// 注意,此处的读值顺序应当是和writeToParcel()方法中一致的
name = dest.readString();
age = dest.readInt();
}
public static final Creator<User> CREATOR = new Creator<User>() {
@Override
public User createFromParcel(Parcel source) {
return new User(source);
}
@Override
public User[] newArray(int size) {
return new User[size];
}
};
private User(Parcel in) {
name = in.readString();
age = in.readInt();
}
}
4、User.aidl中代码添加"parcelable User;",这样AIDL中的实体类就创建完成。代码如下:
package com.demo.aidlserver;
parcelable User;
5、定义暴露给客户端的接口的AIDL,命名“UserManager.aidl”。代码如下:
package com.demo.aidlserver;
// 不是基本类型都要导包,即使在同一目录下
import com.demo.aidlserver.User;
interface UserManager {
// 除了基本数据类型、String、CharSequence,其他类型都要指定tag方向
void add(in User user);
List<User> getUserList();
}
6、clean一下,就会生成aidl对应的java代码,在build->generated->source目录下。我们实际使用的不是AIDL文件,而是根据AIDL文件生成的java文件。
生成的Java文件代码如下:
public interface UserManager extends android.os.IInterface {
public static abstract class Stub extends android.os.Binder implements com.demo.aidlserver.UserManager {
private static final java.lang.String DESCRIPTOR = "com.demo.aidlserver.UserManager";
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
public static com.demo.aidlserver.UserManager asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.demo.aidlserver.UserManager))) {
// 客户端和服务端在同一进程,就不用使用Binder,直接返回该对象,直接调用
return ((com.demo.aidlserver.UserManager) iin);
}
// 客户端和服务端在不同进程
return new com.demo.aidlserver.UserManager.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 {
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_add: {
data.enforceInterface(DESCRIPTOR);
com.demo.aidlserver.User _arg0;
// 从Parcel读取客户端传过来的数据
if ((0 != data.readInt())) {
_arg0 = com.demo.aidlserver.User.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
// 调用服务端实现的add()方法
this.add(_arg0);
// 写入返回给客户端的数据
reply.writeNoException();
if ((_arg0 != null)) {
reply.writeInt(1);
_arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
} else {
reply.writeInt(0);
}
return true;
}
case TRANSACTION_getUserList: {
data.enforceInterface(DESCRIPTOR);
java.util.List<com.demo.aidlserver.User> _result = this.getUserList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
// 运行在客户端,通过mRemote.transact()调用到服务端
private static class Proxy implements com.demo.aidlserver.UserManager {
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 add(com.demo.aidlserver.User user) throws android.os.RemoteException {
// 客户端新建请求参数和返回参数对象
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
// 把请求参数设置到_data中
if ((user != null)) {
_data.writeInt(1);
user.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
// 调用到服务端的onTransact()方法
mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
_reply.readException();
if ((0 != _reply.readInt())) {
user.readFromParcel(_reply);
}
} finally {
_reply.recycle();
_data.recycle();
}
}
@Override
public java.util.List<com.demo.aidlserver.User> getUserList() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.demo.aidlserver.User> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getUserList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.demo.aidlserver.User.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
// 服务端根据这两个值判断客户端调用的是哪一个方法
static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_getUserList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
public void add(com.demo.aidlserver.User user) throws android.os.RemoteException;
public java.util.List<com.demo.aidlserver.User> getUserList() throws android.os.RemoteException;
}
7、创建服务,服务中重要的工作是创建UserManager.Stub实例,在onBind方法中返回该实例,客户端就可以通过这个实例访问服务端的方法。代码如下。注意:UserManager.Stub实现中的add()和getUserList()方法运行在Binder线程池,即子线程中。所以客户端调用服务端提供方法时注意该方法是否是耗时方法,注意不要阻塞主线程。否则会出现ANR,出现ANR的条件是主线程阻塞了然后用户还在继续操作界面,界面一段时间没有响应就会报ANR。如果主线程阻塞后,用户没有操作界面是不会报ANR的。
public class RemoteAidlService extends Service {
private static final String TAG = RemoteAidlService.class.getSimpleName();
private List<User> users;
private UserManager.Stub stub = new UserManager.Stub() {
@Override
public void add(User user) throws RemoteException {
Log.e(TAG, "UserManager.Stub.add>>user:" + user.toString());
users.add(user);
}
@Override
public List<User> getUserList() throws RemoteException {
Log.e(TAG, "UserManager.Stub.getUserList>>");
return users;
}
};
public RemoteAidlService() {
}
@Override
public void onCreate() {
Log.e(TAG, "onCreate>>");
super.onCreate();
users = new ArrayList<>();
}
@Override
public IBinder onBind(Intent intent) {
Log.e(TAG, "onBind>>");
return stub;
}
@Override
public boolean onUnbind(Intent intent) {
Log.e(TAG, "onUnbind>>");
return super.onUnbind(intent);
}
}
8、上面就完成了服务端的代码
四、客户端代码编写
1、将服务端的aidl目录和定义的User.java一起拷贝到客户端中,注意上面文件的包名要和服务端一样。
2、clean一下项目生产需要的java文件
3、新建ServiceConnection实例,在onServiceConnected中生成访问服务端的实例UserManager.Stub.asInterface(service)。代码如下:
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
userManager = UserManager.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
4、绑定服务,代码如下:
bindService(remoteAIDLServiceIntent, serviceConnection, BIND_AUTO_CREATE);
5、现在就可以访问服务端的方法了,在布局文件中加入两个按钮,分别用于添加用户和获取用户列表。添加用户和获取用户列表的代码如下:
case R.id.btn_remote_add_user:
try {
userManager.add(new User("小海", 18));
} catch (RemoteException e) {
e.printStackTrace();
}
break;
case R.id.btn_remote_get_uer:
try {
List<User> users = userManager.getUserList();
Log.e(TAG, "btn_remote_get_uer>>users:" + users.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
break;
五、Binder线程池最大线程数
Binder线程池最大线程数是15,在framework\base\libs\binder\ProcessState.cpp中定义,代码如下。
#define DEFAULT_MAX_BINDER_THREADS 15
我们更改下代码测试一下。客户端新建50个线程调用服务端的userManager.add()方法。具体如下。
for(int i = 0; i < 50; i ++) {
new Thread() {
@Override
public void run() {
super.run();
Log.e(TAG, "btn_remote_add_user>>thread add");
try {
userManager.add(user);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}.start();
}
服务端add()方法中增加延时模拟耗时操作,具体代码如下。
public void add(User user) throws RemoteException {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.e(TAG, "UserManager.Stub.add>>user:" + user.toString() +
"是否主线程:" + (getMainLooper().getThread()==Thread.currentThread()) +
",线程名:" + Thread.currentThread().getName());
user.setName("服务端更改了名字");
users.add(user);
}
打印的日志如下。
因为线程池最大数量是15,所以先运行15个add()方法,时间为15:27:22。因为add()中延时了3秒,3秒后方法结束,其他请求可以执行,所以在 时间15:27:25时运行另外的15个add()方法。但是上面打印出来一次执行的是16个线程,具体原因不知道怎么回事!