持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第10天,点击查看活动详情
一、前言
说了这么多关于binder通信的机制,啃了这么多源代码,我们来简单用一个aidl的demo来深入了解一下关于Android的进程间通信
二、AIDL
AIDL(Android Interface Define Language):
是Android的一种接口定义语言,类似于IDL(Interactive Data Language)
借助它,你可以定义接口,使得客户端和服务端之间实现进程间通信.对android来说,一个进程无法通过正常的方法来访问另一个进程的内存空间.可以说,它们需要把对象分解成操作系统可以理解的原子类型,然后使对象突破在不同进程之间进行传输.具体实现这种机制的代码写起来非常枯燥,所以android为你提供了AIDL语言来进行处理.
三、AIDL定义服务端接口
首先我们需要定义我们的服务端,声明服务端可以提供的具体方法.
as已经提供了AIDL的实现模块
出现的错误
1、忘记导包
com.example.testapt.bean: couldn't find import for class com.example.testapt.bean
aidl记得一定要手动导包
import com.example.testapt.bean.Person;
2、定义方法输入流类型
'com.example.testapt.bean.Person' can be an out type,
so you must declare it as in, out, or inout.
有些类型缺少in\out\inout标签就会报错,in 表示数据只能由客户端流向服务端; out 表示数据只能由服务端流向客户端;inout 表示数据可在服务端和客户端双向流通。
解决办法:在传递对象bean前加个in
// IMyAidlInterface.aidl
package com.example.testapt;
// Declare any non-default types here with import statements
import com.example.testapt.bean.Person;
interface IMyAidlInterface {
void addPeople(in Person testString);
List<Person> getPeopleList();
}
3.1 服务端接口
// MyPersonService.aidl
package com.example.testapt;
// Declare any non-default types here with import statements
import com.example.testapt.bean.Person;
interface MyPersonService {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void addPeople(in Person person);
List<Person> getPeopleList();
}
这里我们声明了一个序列化的对象Person,需要注意的是,Java的基本类型都是支持直接传递的,而对于我们自定义的对象,需要手动将其序列化,并且在对应的aidl接口中进行声明
3.2 自定义序列化对象
package com.example.testapt.bean;
parcelable Person;//声明Person对象被序列化了,否则也不能被AIDL识别
public class Person implements Parcelable {
private int id;
private String name;
protected Person(Parcel in) {
id = in.readInt();
name = in.readString();
}
public Person(int id, String name) {
this.id = id;
this.name = name;
}
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 int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(id);
dest.writeString(name);
}
@Override
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + ''' +
'}';
}
}
3.4 build服务端
声明好相应的aidl文件后,build我们的as工程,可以在build的output目录下看到生成的MyPersonService.java文件
3.5 服务端声明服务
此时我们可以在服务端自定义一个远程服务,对自己声明的服务端接口进行相应的处理,这里我们就对addPeople以及getPeopleList进行相应的处理.
public class AIDLService extends Service {
public static final String TAG = "AIDLService";
private List<Person> personList = new ArrayList<>();
private IBinder iBinder = new MyPersonService.Stub() {
@Override
public void addPeople(Person person) throws RemoteException {
Log.i(TAG, person.toString());
personList.add(person);
}
@Override
public List<Person> getPeopleList() throws RemoteException {
Log.i(TAG, "getPeopleList");
return personList;
}
};
@Override
public void onCreate() {
Log.i(TAG,"onCreate");
super.onCreate();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.i(TAG,"onBind");
return iBinder;
}
@Override
public boolean onUnbind(Intent intent) {
return super.onUnbind(intent);
}
}
3.6 声明服务
最后,我们需要在manifest里注册我们提供的服务
<service
android:name=".AIDLService"
android:enabled="true"
android:exported="true" />
四、AIDL客户端使用接口
4.1 复制服务端接口
首先,我们需要将服务端声明的aidl拷贝到客户端包路径下,并且是完全保留服务器完整包路径.
4.2 build客户端
此时build客户端,也可以在output文件夹下看到生成相应的java代码
4.3 客户端获取服务
我们在bindService获得的ServiceConnection连接对象中连接方法里对IBinder进行相应类型的强转就可以得到服务端的MyPersonService对象了
serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.i(TAG, "onServiceConnected");
myPersonService = MyPersonService.Stub.asInterface(service);
try {
myPersonService.addPeople(new Person(1, "vivian"));
myPersonService.addPeople(new Person(2, "Tom"));
List<Person> peopleList = myPersonService.getPeopleList();
for (int i = 0; i < peopleList.size(); i++) {
Log.i(TAG, peopleList.get(i).toString());
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.i(TAG, "onServiceDisconnected");
}
};
4.4 客户端启动服务
我们用bindService启动服务,需要注意的是,setClassName时指定的服务得是完整路径
binding.fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent service = new Intent();
service.setClassName("com.example.testapt", "com.example.testapt.AIDLService");
bindService(service, serviceConnection, Context.BIND_AUTO_CREATE);
}
});
五、log日志
5.1 客户端日志
2022-10-08 16:12:26.611 14137-14137/com.example.testbinder I/AIDLService--客户端: onServiceConnected
2022-10-08 16:12:26.614 14137-14137/com.example.testbinder I/AIDLService--客户端: Person{id=1, name='vivian'}
2022-10-08 16:12:26.614 14137-14137/com.example.testbinder I/AIDLService--客户端: Person{id=2, name='Tom'}
5.2 服务端日志
2022-10-08 16:12:26.609 13927-13927/com.example.testapt I/AIDLService: onCreate
2022-10-08 16:12:26.610 13927-13927/com.example.testapt I/AIDLService: onBind
2022-10-08 16:12:26.612 13927-13946/com.example.testapt I/AIDLService: Person{id=1, name='vivian'}
2022-10-08 16:12:26.613 13927-13941/com.example.testapt I/AIDLService: Person{id=2, name='Tom'}
2022-10-08 16:12:26.614 13927-13941/com.example.testapt I/AIDLService: getPeopleList
六、AIDL总结
可以看到,AIDL已经隐藏了很多实现细节,我们只要模板化的实现相应的接口,就可以轻松地实现进程间的数据传输.
需要注意的是,我们在获取代理对象时,提供了三种返回
public static com.example.testapt.MyPersonService asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.example.testapt.MyPersonService))) {
return ((com.example.testapt.MyPersonService)iin);
}
return new com.example.testapt.MyPersonService.Stub.Proxy(obj);
}
- 1、如果传入的IBinder是空,返回null,这个很好理解
- 2、如果在同一进程下,那么asInterface()将返回服务端的Stub对象本身,因为此时根本不需要跨进程通信,直接调用Stub对象的接口就可以了,返回的实现就是服务端的Stub实现,没有跨进程通信
- 3、如果不是同一进程,此时返回的是Stub.Proxy对象,该对象持有远程的Binder引用,此时是IPC调用,会通过调用transact方法与服务端通信
可以看到,Service的作用就是为我们创建了Binder驱动,并且添加了判断,如果是同一个进程调用,就返回本地的服务端对象,如果是IPC通信,就提供了一个代理类.