一、简介Android Interface Definition Language (AIDL)
基于Binder机制封装的Android跨进程通信DSL(领域特定语言),提供Android进程间通信能力。AIDL将复杂的Binder机制进行封装,让业务只需关注方法调用和参数传递。使用步骤如下:
(1)定义 AIDL 接口:在 Android Studio 中创建一个 AIDL 文件,定义需要跨-- 进程调用的方法和参数。方法和参数的声明需要按照 AIDL 的规范进行,包括数据类型、参数方向等;
(2)实现 AIDL 接口:通过继承 AIDL 自动生成的 Stub 类来实现;
(3)在服务中注册 AIDL 接口:Service的onBind()方法中返回步骤2的Stub类实现;
(4)绑定服务:在客户端中绑定服务,通过 IBinder 接口获取 AIDL 对象;
(5)调用远程方法:通过获取的 AIDL 对象调用远程方法实现跨进程通信。
二、AIDL支持的数据类型、文件类型:
1. AIDL支持的数据类型
(1) Java8种基本数据类型:byte、short、int、long、float、double、boolean、char
(参数的定向tag默认为in)
(2) Java常用数据类型:String、CharSequence、List、Map
(参数的定向tag默认为in,并且只能是 in)
(3) aidl文件的interface类型(接口类,用于进程间互相调用、回调)
(4) aidl文件的parcelable类型(数据类,需要配合同包路径的.java
数据类,这个.java
数据类需要实现Parcelable
序列化)
2. AIDL文件有两大类:数据类,接口类(包含两类:交互接口,回调接口)
- 自定义的Parcelable类和AIDL接口在其它AIDL中引用必须显示import
- 自定义的Parcelable类必须新建一个同名的AIDL文件,添加package、parcelable xxxClass
三、AIDL关键字
1.in、out、inout修饰的参数
表示跨进程通信中数据的流向(基本数据类型默认是in,非基本数据类型可以使用其它数据流向out、inout),除基本类型,其它类型参数必须标明方向
in表示数据只能由客户端流向服务端(表现为服务端修改此参数,不会影响客户端的对象)
out表示数据只能由服务端流向客户端(表现为服务端收到的参数是空对象,并且服务端修改对象后客户端会同步变动)
inout则表示数据可在服务端与客户端之间双向流通(表现为服务端能接收到客户端传来的完整对象,并且服务端修改对象后客户端会同步变动)
原理:in会读取客户端传入的对象数据;out会新创建一个对象,而不读取原对象,同时会将新对象返回给客户端;inout则包含前面两步操作。
2.oneway关键字修饰的方法
用于修改远程调用的行为,oneway方法不可以有返回值,也不可以带out、inout参数
- 本地调用(同步调用)
- 远程调用(异步调用,客户端不会被阻塞)
使用oneway时,远程调用不会阻塞,它只是发送事务数据并立即返回。接口的实现最终接收此调用时,是以正常远程调用形式将其作为来自Binder线程池的常规调用进行接收
四、使用举例
1.APK1和APK2两个进程需要进行通信,APK1作为客户端可调用服务端APK2方法且可以传入参数,服务端方法执行过程中可进行回调
- AIDL文件,客户端APK1和服务端APK2保持包名和文件内容一致,aidl文件目录为src->main->aidl,与java目录同级,注意包名与java中保持一致
// IConfigService.aidl
package com.xxx.ipc;
interface IConfigService {
oneway void queryData(String name);
}
- 服务端APK2建立Service类作为IPC服务端
class IPCService : Service() {
private var mBinder = object : IConfigService.Stub() {
override fun queryData(name: String?) {
// 服务端 doSomething
}
}
override fun onBind(intent: Intent): IBinder {
return mBinder
}
}
- 客户端调用服务端
private var iConfigService: IConfigService? = null
// 添加死亡代理
private var mDeathRecipient = object : IBinder.DeathRecipient {
override fun binderDied() {
if (iConfigService == null) {
return
}
iConfigService?.asBinder()?.unlinkToDeath(this, 0)
iConfigService = null
}
}
var serviceConnectionConfig = object : ServiceConnection {
override fun onServiceDisconnected(name: ComponentName?) {
// 与服务端意外断开时回调
}
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
iConfigService = IConfigService.Stub.asInterface(service)
service?.isBinderAlive
service?.linkToDeath(mDeathRecipient, 0)
}
}
// 绑定远程服务
var intent = Intent()
intent.component = ComponentName("服务端进程包名", "服务端Service类完整包名.IPCService")
bindService(intent, serviceConnectionConfig, Context.BIND_AUTO_CREATE)
// ServiceConnection onServiceConnected()回调后 客户端可调用服务端方法
iConfigService?.queryData("hello")
// 解除绑定
unbindService(serviceConnectionConfig)
2.自定义enum Parcelable类 & 服务端回调
// SaveResult.java
package com.xxx.ipc.save.result
import android.os.Parcel
import android.os.Parcelable
enum class SaveResult : Parcelable {
SAVE_SUCCESS, // 保存成功
SAVE_PARAMETER_NULL, // 参数含空
SAVE_FAILED_PATH_ERROR, // 路径错误
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(ordinal)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<SaveResult> {
override fun createFromParcel(parcel: Parcel): SaveResult {
return values()[parcel.readInt()]
}
override fun newArray(size: Int): Array<SaveResult?> {
return arrayOfNulls(size)
}
}
}
每个Parcelable类客户端和服务端都需要相同的文件在工程目录中,这样代码中才能找到对应的类,所示enum类可作为参数传给服务端
// SaveResult.aidl
package com.xxx.ipc.save.result;
parcelable SaveResult;
定义服务端回调接口
// IContentCallback.aidl
package com.xxx.ipc;
import com.xxx.ipc.save.result.SaveResult; // SaveResult为一个自定义的enum parcelable
interface IContentCallback {
void onStart();
void onProgress();
void onSaveResult(in SaveResult result, String saveFullPath);
}
- 服务端Service代码
class IPCService : Service() {
private var mRemoteSaveCallback = RemoteCallbackList<IContentCallback>()
fun sendSaveCallback(result: SaveResult, fullPath: String) {
var callbackNum = mRemoteSaveCallback.beginBroadcast()
for (i in 0 until callbackNum) {
mRemoteSaveCallback.getBroadcastItem(i).onSaveResult(result, fullPath)
}
mRemoteSaveCallback.finishBroadcast()
}
private var mBinder = object : IConfigService.Stub() {
override fun addSaveCallback(saveCallback: IContentCallback?) {
mRemoteSaveCallback.register(saveCallback)
}
override fun removeSaveCallback(saveCallback: IContentCallback?) {
mRemoteSaveCallback.unregister(saveCallback)
}
}
override fun onBind(intent: Intent): IBinder {
return mBinder
}
}
- 客户端实现回调代码
private var iContentCallback = object : IContentCallback.Stub() {
override fun onSaveResult(SaveResult result, String saveFullPath) {
// do something
}
}
// ServiceConnection onServiceConnected()回调后执行
iConfigService?.addSaveCallback(iContentCallback) // 添加回调
iConfigService?.removeSaveCallback(iContentCallback) // 移除回调
五、原理
通过将接口的定义文件(.aidl 文件)编译成 Java 代码,生成一个实现 android.os.IInterface 接口的 Binder 对象,使得客户端可以通过获取这个 Binder 对象来调用服务端中的方法实现进程间通信。IInterface 接口是一个标准的 Android 接口,所有使用 Binder 进行进程间通信的服务端都需要实现这个接口。
六、注意事项
1.AIDL文件使用英文注释(测试使用Gradle5.4.1能使用中文注释,更高版本待测试);
2.AIDL接口文件不可以定义重载方法,即不能定义两个方法名相同的方法,即使参数个数、类型不一致也不行;
3.避免混淆AIDL文件;
4.在使用AIDL进行跨进程通信的过程中,需要使用Parcelable 或 Serializable 的数据类型进行参数传递,AIDL文件及自定义的Parcelable类客户端和服务端保持文件内容和包名一致;
5.Parcelable类在main-java目录中,对应的AIDL文件在main-aidl中,两个文件包名需一致;
6.AIDL文件、Parcelable类可打包为aar包提供给客户端作为SDK,放入module下libs目录,通过implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
导入
7.AIDL被调用的过程中可能会发生异常,需做好异常处理逻辑
网友评论