抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

AIDL 是什么

Android Interface Definition Language (AIDL) 允许定义客户端和服务都同意的编程接口,以便使用进程间通信 (IPC) 相互通信。
在 Android 上,一个进程不能正常访问另一个进程的内存。 所以说,他们需要将他们的对象分解为操作系统可以理解的原语,并将对象编组跨越该边界。

注意:仅当允许来自不同应用程序的客户端访问你的 IPC 服务并希望在你的服务中处理多线程时,才需要使用 AIDL。
如果不需要跨不同应用程序执行并发 IPC,则应通过实现 Binder 创建接口,或者如果想执行 IPC 但不需要处理多线程,则使用 Messenger 实现接口。

在开始设计 AIDL 接口之前,请注意对 AIDL 接口的调用是直接函数调用。 不应该对发生调用的线程做出假设。 根据调用是来自本地进程中的线程还是远程进程,发生的情况会有所不同。 具体来说:

  • 从本地进程进行的调用在进行调用的同一线程中执行。 如果这是主 UI 线程,则该线程将继续在 AIDL 接口中执行。
    如果它是另一个线程,那就是在服务中执行代码的线程。 因此,如果只有本地线程正在访问服务,可以完全控制在其中执行哪些线程(但如果是这种情况,那么根本不应该使用 AIDL,而应该通过实现 Binder 创建接口 )。

  • 来自远程进程的调用是从平台在你自己的进程内部维护的线程池中分派的。 你必须为来自未知线程的传入呼叫做好准备,同时发生多个呼叫。
    换句话说,AIDL 接口的实现必须是完全线程安全的。 从同一远程对象上的一个线程发出的调用按顺序到达接收端。

  • oneway 关键字修改远程调用的行为。 使用时,远程调用不会阻塞; 它只是发送交易数据并立即返回。 接口的实现最终从 Binder 线程池接收这个作为常规调用作为常规远程调用。
    如果 oneway 与本地调用一起使用,则没有影响,调用仍然是同步的。

定义 AIDL 接口

必须使用 Java 语法在 .aidl 文件中定义 AIDL 接口,然后将其保存在托管服务的应用程序和绑定到服务的任何其他应用程序的源代码中(在 src/ 目录中)。

在构建每个包含 .aidl 文件的应用程序时,Android SDK 工具会根据 .aidl 文件生成一个 IBinder 接口,并将其保存在项目的 gen/ 目录中。 服务必须适当地实现 IBinder 接口。 然后客户端应用程序可以绑定到服务并从 IBinder 调用方法来执行 IPC。

要使用 AIDL 创建 bounded 服务,请执行以下步骤:

  1. 创建 .aidl 文件。 该文件定义了带有方法签名的编程接口。
  2. 实现接口。Android SDK 工具会根据你的 .aidl 文件以 Java 编程语言生成接口。 此接口有一个名为 Stub 的内部抽象类,它扩展 Binder 并实现 AIDL 接口中的方法。 你必须扩展 Stub 类并实现方法。
  3. 向客户端公开接口。实现一个 Service 并覆盖 onBind() 以返回 Stub 类的实现。

创建 .aidl 文件

AIDL 使用一种简单的语法,允许使用一个或多个可以接受参数和返回值的方法来声明接口。 参数和返回值可以是任何类型,甚至是其他 AIDL 生成的接口。

必须使用 Java 语言构建 .aidl 文件。 每个 .aidl 文件必须定义一个接口,并且只需要接口声明和方法签名。

默认情况下,AIDL 支持以下数据类型:

  • Java 中的所有基本类型(如 int、long、char、boolean 等)
  • 原始类型数组,例如 int[]
  • String
  • CharSequence
  • List
    List 中的所有元素必须是此列表中受支持的数据类型之一,或者是你声明的其他 AIDL 生成的接口或 parcelables 之一。 List 可以选择用作参数化类型类(例如,List)。 对方接收到的实际具体类始终是一个 ArrayList,尽管生成该方法是为了使用 List 接口。
  • Map
    Map 中的所有元素必须是此列表中支持的数据类型之一,或者是你声明的其他 AIDL 生成的接口或 parcelables 之一。 不支持参数化类型映射(例如 Map<String,Integer> 形式的映射)。 对方接收到的实际具体类始终是HashMap,虽然生成方法是为了使用Map接口。 考虑使用 Bundle 作为 Map 的替代品。

必须为上面未列出的每个附加类型包含一个 import 语句,即使它们与你的接口在同一个包中定义。

