【Android】一文讲清楚如何使用 AIDL

1,156 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第 2 天,点击查看活动详情

00. 前言

AIDL,是什么?做 Android 开发没用过,但至少都听过,或者在面试中遇到过(请问:你了解到有什么进程间通信的方式吗?)

在过去的开发中,我也并未实际上用过这个东西,就是有种纸上得来终觉浅。直到现在做车机开发,这可谓是一项基础功!于是借此机会来跟大家谈谈 AIDL 到底是什么?实际怎么用?以及用的过程中会有哪些问题?和一些优化的想法。

01. AIDL 是什么?

AIDL 全称是 Android Interface Definition Language,也就是 Android 接口定义语言。那这是用来干嘛的呢?前言中也提到了,就是用来做进程间通信的。

那么在实现 AIDL 之前,我们还需要掌握绑定服务的内容。因为就是通过绑定服务的形式来进行通信的。

02. 怎么用

第一步、创建 AIDL

在与 java 同级目录下创建后缀为 .aidl 的文件,如下图所示:

image.png

看看生成的 .aidl 文件

image.png

这里有两点提示,我们先看标识 2

  • 默认情况下,AIDL 仅支持部分数据类型。

那这里的部分数据类型包括:Java 编程语言中的所有原语类型(如 intlongcharboolean 等)StringCharSequenceList 、Map

如果仅仅是支持这些类型,是很难满足我们的需求的,所以有了标识 1。

  • 即如果我们用到了非默认的变量类型(上述类型),那么我们需要 import 进来。

如何使用自定义的数据类型?

第一步

java 包下创建一个我们自定义的数据类型- DemoModel并实现 Parcelable 接口。支持 Parcelable 接口很重要,因为 Android 系统能通过该接口将对象分解成可编组至各进程的原语。

image.png

第二步

aidl 目录下同样包名下,声明这个数据类型

image.png

这样我们就可以在 IMyAidlInterface 的方法中使用这个数据类型的。

但我们还需要为其声明数据走向的方向标识(这是必须的)inoutinout 三种类型。

这三种类型分别代表着:

  • in:只能由客户端传向服务端。

  • out:只能由服务端传向客户端。

  • inout:双向。

Tips:应该将方向限定为实际需求的方向,因为编组参数的开销比较大。

image.png

此时, Make 一遍这个项目,就能在 build 包下找到同名的 java 文件了。实际上,在真正通信的过程中,就是它在发挥着作用。

image.png

第二步、在服务端实现接口

image.png

新建一个服务,在服务里面实现,最后在 onBind() 返回出去。这样其他进程成功绑定这个服务后,便可以拿到这个对象了。

此时,服务端的工作就已经完成了,进入客户端的工作了。

第三步、客户端调用

在通过服务绑定之前,我们还需要先将两个东西(一个是整个 aidl 的包,一个是用到的 model),直接 cv(复制)到客户端的项目中

image.png

然后在客户端通过绑定服务的形式,绑定到服务端的服务上。此时遇到一个问题,我在两个项目中分别写 ServerClient 端,然后通过下面的代码绑定服务,一直未成功。

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

image.png

到这里,两个进程间就算正式通信完成了~

03. 遇到的问题

经历了上面的流程后,我们可以明显感受到一个最大的问题,那就是在客户端使用服务端定义的接口时,需要把有关的 aidl 和涉及到的类复制过来。如果服务端修改后,客户端也要及时更新有关内容。否则容易出现异常。或者,我们在修改的时候,需要考虑向后兼容性,避免原本使用服务的应用出现异常。

目前有一个想法,就是将所有 aidl 放到一个项目中,然后放到 maven 上,其他应用通过依赖的方式来获取有关内容。这样每次修改后,客户端只需要同步即可使用最新内容,但暂未实践。有关想法欢迎交流~

04. 结语

本文主要通过具体案例,展示如何通过 AIDL 进行进程间通信。下一篇文章将会浅析 AIDL 底层具体是如何实现通信的。有需要源码的,评论区回复。不过建议,源码用于调试后,自己再重新写一次。