持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第 2 天,点击查看活动详情
00. 前言
AIDL,是什么?做 Android 开发没用过,但至少都听过,或者在面试中遇到过(请问:你了解到有什么进程间通信的方式吗?)
在过去的开发中,我也并未实际上用过这个东西,就是有种纸上得来终觉浅。直到现在做车机开发,这可谓是一项基础功!于是借此机会来跟大家谈谈 AIDL 到底是什么?实际怎么用?以及用的过程中会有哪些问题?和一些优化的想法。
01. AIDL 是什么?
AIDL 全称是 Android Interface Definition Language,也就是 Android 接口定义语言。那这是用来干嘛的呢?前言中也提到了,就是用来做进程间通信的。
那么在实现 AIDL 之前,我们还需要掌握绑定服务的内容。因为就是通过绑定服务的形式来进行通信的。
02. 怎么用
第一步、创建 AIDL
在与 java 同级目录下创建后缀为 .aidl 的文件,如下图所示:
看看生成的 .aidl 文件
这里有两点提示,我们先看标识 2
- 默认情况下,AIDL 仅支持部分数据类型。
那这里的部分数据类型包括:Java 编程语言中的所有原语类型(如 int、long、char、boolean 等)String、CharSequence、 List 、Map。
如果仅仅是支持这些类型,是很难满足我们的需求的,所以有了标识 1。
- 即如果我们用到了非默认的变量类型(上述类型),那么我们需要 import 进来。
如何使用自定义的数据类型?
第一步
在 java 包下创建一个我们自定义的数据类型- DemoModel ,并实现 Parcelable 接口。支持 Parcelable 接口很重要,因为 Android 系统能通过该接口将对象分解成可编组至各进程的原语。
第二步
在 aidl 目录下同样包名下,声明这个数据类型
这样我们就可以在 IMyAidlInterface
的方法中使用这个数据类型的。
但我们还需要为其声明数据走向的方向标识(这是必须的)in、out、inout 三种类型。
这三种类型分别代表着:
-
in:只能由客户端传向服务端。
-
out:只能由服务端传向客户端。
-
inout:双向。
Tips:应该将方向限定为实际需求的方向,因为编组参数的开销比较大。
此时, Make 一遍这个项目,就能在 build 包下找到同名的 java 文件了。实际上,在真正通信的过程中,就是它在发挥着作用。
第二步、在服务端实现接口
新建一个服务,在服务里面实现,最后在 onBind()
返回出去。这样其他进程成功绑定这个服务后,便可以拿到这个对象了。
此时,服务端的工作就已经完成了,进入客户端的工作了。
第三步、客户端调用
在通过服务绑定之前,我们还需要先将两个东西(一个是整个 aidl 的包,一个是用到的 model),直接 cv(复制)到客户端的项目中
然后在客户端通过绑定服务的形式,绑定到服务端的服务上。此时遇到一个问题,我在两个项目中分别写 Server 和 Client 端,然后通过下面的代码绑定服务,一直未成功。
const val REMOTE_ACTION = "com.qingqing.demo.aidlserver.IMyAidlInterface"
const val REMOTE_PACKAGE = "com.qingqing.demo.aidlserver"
val serviceIntent = Intent(REMOTE_ACTION)
serviceIntent.setPackage(REMOTE_PACKAGE)
bindService(serviceIntent, mConnection, BIND_AUTO_CREATE)
截止截稿前,仍未解决。后期解决后,会及时更新。所以暂时统一在一个项目中,但是给 Service 单独设置了进程,以便来模拟进程间通信。
于是通过,下面的方式绑定服务
private val mConnection = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) {
Log.i(TAG, "onServiceConnected: ")
imai = IMyAidlInterface.Stub.asInterface(service)
Log.i(TAG, "onServiceConnected: 拿到了服务端的实现!$imai")
//调用服务端提供的 nonDefaultType 方法,并在服务端打印出回去的 Name 和 Id
val demoModel = DemoModel()
demoModel.mName = "Quincy"
demoModel.mId = 930135849
imai?.nonDafultType(demoModel)
}
override fun onServiceDisconnected(className: ComponentName) {
imai = null
}
}
看一下日志,红色框是客户端,进程号为 29618;绿色框是服务端,进程号为:29665
到这里,两个进程间就算正式通信完成了~
03. 遇到的问题
经历了上面的流程后,我们可以明显感受到一个最大的问题,那就是在客户端使用服务端定义的接口时,需要把有关的 aidl 和涉及到的类复制过来。如果服务端修改后,客户端也要及时更新有关内容。否则容易出现异常。或者,我们在修改的时候,需要考虑向后兼容性,避免原本使用服务的应用出现异常。
目前有一个想法,就是将所有 aidl 放到一个项目中,然后放到 maven 上,其他应用通过依赖的方式来获取有关内容。这样每次修改后,客户端只需要同步即可使用最新内容,但暂未实践。有关想法欢迎交流~
04. 结语
本文主要通过具体案例,展示如何通过 AIDL 进行进程间通信。下一篇文章将会浅析 AIDL 底层具体是如何实现通信的。有需要源码的,评论区回复。不过建议,源码用于调试后,自己再重新写一次。