定义服务接口时,请注意:

  • 方法可以接受零个或多个参数,并返回一个值或 void。
  • 所有非原始参数都需要一个方向标签来指示数据的走向。 in、out或inout。
    警告:应该将方向限制为真正需要的方向,因为编组参数很昂贵。
  • .aidl 文件中包含的所有代码注释都包含在生成的 IBinder 接口中(除了 import 和 package 语句之前的注释)。
  • 可以在 AIDL 接口中定义 String 和 int 常量。 例如:const int VERSION = 1;。
  • 方法调用由 transact() 代码分派,该代码通常基于接口中的方法索引。 因为这使得版本控制变得困难,你可以手动将事务代码分配给一个方法:void method() = 10;。
  • 可以为 Null 的参数和返回类型必须使用 @nullable 进行注释。

只需将 .aidl 文件保存在项目的 src/ 目录中,当构建应用程序时,SDK 工具会在项目的 gen/ 目录中生成 IBinder 接口文件。 生成的文件名与 .aidl 文件名匹配,但具有 .java 扩展名(例如,IRemoteService.aidl 生成 IRemoteService.java)。

实现接口

当你构建应用程序时,Android SDK 工具会生成一个以你的 .aidl 文件命名的 .java 接口文件。 生成的接口包括一个名为 Stub 的子类,它是其父接口的抽象实现,并声明了 .aidl 文件中的所有方法。

注意:Stub 还定义了一些辅助方法,最值得注意的是 asInterface(),它接受一个 IBinder(通常是传递给客户端的 onServiceConnected() 回调方法的那个)并返回一个 stub 接口的实例。

在实现 AIDL 接口时,你应该注意一些规则:

  • 传入调用不能保证在主线程上执行,因此你需要从一开始就考虑多线程并正确构建你的服务以实现线程安全。
  • 默认情况下,RPC 调用是同步的。 如果你知道该服务需要多于几毫秒来完成一个请求,你不应该从 Activity 的主线程调用它,因为它可能会挂起应用程序(Android 可能会显示“应用程序未响应”对话框)——你应该 通常从客户端中的单独线程调用它们。
  • 只有 Parcel.writeException() 的参考文档中列出的异常类型会被发送回调用者。

客户端还必须能够访问接口类,因此如果客户端和服务位于不同的应用程序中,那么客户端的应用程序必须在其 src/ 目录中拥有 .aidl 文件的副本(生成 android.os.Binder 接口) — 提供客户端访问 AIDL 方法)。

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

通过 IPC 传递对象

在 Android 10(API 级别 29)中,可以直接在 AIDL 中定义 Parcelable 对象。 这里也支持作为 AIDL 接口参数和其他 parcelables 支持的类型。 这避免了手动编写编组代码和自定义类的额外工作。 但是,这也会创建一个裸结构。 如果需要自定义访问器或其他功能,则应实现 Parcelable。

你还可以通过 IPC 接口将自定义类从一个进程发送到另一个进程。 但是,你必须确保你的类的代码可用于 IPC 通道的另一端,并且你的类必须支持 Parcelable 接口。 支持 Parcelable 接口很重要,因为它允许 Android 系统将对象分解为可以跨进程编组的基元。

Stub类的声明:

1
public static abstract class Stub extends Binder implements IRemoteService

Stub.asInterface()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static IRemoteService asInterface(IBinder obj) {
if (obj==null) {
return null;
}

IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if ((iin!=null) && (iin instanceof IRemoteService)) {
return ((IRemoteService)iin);
}
//
return new IRemoteService.Stub.Proxy(obj);
}

public @Nullable IInterface queryLocalInterface(@NonNull String descriptor) {
if (mDescriptor != null && mDescriptor.equals(descriptor)) {
return mOwner;
}
return null;
}

Stub.Proxy 中方法的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(anInt);
_data.writeLong(aLong);
_data.writeInt(((aBoolean)?(1):(0)));
_data.writeFloat(aFloat);
_data.writeDouble(aDouble);
_data.writeString(aString);
//
boolean _status = mRemote.transact(Stub.TRANSACTION_basicTypes, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
getDefaultImpl().basicTypes(anInt, aLong, aBoolean, aFloat, aDouble, aString);
return;
}
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}

Binder.transact() 的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
public final boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply,
int flags) throws RemoteException {
if (false) Log.v("Binder", "Transact: " + code + " to " + this);

if (data != null) {
data.setDataPosition(0);
}
boolean r = onTransact(code, data, reply, flags);
if (reply != null) {
reply.setDataPosition(0);
}
return r;
}

Stub.onTransact() 的实现:

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
String descriptor = DESCRIPTOR;
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(descriptor);
return true;
}
case TRANSACTION_basicTypes:
{
data.enforceInterface(descriptor);
int _arg0;
_arg0 = data.readInt();
long _arg1;
_arg1 = data.readLong();
boolean _arg2;
_arg2 = (0!=data.readInt());
float _arg3;
_arg3 = data.readFloat();
double _arg4;
_arg4 = data.readDouble();
String _arg5;
_arg5 = data.readString();
//
this.basicTypes(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5);
reply.writeNoException();
return true;
}
default:
{
return super.onTransact(code, data, reply, flags);
}
}

