AIDL (Android Interface Definition Language 接口定义语言)是一种IDL 语言,用于在两个进程(两个应用)之间进行进程间通信(IPC)的代码。如果在一个进程中(例如Activity)要调用另一个进程中(例如Service)对象的操作,就可以使用AIDL的方式实现
AIDL传输数据支持: Java基本数据类型,String、CharSequence,List、Map和实现了Parcelable接口的自定义对象
注意
传递非基本参数类型和String参数类型的前面必须有修饰符(表明这个数据的去向):in、out、inout修饰,否则会报错
in:参数由客户端设置,即客户端传入参数值(如果服务端调用则没有数据)
out:参数由服务端设置,即服务端返回值(如果客户端调用则没有数据)
inout:客户端输入端都可以设置,即双向都可传入值
基本类型只能是in
AIDL实现步骤
服务端步骤
- 编写AIDL文件,定义接口方法(放到main文件夹下aidl目录的包名下)
- 在Service类中新建实现该AIDL文件名.stub类的子类,并实现AIDL接口方法
- 在Service类onBind方法中返回实现了Stub的子类
客户端步骤
- 把编写的aidl目录和目录下的所有文件拷贝到客户端的main文件夹下
- 在客户端Activity中自定义类实现ServiceConnection接口
- 在onServiceConnected方法内通过 AIDL文件名.Stub.asInterface(service)拿到IBinder对象
- 在onServiceConnected方法内通过 AIDL文件
- 隐式启动服务端Service,开始获取服务端数据
实现步骤分解
服务端工程步骤
第一步:在main下新建aidl文件夹,再新建IPeople.aidl
如图可快速创建
新建People类
public class People implements Parcelable {
private String name;
private int age;
public People() {
}
public People(String name, int age) {
this.name = name;
this.age = age;
}
protected People(Parcel in) {
name = in.readString();
age = in.readInt();
}
public static final Creator<People> CREATOR = new Creator<People>() {
@Override
public People createFromParcel(Parcel in) {
return new People(in);
}
@Override
public People[] newArray(int size) {
return new People[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;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeInt(age);
}
@Override
public String toString() {
return "People{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
新建People.aidl
传递对象时,必须为对象创建aidl类,类中只需要这样写就行
package com.cn.lyz.aidlservicedemo;
parcelable People;
新建AIDL类 IPeople.aidl
package com.cn.lyz.aidlservicedemo;
import com.cn.lyz.aidlservicedemo.People; //需手动导入People包
interface IPeople {
//客户端调用会阻塞,此方法在binder线程池中执行
String getName(int num);
String addPeople_in(in People people);
String addPeople_out(out People people);
String addPeople_inout(inout People people);
People getPeople(int i);
List<People> getPeopleList();
int size();
//oneway修饰的方法不能有返回值
//oneway修饰的客户端不会阻塞,此方法在binder线程池中执行
oneway void replaceName(int index, String name);
}
提示
当参数是对象(People)时,每个参数前面必须有修饰符(表明这个数据的去向):in、out、inout修饰,否则会报错
- in:参数由客户端设置,即客户端传入参数值
- out:参数由服务端设置,即服务端返回值
- inout:客户端输入端都可以设置,即双向都可传入值
- 基本类型只能是in
oneway修饰符说明
- 未使用oneway修饰的方法,客户端调用会直接阻塞(不能执行耗时操作),服务端方法在binder线程池中执行
- 使用oneway修饰的方法(不能有返回值),客户端不会阻塞(可执行耗时操作),服务端方法在binder线程池中执行
注意
IPeople接口中使用了People类,需手动导入People的包, 否则会报错
import com.cn.lyz.aidlservicedemo.People;
重新编译后会在build文件夹下生成同名java文件如图
打开build文件夹下的IPeople.java文件,里面有一个报错如图
或者在运行时报这个错误,如图
这是需要在People类中添加readFromParcel方法
public void readFromParcel(Parcel source) {
name = source.readString();
age = source.readInt();
}
readFromParcel中读的顺序要和writeToParcel写的顺序一致,否则会数据错乱
第二步:新建PeopleService服务类
public class PeopleService extends Service {
private String[] peopleName = {"刘备","关羽","张飞"};
private IBinder mIBinder = new PeopleBinder();
private ArrayList<People> list = new ArrayList<>();
@Override
public void onCreate() {
super.onCreate();
Log.d("thread:", Thread.currentThread() + "-onCreate-");
}
/**
* 返回IBinder对象
* 运行在主线程
*/
@Override
public IBinder onBind(Intent intent) {
Log.d("thread:", Thread.currentThread() + "-onBind-");
return mIBinder;
}
/**
* 继承IPeople.Stub实现IPeople接口方法
* 运行在子线程
*/
private class PeopleBinder extends IPeople.Stub{
@Override
public String getName(int num) throws RemoteException {
Log.d("thread:", Thread.currentThread() + "-PeopleBinder-");
return peopleName[num];
}
@Override
public String addPeople_in(People people) throws RemoteException {
list.add(people);
return people.toString();
}
@Override
public String addPeople_out(People people) throws RemoteException {
list.add(people);
return people.toString();
}
@Override
public String addPeople_inout(People people) throws RemoteException {
list.add(people);
return people.toString();
}
@Override
public People getPeople(int i) throws RemoteException {
return list.get(i);
}
@Override
public List<People> getPeopleList() throws RemoteException {
return list;
}
@Override
public int size() throws RemoteException {
return list.size();
}
//oneway修饰的方法 客户端调用不会阻塞,此方法在binder线程池中执行
@Override
public void replaceName(int index, String name) throws RemoteException {
try {
//睡眠5秒
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
peopleName[index] = name;
for (int i = 0; i < peopleName.length; i++) {
Log.d("click2Btn:", "name:"+peopleName[i]);
}
}
}
}
在AndroidManifest.xml文件配置PeopleService:
<service
android:name=".PeopleService">
<intent-filter>
<!--添加action用于隐式启动Service-->
<action android:name="This is a magic code"/>
</intent-filter>
</service>
编译运行,报错提示找不到People类,如图
需要在app目录下的build.gradle内添加
android {
.....省略.....
sourceSets {
main {
java.srcDirs = ['src/main/java', 'src/main/aidl']
}
}
}
此时就可以把AIDLService工程运行到手机上了(客户端工程中也需要添加此代码)
客户端工程步骤
第三步:把服务端的aidl文件夹拷贝到客户端的main目录下,编译一下即可
第四步:在客户端Activity中自定义类实现ServiceConnection接口
public class MainActivity extends AppCompatActivity {
private Button mBtn1;
private Button mBtn2;
private IPeople peopleQuery;
private PeopleConnection peopleConn = new PeopleConnection();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
initData();
}
private void initView() {
mBtn1 = (Button) findViewById(R.id.btn1);
mBtn2 = (Button) findViewById(R.id.btn2);
}
private void initData() {
mBtn1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread(new Runnable() {
@Override
public void run() {
click1Btn();
}
}).start();
// click1Btn();
}
});
mBtn2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
click2Btn();
}
});
}
/**
* 默认在主线程执行
* 运行在主线程
*/
private final class PeopleConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//用此方法获取IPeople接口对象
//运行在主线程
Log.d("thread:", Thread.currentThread() + "-PeopleConnection-");
peopleQuery = IPeople.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
//运行在主线程
peopleQuery = null;
}
}
private void click1Btn() {
Log.d("thread:", Thread.currentThread() + "-click1Btn-");
//设置服务端服务的action
Intent intent = new Intent("This.is.a.magic.code");
//设置服务端的包名
intent.setPackage("com.cn.lyz.aidlservicedemo");
//必须使用bindService开启服务
bindService(intent, peopleConn, BIND_AUTO_CREATE);
}
/**
*
* 客户端调用接口方法会直接阻塞,服务端方法在binder线程(子线程)中执行
*/
private void click2Btn() {
try {
//获取服务端数组内角标为1的名字
String name = peopleQuery.getName(1);
Log.d("click2Btn:", "name:" + name);
//给服务端列表添加People
String p1 = peopleQuery.addPeople_in(new People("张三", 20));
String p2 = peopleQuery.addPeople_out(new People("李四", 30));
String p3 = peopleQuery.addPeople_inout(new People("王五", 40));
Log.d("click2Btn:", "p1:" + p1 + "--p2:" + p2 + "--p3:" + p3);
//获取服务端列表中第一个元素
People people = peopleQuery.getPeople(0);
Log.d("click2Btn:", "people:" + "name:"+people.getName()+"-age:"+people.getAge());
//获取服务端列表大小
int size = peopleQuery.size();
Log.d("click2Btn:", "size=" + size);
//循环打印服务端列表的内容
List<People> peopleList = peopleQuery.getPeopleList();
for (int i = 0; i < peopleList.size(); i++) {
People p = peopleList.get(i);
Log.d("click2Btn:", "for循环内people:" + p.toString());
}
//非阻塞执行
peopleQuery.replaceName(1, "吕布");
Log.d("click2Btn:", "非阻塞执行");
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(peopleConn);//解绑
}
}
打印结果
12-21 16:05:34.819 29532-29532/com.cn.lyz.aidldemo D/click2Btn:: name:关羽
12-21 16:05:34.822 29532-29532/com.cn.lyz.aidldemo D/click2Btn:: p1:People{name='张三', age=20}--p2:People{name='null', age=0}--p3:People{name='王五', age=40}
12-21 16:05:34.822 29532-29532/com.cn.lyz.aidldemo D/click2Btn:: people:name:张三-age:20
12-21 16:05:34.822 29532-29532/com.cn.lyz.aidldemo D/click2Btn:: size=3
12-21 16:05:34.823 29532-29532/com.cn.lyz.aidldemo D/click2Btn:: for循环内people:People{name='张三', age=20}
12-21 16:05:34.823 29532-29532/com.cn.lyz.aidldemo D/click2Btn:: for循环内people:People{name='null', age=0}
12-21 16:05:34.823 29532-29532/com.cn.lyz.aidldemo D/click2Btn:: for循环内people:People{name='王五', age=40}
12-21 16:05:34.823 29532-29532/com.cn.lyz.aidldemo D/click2Btn:: 非阻塞执行
12-21 16:05:39.824 29209-29223/com.cn.lyz.aidlservicedemo D/click2Btn:: name:刘备
12-21 16:05:39.824 29209-29223/com.cn.lyz.aidlservicedemo D/click2Btn:: name:吕布
12-21 16:05:39.825 29209-29223/com.cn.lyz.aidlservicedemo D/click2Btn:: name:张飞
第二行打印结果为,p1、p3对象有内容,p2对象内容为空,由此得出各个修饰符的作用
in:参数由客户端设置,即客户端传入参数值(如果服务端调用则没有数据)
out:参数由服务端设置,即服务端返回值(如果客户端调用则没有数据)
inout:客户端输入端都可以设置,即双向都可传入值
AIDL简单源码分析
自动生成的IPeople.java文件
package com.cn.lyz.aidlservicedemo;
public interface IPeople extends android.os.IInterface {
public static abstract class Stub extends android.os.Binder implements com.cn.lyz.aidlservicedemo.IPeople {
private static final java.lang.String DESCRIPTOR = "com.cn.lyz.aidlservicedemo.IPeople";
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
public static com.cn.lyz.aidlservicedemo.IPeople asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.cn.lyz.aidlservicedemo.IPeople))) {
return ((com.cn.lyz.aidlservicedemo.IPeople) iin);
}
return new com.cn.lyz.aidlservicedemo.IPeople.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_getName: {
data.enforceInterface(DESCRIPTOR);
int _arg0;
_arg0 = data.readInt();
java.lang.String _result = this.getName(_arg0);
reply.writeNoException();
reply.writeString(_result);
return true;
}
//省略....
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.cn.lyz.aidlservicedemo.IPeople {
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 java.lang.String getName(int num) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.lang.String _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(num);
mRemote.transact(Stub.TRANSACTION_getName, _data, _reply, 0);
_reply.readException();
_result = _reply.readString();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
//省略....
}
}
}
peopleQuery = IPeople.Stub.asInterface(service);
当跨进程进行通信peopleQuery调用方法时,其实就是在调用本地的
com.cn.lyz.aidlservicedemo.IPeople.Stub.Proxy(obj)对象中的方法,在Proxy代理对象中每个方法都会执行了mRemote.transact(Stub.TRANSACTION_xxx, _data, _reply, 0)方法
mRemote:远程服务端对象
远程对象mRemote对象继承IPeople.Stub对象,IPeople.Stub继承Binder,Binder实现IBinder
当执行mRemote.transact代码的时候,就开始进行进程间通信了,由客户端开始调用服务端的代码了
所以mRemote会调用IBinder的transact,继而传递到Binder对象中的transact方法中
public final boolean transact(int code, Parcel data, Parcel reply,
int flags) throws RemoteException {
if (false) Log.v("Binder", "Transact: " + code + " to " + this);
if (data != null) {
data.setDataPosition(0);
}
boolean r = onTransact(code, data, reply, flags);
if (reply != null) {
reply.setDataPosition(0);
}
return r;
}
在transact方法内部会调用onTransact方法,继而调用IPeople.Stub类中onTransact方法,而在此方法中会调用我们自己的方法(如:IPeople.java文件的第38行)待方法内部逻辑处理完成后,再返回给客户端进行处理
注意事项
1:Android5.0以上系统不支持隐式启动service,报错如下:
java.lang.IllegalArgumentException: Service Intent must be explicit
解决
给发送的隐式意图设置服务端包名
intent.setPackage(“设置服务端工程的包名”);
2:报错找不到传递的对象类
解决
需要在app目录下的build.gradle内添加
android {
.....省略.....
sourceSets {
main {
java.srcDirs = ['src/main/java', 'src/main/aidl']
}
}
}
3:找不到方法readFromParcel(Parcel)方法
解决
在People类中添加readFromParcel方法,并且读的顺序要和writeToParcel写的顺序一致,否则会数据错乱
4:当传递参数为对象时,每个参数前必须有修饰符:in,out,inout,否则会报错
5:当传递参数为对象时,需要手动导入该对象的包,否则报错
6:AIDL服务默认是运行在主线程中,客户端调用未使用oneway修饰的方法,会直接阻塞(不能执行耗时操作),若有耗时操作,应该在子线程中调用AIDL客户端使用oneway修饰的方法(不能有返回值),不会阻塞(可执行耗时操作)