AIDL(Android Interface Definition Language)是Android系统自定义的接口描述语言。可以用来实现进程间的通讯。

在 Android 中,要实现进程间的通讯,一般来说,有以下几种方式:

使用 Bundle

最常见的的是我们通过特定的 Action 或者 data 启动另外一个应用的 Activity 或者 service。我们可以将要传递的数据封装在 bundle 当中。

文件共享

两个应用读取某个文件,从而达到进程通讯的问题,不过这种方法需要处理好文件锁的问题,不然很容易引发数据错乱。

使用 Messenger

Messenger 进行进程间的通讯是串行的,而且是单向的,如果客户端和服务端想进行双向通讯,需要维护两个 Messenger,相对比较麻烦

使用 AIDL

AIDL 简介

aidl 一般用来进程通讯。一般来说,主要有两种角色,客户端 (Client)和服务端(Server)。

服务端

一般用来处理客户端的请求,他把与客户端通讯的方式抽象成接口,并编写成 AIDL 文件。

通常服务端需要实现一个 Service,来处理客户端的请求

客户端

通常我们需要将服务端 的 AIDL 文件 copy 过来,并通过 Intent 的方式来启动我们服务端的 Service。

接下来让我们一起来实现一个简单功能,通过一哥 app(Clilent) 唤起另外一个 APP (Server),并进行两者之间的通讯。首先,我们先来看一下服务端的实现。

服务端的实现

服务端的实现,一般来说,需要以下步骤:

将请求抽象成接口,并编写 aidl 文件;

编写一个 Service,实现接口,处理客户端的请求,并将 binder 返回回去;

在 AndroidManifet 配置 Service,将我们的 Service 暴露出去。

将请求抽象成接口,编写 aidl 文件

一般来说, AIDL 文件支持以下类型

Java 编程语言中的所有原语类型(如 int、long、char、boolean 等等)

String

CharSequence

List

List 中的所有元素都必须是以上列表中支持的数据类型、其他 AIDL 生成的接口或您声明的可打包类型。 可选择将 List 用作“通用”类(例如,List)。另一端实际接收的具体类始终是 ArrayList,但生成的方法使用的是 List 接口。

Map

Map 中的所有元素都必须是以上列表中支持的数据类型、其他 AIDL 生成的接口或您声明的可打包类型。 不支持通用 Map(如 Map 形式的 Map)。 另一端实际接收的具体类始终是 HashMap,但生成的方法使用的是 Map 接口。

实现 Parceable 的自定义类型

注意事项

在 aidl 文件中,除了 Java 编程语言中的所有原语类型、String、CharSequence、List、Map,其他在 AIDL 文件中用到的类,你必须使用 import 语句导入,否则会报错。

当你使用实现Parceable 的自定义类型的时候,当其作为参数的时候,你必须为其制定是输入或者是输出参数。in 表示输入参数,即服务端可以修改该类型

out 表示输出参数,即客户端可以修改该类型,客户端不行

inout 表示客户端和服务端都可以修改该类型

void onSuccess(int code,in MusicInfo musicInfo);

有人可能会这样想,既然 inout 表示客户端和服务端都可以修改该类型,那我们平时在写 aidl 文件的时候,直接在方法参数前面加上 inout 修饰就 OK了,省得去区分。

这样做法当然不行,既然双方都可以修改,那系统的开销肯定会比较大。就好比管道一样。

说了这么多,接下来让我们一起来看一下例子 IEasyService.aidl

package xj.musicserver.easy;

// Declare any non-default types here with import statements

interface IEasyService {

/**

* Demonstrates some basic types that you can use as parameters

* and return values in AIDL.

*/

void connect(String mes);

void disConnect(String mes);

}

这个 aidl 文件很接口,只有两个方法,connect 和 disConnect 方法。

0135236f348a57995e12abc804139d67.png

这里我们把 aidl 文跟 Java 文件中放在一起,需要在 build.gradle 中配置

sourceSets {

main {

jniLibs.srcDirs = ['libs']

aidl.srcDirs = ['src/main/java']

}

}

关于怎样在 AndroidStudio 中引用 aidl 文件的,可以参考我的这一篇文章 AndroidStudio 引用 aidl 文件的两种方法

编写一个 Service,实现接口,处理客户端的请求,并将接口返回回去