IBinder

IBinder 是远程对象的基本接口,轻量级远程过程调用机制的核心部分,专为执行进程内和跨进程调用时的高性能而设计。 该接口描述了与远程对象交互的抽象协议。 不要直接实现该接口,而是从 Binder 扩展。

关键的 IBinder API 是与 Binder.onTransact() 相匹配的 transact()。 这些方法允许您分别发送对 IBinder 对象的调用和接收对 Binder 对象的调用。
该事务 API 是同步的,因此对 transact() 的调用不会返回,直到目标从 Binder.onTransact() 返回; 这是调用本地进程中存在的对象时的预期行为,并且底层进程间通信 (IPC) 机制确保在跨进程时应用这些相同的语义。

通过 transact() 发送的数据是一个 Parcel,它是一个通用的数据缓冲区,还维护一些有关其内容的元数据。 元数据用于管理缓冲区中的 IBinder 对象引用,以便在缓冲区跨进程移动时可以维护这些引用。
此机制确保当 IBinder 写入 Parcel 并发送到另一个进程时,如果另一个进程将对同一 IBinder 的引用发送回原始进程,则原始进程将收到相同的 IBinder 对象。 这些语义允许将 IBinder/Binder 对象用作可以跨进程管理的唯一标识(用作令牌或用于其他目的)。

系统在其运行的每个进程中维护一个事务线程池。这些线程用于调度来自其他进程的所有 IPC。 例如,当从进程 A 到进程 B 进行 IPC 时,A 中的调用线程在将事务发送到进程 B 时会在 transact() 中阻塞。B 中的下一个可用池线程接收传入事务,调用 Binder.onTransact() 对目标对象进行处理,并回复结果 Parcel。 收到结果后,进程 A 中的线程返回以允许其继续执行。 实际上,其他进程似乎用作您未创建并在自己的进程中执行的附加线程。

Binder系统还支持跨进程的递归。 例如,如果进程 A 执行一个事务来处理 B,并且进程 B 在处理该事务时调用 A 中实现的 IBinder 上的 transact(),则 A 中当前等待原始事务完成的线程将负责处理 在B调用的对象上调用 Binder.onTransact()。这确保了调用远程 binder 对象时的递归语义与调用本地对象时的递归语义相同。

当使用远程对象时,您经常想知道它们何时不再有效。 可以通过三种方法来确定:

  • 如果尝试在进程不再存在的 IBinder 上调用 transact() 方法,则会抛出 RemoteException 异常。
  • 可以调用 pingBinder() 方法,如果远程进程不再存在,则返回 false。
  • linkToDeath() 方法可用于向 IBinder 注册 IBinder.DeathRecipient,当其包含进程消失时将调用该 IBinder.DeathRecipient。

transact()

返回 Binder.onTransact 的结果。 调用成功一般返回 true; false 通常表示 transaction 代码不被理解。 对于对不同进程的单向调用,永远不应返回 false。 如果对同一进程中的代码(通常是 C++ 或 Rust 实现)进行单向调用,则不存在单向语义,并且仍然可以返回 false。

Binder

Binder 是远程对象的基类,IBinder 定义的轻量级远程过程调用机制的核心部分。 此类是 IBinder 的实现,它提供此类对象的标准本地实现。

大多数开发者不会直接实现这个类,而是使用 aidl 工具来描述所需的接口,让它生成适当的 Binder 子类。 但是,您可以直接从 Binder 派生来实现您自己的自定义 RPC 协议,或者直接实例化原始 Binder 对象以用作可以跨进程共享的令牌。

这个类只是一个基本的 IPC 原语; 它对应用程序的生命周期没有影响,并且仅在创建它的进程继续运行时才有效。 要正确使用此功能,您必须在顶级应用程序组件(android.app.Service、android.app.Activity 或 android.content.ContentProvider)的上下文中执行此操作,让系统知道您的进程应该保持运行。

您必须记住进程可能消失的情况,因此要求您稍后重新创建一个新的 Binder,并在进程再次启动时重新附加它。 例如,如果您在 android.app.Activity 中使用此功能,则您的 Activity 进程可能会在 Activity 未启动时随时被终止; 如果稍后重新创建该 Activity,您将需要创建一个新的 Binder 并将其再次交回正确的位置; 您需要注意,您的进程可能出于其他原因(例如接收广播)而启动,该原因不会涉及重新创建活动,从而运行其代码来创建新的 Binder。

onTransact()

默认实现是一个返回 false 的 stub。 您将需要覆盖它以对事务进行适当的解组。
如果你想调用它,请调用 transact()
返回结果的实现通常应该使用 Parcel.writeNoExceptionParcel.writeException 将异常传播回调用者。

评论