使用AIDL进程间通信

前言

Android 进程间通信的方式之一就是使用AIDL进行,本文详细介绍如何使用AIDL的方式进行进程间通信

什么是AIDL

AIDL 全称 Android Interface Definition Language, 翻译以后就是 Android接口定义语言。在官网中这么描述

它允许您定义客户端和服务都同意的编程接口,以便使用进程间通信(IPC)相互通信

创建AIDL文件

如果使用AIDL做进程间通信.在google官网中也给我演示了如何使用,下面从基础类型演示演示如何正确的使用AIDL进程间通信

在项目目录右击,选择 New 在选择 AIDL 如👇所示,即可创建出一个 .aidl 结尾的文件

创建出来的 .aidl 文件 是和 java , res 同级 如👇所示,示例中创建的AIDL文件最终的目录位置在 com.starot.lib_intent.aidl

在我们创建的aidl 文件中,定义我们需要的接口。例如

1
2
3
4
5
6
7
8
// ITest.aidl
package com.starot.lib_intent.aidl;

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

interface ITest {
String add(in String name, int a, int b);
}

书写完以后,一定要rebuild一下项目.

Rebuild SUCCESS 之后,会在build 下看到 Android Studio 给我们生成的 由AIDL 文件生成的 Java interface 接口

到此。AIDL文件的创建过程也就OVER了.

服务端设置

官网中的示例,是创建一个Service,用来提供方法给客户端的调用,看一下如何实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//step1:继承Service 
class RemoteService : Service() {

//step2:继承我们rebuild 之后生成的 Stub 实现 定义在AIDL接口中对外的方法
class MyBinder : ITest.Stub() {
override fun add(name: String, a: Int, b: Int): String {
return name + ":" + (a + b)
}
}

private var binder: MyBinder? = null
//step3:重载Service onCreate方法,将Binder对象创建出来
override fun onCreate() {
super.onCreate()
binder = MyBinder()
}


override fun onBind(intent: Intent?): IBinder? {
//step4:将创建的binder对象返回
return binder
}
}

写完服务以后,需要在xml 注册,因为示例是在两个不同的app之间进行,所以不需要写process去指定进程.
exported的作用是其他应用程序的组件是否可以调用该服务或与之交互,这里设置成true
permission属于自定义权限,主要是为了消掉👇的警告。同时因为是自定义权限,需要声明。在使用Messenger进程间通信一文中,关于这个问题,还专门请教了朱凯

1
2
3
4
5
6
7
8
9
10
11
12
13
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.starot.lib_intent">
<!-- 申明权限的级别和描述 -->
<permission android:name="com.starot.lib_intent.CUSTOME_PERMISSION"
android:description="@string/permission_des"
android:protectionLevel="signature"/>
<application>
<service android:name=".aidl.RemoteService"
android:enabled="true"
android:exported="true"
android:permission="com.starot.lib_intent.CUSTOME_PERMISSION"/>
</application>
</manifest>

到这里,服务端的代码基本上就告一段落了。

客户端设置

写完了服务端,客户端就是复制粘贴的事情,回看上文我们创建的AIDL文件的目录 com.starot.lib_intent.aidl ,将其完整的复制到客户端的项目中,一定要注意目录位置不能错.

复制粘贴完成以后,不能忘记需要对客户端的项目Rebuild,原因和上面一样,我们需要Android Studio 为我们生成好需要的Stub对象

建立连接

官网中也明确说明了

注意:如果您使用意图绑定到 Service,请使用显式 意图确保您的应用程序安全 。使用隐式意图启动服务会带来安全隐患,因为您无法确定哪个服务将对意图做出响应,并且用户无法看到哪个服务会启动。从Android 5.0(API级别21)开始,如果您bindService() 使用隐式意图进行调用,系统将引发异常。

所有示例中并没有加入 action,和 category,而是指定bind具体哪个service.在使用Messenger进程间通信一文中也是使用的指定servcie 的方式去绑定

1
2
3
4
5
6
val intent = Intent()
intent.component =
//参数1 服务端的appid,
//参数2 服务端需要绑定服务的完整路劲
ComponentName("com.allens.intent", "com.starot.lib_intent.aidl.RemoteService")
bindService(intent, connect, BIND_AUTO_CREATE)

bindService第二个参数中,我们需要传入连接的接口,在连接成功以后,需要通过 生成的Stub对象的asInterface将返回的参数转换成 YourServiceInterface 类型,这里的示例是转成 ITest 类型

1
2
3
4
5
6
7
8
9
10
11
private var aidl: ITest? = null
private val connect = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
//绑定服务成功回调
aidl = ITest.Stub.asInterface(service);
}

override fun onServiceDisconnected(name: ComponentName?) {
aidl = null
}
}

传递参数

到这里的传递参数,就是方法的调用了。

1
val data =  aidl?.add("加法计算", 10, 20)

传递自定义的数据格式

上面的示例,使用了 基础类型,String,int 除此之外还支持其他的基础类型 更更多详细信息可以查看官网文档

如果想自定义类型,官网也提供了解决办法,通过 IPC 传递对象

创建自定义对象

