一、概述

首先我们看下谷歌官方对AIDL的介绍
AIDL(Android Interface Defination Language):安卓接口定义语言。在 Android 中,一个进程通常无法访问另一个进程的内存。因此,为进行通信,进程需将其对象分解成可供操作系统理解的原语,并将其编组为可供您操作的对象。编写执行该编组操作的代码较为繁琐,因此 Android 会使用 AIDL 为您处理此问题。

总而言之,如果在一个线程中要调用另一个进程中对象的方法,可使用AIDL生成可序列化的参数,AIDL会生成一个服务端对象的代理类,通过它,客户端可以实现简介调用服务端对象的方法。

二、AIDL原理

2.1、调用图

aidl android 系统类 android aidl详解_aidl android 系统类

2.2、关键类和方法

AIDL本质是系统提供了一套可以快速实现Binder的工具。关键类和方法:

  • AIDL接口:继承IInterface
  • Stub类:Binder的实现类,服务端通过这个类来提供服务,通常由编译器生成。
  • Proxy类:服务端的本地代理,客户端通过这个类来调用服务端的方法。通过asInterface方法获得stub接口的实例
  • 客户端调用:将服务端返回的Binder对象,转换成客户端所需要的AIDL接口类型对象,如果客户端与服务端位于同一进程,则直接返回Stub对象本身,否则返回系统封装后的Stub.proxy对象
  • asBinder:运行在服务端的Binder线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法来处理。
  • transact():运行在客户端,当客户端发起远程请求的同时将当前线程挂起。之后调用服务端的onTransact()直到远程请求返回,当前线程才继续执行。

三、使用AIDL的方法(以下内容均来自谷歌官网)

主要有以下三个步骤:

3.1、 创建.aidl文件

AIDL文件的编写是通过Java构建,文件的编写更像是在编写一个接口文件,只需要接口声明和方法签名,不同之处在于部分数据类型上的差异,AIDL支持以下数据类型:

  • Java 编程语言中的所有原语类型(如 int、long、char、boolean 等)
  • String
  • CharSequence
  • ListList 中的所有元素必须是以上列表中支持的数据类型,或者您所声明的由 AIDL 生成的其他接口或 Parcelable 类型。您可选择将 List 用作“泛型”类(例如,List)。尽管生成的方法旨在使用 List 接口,但另一方实际接收的具体类始终是 ArrayList
  • MapMap 中的所有元素必须是以上列表中支持的数据类型,或者您所声明的由 AIDL 生成的其他接口或 Parcelable 类型。不支持泛型 Map(如 Map<String,Integer> 形式的 Map)。尽管生成的方法旨在使用 Map 接口,但另一方实际接收的具体类始终是 HashMap

定义方法时,需要注意:

  • 方法可带零个或多个参数,返回值或空值。
  • 所有非原语参数均需要指示数据走向的方向标记。这类标记可以是 inoutinout(见下方示例)。
    原语默认为in,不能是其他方向。
  • 生成的 IBinder 接口内包含 .aidl 文件中的所有代码注释(import 和 package 语句之前的注释除外)。
  • 可以在 AIDL 接口中定义 String 常量和 int 字符串常量。例如:const int VERSION = 1;
  • 方法调用由 transact() 代码分派,该代码通常基于接口中的方法索引。由于这会增加版本控制的难度,因此您可以向方法手动配置事务代码:void method() = 10;
  • 使用 @nullable 注释可空参数或返回类型。
    以下是.aidl文件示例
// IRemoteService.aidl
package com.example.android

// Declare any non-default types here with import statements
/** Example service interface */
internal interface IRemoteService {
    /** Request the process ID of this service, to do evil things with it. */
    val pid:Int

