前言 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 package com.starot.lib_intent.aidl;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 class RemoteService : Service() { class MyBinder : ITest.Stub() { override fun add (name: String, a: Int, b: Int) : String { return name + ":" + (a + b) } } private var binder: MyBinder? = null override fun onCreate () { super .onCreate() binder = MyBinder() } override fun onBind (intent: Intent?) : IBinder? { return binder } }
写完服务以后,需要在xml 注册,因为示例是在两个不同的app之间进行,所以不需要写process
去指定进程.exported
的作用是其他应用程序的组件是否可以调用该服务或与之交互,这里设置成truepermission
属于自定义权限,主要是为了消掉👇的警告。同时因为是自定义权限,需要声明。在使用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 = 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.aidlclass 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 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则是 都可以流向