第一步肯定先创建一个自定义的对象,按照官网提示,还需要进行序列化,下面的示例将实体类放在了 java 目录下 com.starot.lib_intent.aidl下面 如👇所示。

放上完整的代码,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package com.starot.lib_intent.aidl

class BookInfo() : Parcelable {
var name: String? = null
var index: Int = 0

companion object CREATOR : Parcelable.Creator<BookInfo> {
override fun createFromParcel(parcel: Parcel): BookInfo {
return BookInfo(parcel)
}

override fun newArray(size: Int): Array<BookInfo> {
return Array(size) { BookInfo() }
}
}

public constructor(inParcel: Parcel) : this() {
readFromParcel(inParcel)
}

override fun writeToParcel(outParcel: Parcel, flags: Int) {
outParcel.writeString(name)
outParcel.writeInt(index)
}

public fun readFromParcel(inParcel: Parcel) {
name = inParcel.readString()
index = inParcel.readInt()
}

override fun describeContents(): Int {
return 0
}
}

创建AIDL文件

对,没看错,这个时候还需要创建一个AIDL文件,而且需要在你之前使用的AIDL位置同一个目录

画重点 👆👆👆

示例中创建了一个BookInfo.aidl的文件

1
2
3
4
5
//注意👇  和之前写的在同一级目录
package com.starot.lib_intent.aidl;

//注意小写 👇
parcelable BookInfo;

使用新创建的AIDL文件

创建了需要在进程间传递的对象,按照上面基础类型的方法,我们还需要添加其他方法去做进程间的数据交互.修改之前创建的ITest.aidl 添加方法,注意看到添加的方法中,都在前面加上了 in out inout .这个放在后面说。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ITest.aidl
package com.starot.lib_intent.aidl;

//导入对象的包名
import com.starot.lib_intent.aidl.BookInfo;


interface ITest {
String add(in String name, int a, int b);

BookInfo updateBookIn(in BookInfo book);
BookInfo updateBookOut(out BookInfo book);
BookInfo updateBookInOut(inout BookInfo book);
}

复制到客户端

又是老套路,将刚刚写好的 ITest.aidl BookInfo.aidl以及创建的实体类BookInfo.kt全部复制到客户端,依然需要注意。需要在相同的目录位置下

划重点 👆

传递自定义对象

这里就和基础类型没什么区别,通过方法调用就行了

1
2
3
4
5
var bookInOut = BookInfo().apply {
name = "allens"
index = currentIndex
}
val updateBookInOut = aidl?.updateBookInOut(bookInOut)

in out inout 区别

在上面我们在使用自定义类型的时候,发现在所有的自定义类型前面都加上了in out inout 这样的小tag,eumm 如果不加会有问题么?试了一下

直接抛出错误。在官网中其实对这个也有说明

所有非原语参数均需要指示数据走向的方向标记

为什么要加上呢??

官网给出的解释

您应将方向限定为真正需要的方向,因为编组参数的开销较大

有什么区别呢

在客户端将创建好的BookInfo对象设置初始值 name 为 allens,index为1,然后通过AIDL 同步到服务端,在服务端修改其属性值,在客户端中查看成员变量属性

客户端代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var bookIn = BookInfo().apply {
name = "allens"
index = 1
}
aidl?.updateBookIn(bookIn)
Log.i(TAG,"客户端收到 in name:${bookIn.name} index:${bookIn.index}")


var bookOut = BookInfo().apply {
name = "allens"
index = 1
}
idl?.updateBookOut(bookOut)
Log.i(TAG,"客户端收到 out name:${bookOut.name} index:${bookOut.index}")

var bookInOut = BookInfo().apply {
name = "allens"
index = 1
}
aidl?.updateBookInOut(bookInOut)
Log.i(TAG,"客户端收到 inout name:${bookInOut.name} index:${bookInOut.index}")

服务端代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
override fun updateBookIn(book: BookInfo): BookInfo {
Log.i(TAG,"服务端收到 in name:${book.name} index:${book.index}")
return book.apply {
name = "江海洋"
index = 100
}
}

override fun updateBookOut(book: BookInfo): BookInfo {
Log.i(TAG,"服务端收到 out name:${book.name} index:${book.index}")
return book.apply {
name = "江海洋"
index = 100
}
}

override fun updateBookInOut(book: BookInfo): BookInfo {
Log.i(TAG,"服务端收到 inout name:${book.name} index:${book.index}")
return book.apply {
name = "江海洋"
index = 100
}
}

执行以后查看log打印

1
2
3
4
5
6
7
8
com.allens.intent I: 服务端收到 in name:allens index:1
com.allens.demo I: 客户端收到 in name:allens index:1

com.allens.intent I: 服务端收到 out name:null index:0
com.allens.demo I: 客户端收到 out name:江海洋 index:100

com.allens.intent I: 服务端收到 inout name:allens index:1
com.allens.demo I: 客户端收到 inout name:江海洋 index:100

通过打印能够看出,in 类型的数据,是允许数据从客户端流向向服务端,但是不允许服务端向客户端流,如下所示

out 类型和in类型相反,是允许数据从服务端流向客户端,但是不允许客户端流向服务端

inout则是 都可以流向