public class EasyService extends Service {

private static final String TAG = "EasyService";

public EasyService() {

}

IEasyService.Stub mIBinder=new IEasyService.Stub() {

@Override

public void connect(String mes) throws RemoteException {

LogUtil.i(TAG,"connect: mes =" + mes);

}

@Override

public void disConnect(String mes) throws RemoteException {

LogUtil.i(TAG, "disConnect: mes =" +mes);

}

};

@Override

public IBinder onBind(Intent intent) {

LogUtil.i(TAG,"onBind: intent = "+intent.toString());

return mIBinder;

}

@Override

public boolean onUnbind(Intent intent) {

LogUtil.i(TAG,"onUnbind: =");

return super.onUnbind(intent);

}

@Override

public void onDestroy() {

LogUtil.i(TAG,"onDestroy: =");

super.onDestroy();

}

}

这个 Service 所做的动作就是当客户端连接上的时候,会启动我们的 Service,此时会调用 Service 的 onBInd 方法,在 onBinder 方法里面,我们将 mIBinder (实现了 IEasyService.Stub 接口)返回回去。他充当客户端和服务端的桥梁,通过他我们可以进行通讯。

至于这个 IEasyService.Stub 是什么呢?其实当我们在 AndroidStudio 里面编写完 aidl 文件,重新 make project 一下,就会自动生成了。

将服务端的 Service 暴露出去

在 AndroidManifest 文件下,配置 Service 的 action 及 exported 等信息。

其中 android:exported=“true” 表示别的进程可以访问,这个是必须配置的。android:process=":remote" 表示运行在 :remote 进程,不配置的话默认运行所在的 App 进程,这个可以不配置。

android:name=".easy.EasyService"

android:enabled="true"

android:exported="true"

android:process=":remote">

客户端的实现

将服务端的 aidl 文件 copy 过来,注意要放在同一个包下。

通过服务端 Service 的 Action 启动, 当启动 Service 成功的时候,将服务端返回的 Binder 保存下来并转化成相应的实例。

之后如果想与服务端通讯,通过保存下来的 Binder,即可调用服务端的方法。

第一步:将服务端的 aidl 文件 copy 过来,注意要放在同一个包下

79952053266af15786f56a555de2c6dd.png

第二步,通过服务端 Service 的 Action 启动, 当启动 Service 成功的时候,将服务端返回的 Binder 保存下来并转化成相应的实例

当我们点击按钮的时候,我们通过 Action 去启动远程 servic。

case R.id.btn_start_service:

LogUtil.i(TAG,"onButtonClick: btn_start_service=");

Intent intent = new Intent(ACTION);

// 注意在 Android 5.0以后,不能通过隐式 Intent 启动 service,必须制定包名

intent.setPackage(XJ_MUSICSERVER);

bindService(intent,mServiceConnection, Context.BIND_AUTO_CREATE);

private static final String ACTION = "xj.musicserver.easy.IEasyService";

private IEasyService mIEasyService;

ServiceConnection mServiceConnection=new ServiceConnection() {

@Override

public void onServiceConnected(ComponentName name, IBinder service) {

mIEasyService = IEasyService.Stub.asInterface(service);

}

@Override

public void onServiceDisconnected(ComponentName name) {

mIEasyService=null;

}

};

测试将会启动远程 Service,及服务端的 EasyService,并调用 onBind 方法,打印出相应信息。

xj.musicserver:remote I/MusicInterface: EasyService :onBind: intent = Intent { act=xj.musicserver.easy.IEasyService pkg=xj.musicserver }

第三步:如果想与服务端通讯,通过保存下来的 Binder,即可调用服务端的方法。

比如当我们点击按钮的时候,调用 connect 方法。

case R.id.btn_contact:

LogUtil.i(TAG,"onButtonClick: btn_contact=");

if(mIEasyService!=null){

mIEasyService.connect(" Cilent connect");

}

此时将会调用远程 service 的 mIbinder 的 connect 方法,log 中输出

xj.musicserver:remote I/MusicInterface: EasyService :connect: mes = Cilent connect

注意事项

客户端和服务端的 aidl 文件必须放在同一个目录下

关于怎样在 AndroidStudio 中,配置 aidl 的,可以参看 AndroidStudio 引用 aidl 文件的两种方法

推荐阅读

扫一扫,欢迎关注我的微信公众号 stormjun94, 目前专注于 Android 开发,主要分享 Android开发相关知识和技术人成长历程,包括个人总结,职场经验,面试经验等。

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