一篇文章搞定《AIDL详解》
前言
首先本文是想用AIDL来实践上一篇的Binder模型C/S架构
本文结构:
1、什么是AIDL
2、AIDL的用法
3、AIDL需要注意的点
什么是AIDL
AIDL,即Android Interface Definition Language,是一种在 Android 平台上用于进程间通信(IPC)的接口定义语言。它允许一个 Android 应用程序的进程与另一个应用程序的进程进行通信,实现进程间数据共享和方法调用。
AIDL 和 ContentProvider 的区别
都是 Android 应用程序的进程与另一个应用程序的进程进行通信他和ContentProvider有什么区别呢?
1、AIDL:AIDL是一种基于接口定义的方式,用于实现不同进程间的通信。它的主要特点如下:
- 强类型:AIDL使用强类型接口定义,可以确保参数和返回值的类型安全性。
- 支持双向通信:除了可以从客户端调用服务端的方法,还可以让服务端调用客户端的方法,实现客户端和服务端的双向通信。
- 支持多线程:AIDL可以在多个线程之间进行通信,可以实现并发调用。
- 灵活性差:AIDL需要手动编写接口定义和实现,更加繁琐一些。
2、ContentProvider:ContentProvider是一种用于实现数据共享的机制,可以让不同应用程序之间共享数据。它的主要特点如下:
- 封装性强:ContentProvider将数据封装在特定的数据存储中(如SQLite数据库,文件等),通过URI对外提供访问。
- 安全性好:通过权限控制和URI的限制,可以对数据进行安全的控制和共享。
- 内容访问统一:通过ContentResolver可以统一访问不同应用程序的数据。
- 对象传输限制:ContentProvider主要用于共享结构化数据,对于复杂对象和跨进程调用则不太方便。
总结:
AIDL适用于需要进行复杂跨进程通信,且需要双向通信或多线程并发调用的场景。比如:它的优点是类型安全和灵活性,但需要手动编写接口定义和实现。
而ContentProvider适用于需要共享数据的场景,它的优点是封装性强和安全性好,但对于复杂对象和跨进程调用较不方便。
项目中的场景
1、控制音乐播放器
一个应用程序需要与音乐播放器服务进行交互,控制音乐的播放、暂停、切换曲目等操作。
在这种情况下,应用程序可以通过定义AIDL接口,将这些操作方法暴露给音乐播放器服务,并通过AIDL进行跨进程通信。这样,应用程序就可以通过AIDL接口调用远程服务提供的方法,实现对音乐播放器的控制。
2、消息推送:
应用程序通常需要与消息推送服务进行通信,以接收和处理来自服务端的推送消息。为了实现这种通信,应用程序可以定义一个AIDL接口,暴露接收消息的方法。然后,应用程序将AIDL接口注册到消息推送服务,使其可以回调应用程序中的方法并传递推送消息。
3、独立进程的WebView:
WebView默认是在应用程序的主进程中运行的,但可以通过设置将其作为独立的进程运行。这样,应用程序可以通过AIDL定义接口,并将接口方法暴露给WebView独立进程。通过AIDL接口,应用程序可以向WebView独立进程发送指令,如加载网页、执行JavaScript等操作。
4、身份验证、进程保活…场景很多
AIDL的用法
接下来用一个例子来说明AIDL的创建过程及用法,此示例借助于两个APP来实现,毕竟在开发中真实的需求也是发生在两个APP中。
例子为:是一个通讯录,在服务端维护一个List用来存放联系人信息,客户端可以通过RPC方式来添加联系人、获取联系列表等功能。
1、创建AIDL
这里我们用创建lib的方式去创建AIDL,这是为了客户端和服务端的相同。只需要都引用这个lib就可以了。为什么要相同,写在后面的问题里了。
- 直接右键创建AIDL,这里我们命名为Contact,之后就生成了如小的目录结构。
- 然后添加AIDL接口文件:
首先新建一个Contact类,通过上面的介绍我们知道,普通的java类是不能在AIDL中使用的,必须要实现Parcelable接口,并在AIDL文件中声明:
Contact.java
/**
* Author: mql
* Date: 2023/7/21
* Describe :
*/
public class Contact {
private int phoneNumber;
private String name;
private String address;
public Contact(int phoneNumber, String name, String address) {
this.phoneNumber = phoneNumber;
this.name = name;
this.address = address;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(phoneNumber);
dest.writeString(name);
dest.writeString(address);
}
private final static Creator<Contact> CREATOR = new Creator<Contact>() {
@Override
public Contact createFromParcel(Parcel source) {
return new Contact(source);
}
@Override
public Contact[] newArray(int size) {
return new Contact[size];
}
};
public Contact(Parcel parcel) {
phoneNumber = parcel.readInt();
name = parcel.readString();
address = parcel.readString();
}
@Override
public String toString() {
return "Contact{" +
"phoneNumber=" + phoneNumber +
", name='" + name + '\'' +
", address='" + address + '\'' +
'}';
}
}
- 声明Contact类
Contact.aidl
package com.example.aidl_lib;
parcelable Contact;
- 创建AIDL接口文件,声明需要暴露给客户端的方法。
IContactsManager.aidl
package com.example.aidl_lib;
import com.example.aidl_lib.Contact;
interface IContactsManager {
int getPhoneNumber(in String name);
String getName(int phoneNumeber);
Contact getContact(int phoneNumber);
List<Contact> getContactList();
boolean addContact(in Contact contact);
}
结构如下:
2、创建Server端
首先需要依赖我们创建的aidl_lib模块。
- 创建一个Service,用于响应客户端的绑定请求,我们将这个Service名为ContactManagerService。
- 接着创建一个内部类,让这个类继承AIDL接口中的Stub类,并实现其抽象方法。在Service中返回这个新建这个类的对象。
详细实现如下:ContactManagerService.java
public class ContactManagerService extends Service {
private final static String TAG = ContactManagerService.class.getSimpleName();
private CopyOnWriteArrayList<Contact> contacts = new CopyOnWriteArrayList<>();
@Override
public void onCreate() {
super.onCreate();
contacts.add(new Contact(110, "报警电话", "派出所"));
contacts.add(new Contact(119, "火警电话", "消防局"));
contacts.add(new Contact(112, "故障电话", "保障局"));
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return new ContactManagerBinder();
}
private class ContactManagerBinder extends IContactsManager.Stub{
/**
* 根据号码返回手机号
* @param name
* @return
* @throws RemoteException
*/
@Override
public int getPhoneNumber(String name) throws RemoteException {
if (!TextUtils.isEmpty(name)) {
for (Contact contact:contacts) {
if (contact.name.equals(name)){
return contact.phoneNumber;
}
}
}
return 0;
}
/**
* 根据号码返回名称
* @param phoneNumber
* @return
* @throws RemoteException
*/
@Override
public String getName(int phoneNumber) throws RemoteException {
for (Contact contact:contacts) {
if (contact.phoneNumber == phoneNumber){
return contact.name;
}
}
return null;
}
/**
* 根据号码返回联系人对象
* @param phoneNumber
* @return
* @throws RemoteException
*/
@Override
public Contact getContact(int phoneNumber) throws RemoteException {
for (Contact contact:contacts) {
if (contact.phoneNumber == phoneNumber) {
return contact;
}
}
return null;
}
/**
* 获取联系人集合
* @return
* @throws RemoteException
*/
@Override
public List<Contact> getContactList() throws RemoteException {
return contacts;
}
/**
* 添加联系人
* @param contact
* @return
* @throws RemoteException
*/
@Override
public boolean addContact(Contact contact) throws RemoteException {
if (contact != null) {
return contacts.add(contact);
}
return false;
}
}
}
- 最后在清单文件中将此Service添加配置,并将export属性设为true以供外界调用:
<service android:name=".aidl.contact.ContactManagerService"
android:exported="true"/>
也就是说Server也就是只是去利用注册服务去让外界使用到相关接口方法。
3、创建Client端
首先需要依赖我们创建的aidl_lib模块。
- 在客户中绑定服务端的Service,绑定成功后就可以在ServiceConnection中的onServiceConnected方法中将返回的Binder对象转换成AIDL接口所属的类型。
首先向Intent指定Component,需要传入两个参数,一个是远程Service所在工程包名,另一个是远程Service的全量限定名,然后使用bindService绑定远程
Service:
Intent intent = new Intent();
intent.setComponent(new ComponentName("cn.codingblock.ipc", "cn.codingblock.ipc.aidl.contact.ContactManagerService"));
bindService(intent, serviceConnection, BIND_AUTO_CREATE);
在serviceConnection中获取返回的Binder并使用IContactsManager.Stub.asInterface()方法将Binder对象转换成IContactsManager类型。
private ServiceConnection serviceConnection = new ServiceConnection(){
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mIContactsManager = IContactsManager.Stub.asInterface(service);
Log.i(TAG, "onServiceConnected: mIContactsManager=" + mIContactsManager);
}
@Override
public void onServiceDisconnected(ComponentName name) {
mIContactsManager = null;
Log.i(TAG, "onServiceDisconnected: ");
}
};
- 拿到Binder对象后就可以调用在AIDL文件中声明的方法了,来看一下完整的代码:
public class ContactMangerActivity extends AppCompatActivity {
private static final String TAG = ContactMangerActivity.class.getSimpleName();
private IContactsManager mIContactsManager;
private EditText et_contact_name;
private EditText et_contact_phone_number;
private EditText et_contact_address;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_contact_manger);
ViewUtils.findAndOnClick(this, R.id.btn_add_contact, mOnClickListener);
ViewUtils.findAndOnClick(this, R.id.btn_get_phone_number, mOnClickListener);
ViewUtils.findAndOnClick(this, R.id.btn_get_name, mOnClickListener);
ViewUtils.findAndOnClick(this, R.id.btn_get_contact, mOnClickListener);
ViewUtils.findAndOnClick(this, R.id.btn_get_list, mOnClickListener);
et_contact_name = ViewUtils.find(this, R.id.et_contact_name);
et_contact_phone_number = ViewUtils.find(this, R.id.et_contact_phone_number);
et_contact_address = ViewUtils.find(this, R.id.et_contact_address);
Intent intent = new Intent();
intent.setComponent(new ComponentName("cn.codingblock.ipc", "cn.codingblock.ipc.aidl.contact.ContactManagerService"));
bindService(intent, serviceConnection, BIND_AUTO_CREATE);
}
private ServiceConnection serviceConnection = new ServiceConnection(){
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mIContactsManager = IContactsManager.Stub.asInterface(service);
Log.i(TAG, "onServiceConnected: mIContactsManager=" + mIContactsManager);
}
@Override
public void onServiceDisconnected(ComponentName name) {
mIContactsManager = null;
Log.i(TAG, "onServiceDisconnected: ");
}
};
private View.OnClickListener mOnClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_add_contact:
Contact contact = new Contact(getEtContactPhoneNumber(), getEtContactName(), getEtContactAddress());
try {
mIContactsManager.addContact(contact);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
case R.id.btn_get_phone_number:
String name = getEtContactName();
try {
Log.i(TAG, "onClick: " + name + "的电话:" + mIContactsManager.getPhoneNumber(name));
} catch (RemoteException e) {
e.printStackTrace();
}
break;
case R.id.btn_get_name:
int number = getEtContactPhoneNumber();
try {
Log.i(TAG, "onClick: " + number + " 对应的名称:" + mIContactsManager.getName(number));
} catch (RemoteException e) {
e.printStackTrace();
}
break;
case R.id.btn_get_contact:
int number1 = getEtContactPhoneNumber();
try {
Contact contact1 = mIContactsManager.getContact(number1);
System.out.println(contact1);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
case R.id.btn_get_list:
try {
List<Contact> contacts = mIContactsManager.getContactList();
System.out.println(contacts);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
}
}
};
private String getEtContactName() {
String str = et_contact_name.getText().toString();
if (TextUtils.isEmpty(str)) {
Toast.makeText(this, "请输入联系人名称", Toast.LENGTH_SHORT).show();
return null;
}
return str;
}
private int getEtContactPhoneNumber() {
String str = et_contact_phone_number.getText().toString();
if (TextUtils.isEmpty(str)) {
Toast.makeText(this, "请输入联系人电话", Toast.LENGTH_SHORT).show();
return 0;
}
return Integer.valueOf(str);
}
private String getEtContactAddress() {
String str = et_contact_address.getText().toString();
if (TextUtils.isEmpty(str)) {
Toast.makeText(this, "请输入联系人地址", Toast.LENGTH_SHORT).show();
return null;
}
return str;
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(serviceConnection);
}
}
AIDL需要注意的点
AIDL支持的数据类型
- 八种基本数据类型:byte、char、short、int、long、float、double、boolean
- String,CharSequence
- List类型:List承载的数据必须是AIDL支持的类型,或者是其它声明的AIDL对象
- Map类型:Map承载的数据必须是AIDL支持的类型,或者是其它声明的AIDL对象
- Parcelable:所有实现了此接口的对象
AIDL文件中的定向tag:in、out、inout的区别
interface IController {
int transIn(in State state);
int transOut(out State state);
int transInOut(inout State state);
}
- 在AIDL文件中,定向标签(direction tag)以in、out和inout的形式出现,用于指定参数的传递方向和访问权限。
- in:服务端收到对象后对此对象做任何修改都不会同步给客户端。换句话说,in标签适用于从客户端向服务端传递数据。
- out:服务端收到对象后对此对象做任何修改都会同步给客户端。换句话说,out标签适用于从服务端向客户端传递数据。
- inout:服务端接受对象后,无论是客户端还是服务端对此对象所做的修改都会两端同步。该标签用于指定参数既可以从客户端传递给服务端,也可以从服务端传递给客户端。在服务端方法中,参数可读可写。
- 总结起来,in标签用于客户端到服务端的单向传递,out标签用于服务端到客户端的单向传递,而inout标签用于双向传递。
如何在AIDL中添加权限校验
其实在正式的开发工作中,我们不希望任何客户端都能绑定我们的服务端,因为这会存在极大安全隐患,所以当客户端想我们发来绑定请求是我们需要做权限校验,符合我们权限要求的客户端才可以与我们的服务端建立链接。
1、在客户端和服务端加入权限
<!--声明权限-->
<uses-permission
android:name="cn.codingblock.permission.ACCESS_CONTACT_MANAGER"/>
<!--定义权限-->
<permission
android:name="cn.codingblock.permission.ACCESS_CONTACT_MANAGER"
android:protectionLevel="normal"/>
2、然后在Service的onBinde方法中进行权限验证,验证不通过就直接返回null。
@Nullable
@Override
public IBinder onBind(Intent intent) {
if (checkCallingOrSelfPermission("cn.codingblock.permission.ACCESS_CONTACT_MANAGER") == PackageManager.PERMISSION_DENIED) {
Log.i(TAG, "onBind: 权限校验失败,拒绝绑定...");
return null;
}
Log.i(TAG, "onBind: 权限校验成功!");
return new ContactManagerBinder();
}
注意:要在客户端和服务端两个工程中都加入以上声明权限和定义权限的代码。
为什么客户端和服务端要使用完全相同的路径和结构
因为客户端需要反序列化服务端中所有和AIDL相关的类,如果类的完整路径不一致就无法反序列化成功。
小技巧:为了更加方便的创建AIDL文件,我们可以新建一个lib工程,让客户端APP和服务端APP同时依赖这个lib,这样只需要在这个lib工程中添加AIDL文件就可以了!
或者创建好一个,另一个直接复制过来。
为什么基本类型的参数只能是in
- 基本类型是值类型,而不是引用类型。
- 如果参数使用了inout或者out标签,意味着该参数是可以被服务端修改的,但是由于基本类型是值类型,无法返回修改后的值。
- 因此,为了避免出现无效的参数传递,基本类型的参数只能使用in标签。
什么是stub
stub在AIDL中充当了消息传递的桥梁,负责将客户端的请求传递给服务端,并将服务端的响应传递回客户端。
简单的来说就是AIDL的出站口
什么是proxy
proxy在AIDL中充当了客户端与服务端交互的代理角色。它实现了AIDL接口,并负责将客户端的方法调用转发给stub对象,以便客户端与服务端进行跨进程通信。
简单的来说就是AIDL的进站口
onTransact是做什么的
onTransact()方法在AIDL服务中是处理客户端请求和服务端响应的核心方法。它负责解析请求数据、执行相应的业务逻辑,并将结果封装为输出Parcel对象返回给客户端。
简单的来说就是接收者
transact是做什么的
transact()方法用于将请求发送给远程Binder对象(例如AIDL生成的stub或proxy对象)。通过该方法,可以实现跨进程的方法调用和数据传输。当客户端调用transact()方法时,它将待调用的方法描述符(descriptor)和参数等信息打包成一个Parcel对象,并通过Binder驱动将Parcel发送给服务端。
简单的来说就是发送着