理解Android AIDL
在研究了 Android Frameworks 中进程间通信(IPC)相关的一些程序后,了解到 Android 系统中进程间通信的机制绝大部分就是 Binder,主要表现在系统服务的调用,app进程间功能调用等。而 Android 上实现 Binder 的具体形式就是 AIDL (Android Interface Definition Language) 。
所以在去充分理解 Binder 之前,要先理解下 AIDL 。
AIDL概述
Android 接口定义语言 (AIDL) 是一种让用户抽象出 IPC 的工具。给定一个接口(在.aidl
文件中指定),各种构建系统使用 aidl
二进制文件来构建 C++ 或 Java 绑定,以便该接口可以跨进程使用,而不管那里的运行时或位数如何。
AIDL 可以在 Android 中的任何进程之间使用:平台组件之间或应用程序之间。
下面是一个 AIDL 接口定义示例:
package my.package; // 与普通java文件定义一样有包名
import my.package.Foo; // 可以是在其他地方定义的类型
interface IFoo {
void doFoo(Foo foo);
}
服务器 进程注册一个接口并为其提供调用服务,而 客户端 进程则调用这些接口。在一些情况下,一个 app 进程既充当客户端又充当服务器,因为它可能引用多个接口。
运行
AIDL 使用 binder 内核驱动调用。当进行一次调用时,方法标识符和所有的数据被打包进缓存,并将其拷贝到正在等待读取数据的 binder 线程的远程进程中。当 binder 线程因为业务需要接收数据时,binder 线程会在本地进程中找到本地存根对象,并且这个对象会解包数据并且调用本地接口对象(AIDL定义的接口)。这个本地接口对象是服务进程创建并注册。当调用是在同一个进程和同一个后端发生时,不会产生代理对象,这样的直接调用也不会有任何打包或解包操作。
AIDL 接口的调用是直接函数调用。调用线程实际情况的差异取决于调用是来自本地进程中的线程,还是远程进程中的线程。
从线程角度看 AIDL 接口调用:
-
来自本地进程的调用在发起调用的同一线程内执行。如果调用来自main线程,那么它将继续在 AIDL 接口中执行。如果线程是其他线程,那么其便是在服务中执行代码的线程。因此,若只是发生在service内的线程之间的互相访问,那么可以自行控制调用发生在哪个线程。在这种情况下,可以不定义aidl,直接定义一个接口实现
Binder
。 -
从远程进程中平台持有的线程池中的某个线程发起的调用。为来自未知线程以及同时发生多次调用的情况做好准备。换言之,AIDL 接口调用必须是线程安全的。来自远程进程同一线程内相同对象的多个调用会在同一接收端按顺序调用。
-
oneway
关键字修饰的远程调用。调用端方法不会阻塞。方法会在发送完数据后立即返回。接口实现最终按常规方式从Binder
线程池接收这个正常的远程调用。
AIDL 定义使用
跨进程的 aidl 调用。在服务端和客户端分别定义,调用相关的代码。
服务端定义 aidl 文件及接口。
-
使用 java 语法创建
.aidl
文件。在包含有.aidl
文件的每个 application 中进行构建,Android 的 aidl 工具会基于.aidl
文件内容在build/generated/aidl_source_output/dir
目录下生成对应的.java
文件。从 Android 12 开始,要创建编译 aidl 文件,需要的 build.gradle 文件 buildFeatures 块中添加
aidl=true
的设置。 Android 11 之前可以直接编译 aidl 文件。Android 12 开始需要在
build.gradle
中添加aidl
构建配置项。若 sdk 版本低,不要设置。// app/build.gradle.kts android { // ....... buildFeatures { aidl=true } }
配置完成并 sync 之后,会在
app
模块的main
目录中生成 aidl 目录。src/ ├── main │ ├── aidl │ │ └── com │ │ └── sanren1024 │ │ └── aidlserver
-
在目录中创建
.aidl
文件,并使用 java 语法写接口定义。// IServerHandle.aidl package com.sanren1024.aidlserver; interface IServerHandle { int getPid(); }
在 IDE 中进行一次编译,可以
build/
目录中查看到生成的.java
文件。 -
实现
Service
,重写onBind()
方法,将服务端Binder
对象公开给调用端。在服务端定义一个
Service
类,并定义实现IServerHandle.Stub
的本地实现类,实现接口中定义的方法。public class ServerService extends Service { @Override public IBinder onBind(Intent intent) { Log.i("AIDLServer", "service launch"); return LocalServiceBinder.getService(); } public static class LocalServiceBinder extends IServerHandle.Stub { public static LocalServiceBinder getService() { return new LocalServiceBinder(); } @Override public int getPid() throws RemoteException { return Process.myPid(); } } }
客户端
在客户端要调用服务端的接口,也需要同样包名的的 .aidl
接口定义。
-
在客户端的
src/
目录下使用与服务端同样的.aidl
文件。 -
在需要调用远程方法的
Activity
内使用bindService()
方式进行定义。// XxxActivity.java private IServerHandle mServerHandle; private final ServiceConnection mSC = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { mServerHandle = IServerHandle.Stub.asInterface(service); } @Override public void onServiceDisconnected(ComponentName name) { mServerHandle = null; } };
-
配置调用。从Android 11 开始,客户端 app 调用服务端 app 内定义的
Service
,需要在客户端 Manifest 中配置queries
标签。<!-- 声明app需要访问的其他app的组件信息 --> <queries> <package android:name="com.sanren1024.aidlserver" /> <intent> <action android:name="com.sanren1024.server.AIDL_CALL" /> <category android:name="android.intent.category.DEFAULT" /> </intent> </queries>
AIDL 编译后生成 .java 文件
aidl 接口定义完成后,由 aidl 工具将 .aidl
文件转成 .java
文件。下图是只包含一个接口定义的 .aidl
文件编译后生成的 java 文件。包含外层接口定义,内部生成两个静态类,及方法定义。
具体展开生成内容如下。
package com.sanren1024.aidlserver;
// 生成了接口类,IServerHandle 继承了 android.os.IInterface。内部有两个静态类都实现 IServerHandle 接口。
public interface IServerHandle extends android.os.IInterface
{
// 服务端 IServerHandle 的默认实现。
public static class Default implements com.sanren1024.aidlserver.IServerHandle
{
@Override public int getPid() throws android.os.RemoteException
{
return 0;
}
@Override
public android.os.IBinder asBinder() {
return null;
}
}
// 客户端使用 IServerHandle 的实现。
public static abstract class Stub extends android.os.Binder implements com.sanren1024.aidlserver.IServerHandle
{
private static final java.lang.String DESCRIPTOR = "com.sanren1024.aidlserver.IServerHandle";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
public static com.sanren1024.aidlserver.IServerHandle asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.sanren1024.aidlserver.IServerHandle))) {
return ((com.sanren1024.aidlserver.IServerHandle)iin);
}
return new com.sanren1024.aidlserver.IServerHandle.Stub.Proxy(obj);
}
@Override public android.os.IBinder asBinder()
{
return this;
}
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
java.lang.String descriptor = DESCRIPTOR;
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(descriptor);
return true;
}
case TRANSACTION_getPid:
{
data.enforceInterface(descriptor);
int _result = this.getPid();
reply.writeNoException();
reply.writeInt(_result);
return true;
}
default:
{
return super.onTransact(code, data, reply, flags);
}
}
}
private static class Proxy implements com.sanren1024.aidlserver.IServerHandle
{
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote)
{
mRemote = remote;
}
@Override public android.os.IBinder asBinder()
{
return mRemote;
}
public java.lang.String getInterfaceDescriptor()
{
return DESCRIPTOR;
}
@Override public int getPid() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
int _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
boolean _status = mRemote.transact(Stub.TRANSACTION_getPid, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
return getDefaultImpl().getPid();
}
_reply.readException();
_result = _reply.readInt();
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
public static com.sanren1024.aidlserver.IServerHandle sDefaultImpl;
}
static final int TRANSACTION_getPid = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
public static boolean setDefaultImpl(com.sanren1024.aidlserver.IServerHandle impl) {
if (Stub.Proxy.sDefaultImpl != null) {
throw new IllegalStateException("setDefaultImpl() called twice");
}
if (impl != null) {
Stub.Proxy.sDefaultImpl = impl;
return true;
}
return false;
}
public static com.sanren1024.aidlserver.IServerHandle getDefaultImpl() {
return Stub.Proxy.sDefaultImpl;
}
}
public int getPid() throws android.os.RemoteException;
}
其中类结构图如下。
查看方法
在生成的代码中会看到 attachInterface(IInterface, String)
queryLocalInterface(String)
两个方法,都定义在 Binder
类中。
// Binder.java
private IInterface mOwner;
@Nullable
private String mDescriptor;
// 设置 Binder 成员变量。
public void attachInterface(@Nullable IInterface owner, @Nullable String descriptor) {
mOwner = owner;
mDescriptor = descriptor;
}
// 返回 attachInterface 设置进来的 IInterface 类型变量。
public @Nullable IInterface queryLocalInterface(@NonNull String descriptor) {
if (mDescriptor != null && mDescriptor.equals(descriptor)) {
return mOwner;
}
return null;
}
attachInterface
attachInterface
函数的实现很简单,只是设置了 Binder
的成员变量 mOwner
mDescription
,用以在查询时使用。
queryLocalInterface
返回 mOwner
对象,这个从 attachInterface
设置入。
分析
在 app 进程间调用,需要 Binder
驱动调用。
在服务端,Binder
的本地代理实现定义在一个Service
类内,并通过 onBinder
方法将 Binder
对象公开给调用端。在客户端需要调用服务端方法的时候,先通过 bindService()
方法连接远程 Service
,获取远程接口,再调用相应方法(调用 YourInterfaceName.Stub.asInterface((IBinder) service)
,以将返回的参数转换为 YourInterface 类型。)。Service
创建时,会分别调用到 Service.onCreate()
Service.onBind(Intent)
方法。再从客户端方面,通过 onServiceConnected(ComponentName, IBinder)
将收到一个 IBinder
实例(名为 service
)。