    /** Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    fun basicTypes(anInt:Int, aLong:Long, aBoolean:Boolean, aFloat:Float,
                 aDouble:Double, aString:String)
}

您只需将 .aidl 文件保存至项目的 src/ 目录内,这样在构建应用时,SDK 工具便会在项目的 gen/ 目录中生成 IBinder 接口文件。生成文件的名称与 .aidl 文件的名称保持一致,区别在于其使用 .java 扩展名(例如,IRemoteService.aidl 生成的文件名是 IRemoteService.java)。

3.2、实现接口

当构建应用时,Android SDK 工具会生成以 .aidl 文件命名的 .java 接口文件。生成的接口包含一个名为 Stub 的子类(例如,YourInterface.Stub),该子类是其父接口的抽象实现,并且会声明 .aidl 文件中的所有方法,上一章提到的asInterface方法也是在该Java文件中生成。

到这里我们只是完成接口文件的编写,如要实现 .aidl 生成的接口,请扩展生成的 Binder 接口(即Stub子类),并实现继承自 .aidl 文件的方法。

以下示例展示使用匿名实例实现 IRemoteService 接口(由以上 IRemoteService.aidl 示例定义)的过程:

private val binder = object : IRemoteService.Stub() {

    override fun getPid(): Int =
            Process.myPid()

    override fun basicTypes(
            anInt: Int,
            aLong: Long,
            aBoolean: Boolean,
            aFloat: Float,
            aDouble: Double,
            aString: String
    ) {
        // Does nothing
    }
}

现在,binderStub 类的一个实例(一个 Binder),其定义了服务的远程过程调用 (RPC) 接口。在下一步中,我们会向客户端公开此实例,以便客户端能与服务进行交互。

在实现 AIDL 接口时,应注意遵守以下规则:

  • 由于无法保证在主线程上执行传入调用,因此您一开始便需做好多线程处理的准备,并对您的服务进行适当构建,使其达到线程安全的标准。
  • 默认情况下,RPC 调用是同步调用。如果您知道服务完成请求的时间不止几毫秒,则不应从 Activity 的主线程调用该服务,因为这可能会使应用挂起(Android 可能会显示“Application is Not Responding”对话框)— 通常,您应从客户端内的单独线程调用服务。
  • 您引发的任何异常都不会回传给调用方。

3.3、向客户端公开接口

在为服务实现接口后,您需要向客户端公开该接口,以便客户端进行绑定。如要为您的服务公开该接口,请扩展 Service 并实现 onBind(),从而返回实现生成的 Stub 的类实例(如前文所述)。以下是向客户端公开 IRemoteService 示例接口的服务示例。

class RemoteService : Service() {

    override fun onCreate() {
        super.onCreate()
    }

    override fun onBind(intent: Intent): IBinder {
        // Return the interface
        return binder
    }


    private val binder = object : IRemoteService.Stub() {
        override fun getPid(): Int {
            return Process.myPid()
        }

        override fun basicTypes(
                anInt: Int,
                aLong: Long,
                aBoolean: Boolean,
                aFloat: Float,
                aDouble: Double,
                aString: String
        ) {
            // Does nothing
        }
    }
}

现在,当客户端(如 Activity)调用 bindService() 以连接此服务时,客户端的 onServiceConnected() 回调会接收服务的 onBind() 方法所返回的 binder 实例。

客户端还必须拥有接口类的访问权限,因此如果客户端和服务在不同应用内,则客户端应用的 src/ 目录内必须包含 .aidl 文件(该文件会生成 android.os.Binder 接口,进而为客户端提供 AIDL 方法的访问权限)的副本。

当客户端在 onServiceConnected() 回调中收到 IBinder 时,它必须调用 YourServiceInterface.Stub.asInterface(service),以将返回的参数转换成 YourServiceInterface 类型。例如:

var iRemoteService: IRemoteService? = null

//用于控制远程服务器的连接
val mConnection = object : ServiceConnection {

    // Called when the connection with the service is established
    override fun onServiceConnected(className: ComponentName, service: IBinder) {
        // Following the example above for an AIDL interface,
        // this gets an instance of the IRemoteInterface, which we can use to call on the service
        iRemoteService = IRemoteService.Stub.asInterface(service)
    }

    // Called when the connection with the service disconnects unexpectedly
    override fun onServiceDisconnected(className: ComponentName) {
        Log.e(TAG, "Service has unexpectedly disconnected")
        iRemoteService = null
    }
}

四、总结

如要调用通过 AIDL 定义的远程接口,服务端必须执行以下步骤:

  1. 在项目的 src/ 目录中加入 .aidl 文件,
  2. 声明一个 IBinder 接口实例(基于 AIDL 生成)。
  3. 实现 ServiceConnection
  4. 调用 Context.bindService(),从而传入 ServiceConnection 的实现。
  5. onServiceConnected() 实现中,您将收到一个 IBinder 实例(名为 service)。调用 XXX.Stub.asInterface((IBinder)service),以将返回的参数转换为 XXX 类型。
  6. 调用您在接口上定义的方法。您应始终捕获 DeadObjectException 异常,系统会在连接中断时抛出此异常。您还应捕获 SecurityException 异常,当 IPC 方法调用中两个进程的 AIDL 定义发生冲突时,系统会抛出此异常。
  7. 如要断开连接,请使用您的接口实例调用 Context.unbindService()

总结一下:

  • 当我们编写.aidl文件时,会生成Stub内部子类,客户端通过这个子类与服务端进行通信;Stub子类中有asInterface方法,我们可以通过这个方法获取客户端服务的实例(当服务端与客户端在同一进程返回的是Stub本身,如果在不同的进程,返回的是Stub.Proxy代理);
  • 当服务端使用bindService进行服务绑定,会传入对应的ServiceConnection对象,ServiceConnection.onServiceConnected回调会接收服务端onBind方法返回的binder实例。可以调用RemoteServiceasInterface方法将IBinder对象转换成对应的RemoteService实例,当客户端与服务端在同一个进程,则直接返回Stub对象本身,否则返回系统封装后的Stub.proxy对象。
  • onTransact方法在Stub子类中被定义,客户端使用RemoteService的某个方法时,.aidl文件生成的java方法中会使用transact方法,该方法被调用用来分发请求(实际上对其中的方法进行编号,然后通过根据请求编号来调用对应的方法,编号一般按方法定义顺序,从0开始)。ProxyStub类的内部类,服务端的本地代理,也有对应的asBinder方法。

扩展

当有多个模块都需要AIDL来进行IPC,此时需要为每个模块创建特定的AIDL文件,那么相应的Service就会很多。必然会出现系统资源耗费严重,应用过度重量级的问题。解决方法是建立Binder连接池,即将每个业务模块的Binder请求统一转发到一个远程Service中去执行,从而避免重复创建Service。

工作原理:每个业务模块创建自己的AIDL接口并实现此接口,然后向服务端提供自己的唯一标识和其对
应的Binder对象。服务端只需要一个Service并提供一个queryBinder接口,它会根据业务模块的特征来
返回相应的Binder对象,不同的业务模块拿到所需的Binder对象后就可以进行远程方法的调用了。