Android AIDL 入门
Android 是一个基于 Linux 的操作系统,保留了多进程的特性。在 Android 中IPC
是一个老生常谈的话题,每个进程都是一个单独的JVM虚拟机,它们是相互独立的,拥有自己特定的内存地址和空间,无法直接进行联系,而AIDL
就像一座桥梁连接不同进程,桥制定了规则,规定人怎么来往,哪些人可以来往。
AIDL(Android Interface Definition Language),是一种IDL语言,定义Android中两个进程间通信的规则。其底层采用的是Binder机制,可以看做是Binder的官方封装框架。
语法
AIDL语法和Java大致相似,但是也有着些许区别:
-
AIDL文件大概分为两类
- 一类是定义parcelable对象,给其它AIDL文件使用AIDL支持的默认数据类型外的数据
- 一类是用来定义接口的
-
文件后缀名是
.aidl
-
支持的数据类型
- Java八种基本数据类型(byte/short/int/long/float/double/boolean/char)
- String 与 CharSequence 类型
- List类型:List中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是实现parcelable接口的
- Map类型:Map中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是实现parcelable接口的,Map是不支持泛型的。
- 实现parcelable接口的对象
-
目标文件即使与你正在编写的文件在同一个包下,也需要导包
-
AIDL中的定向 tag 表示了在跨进程通信中数据的流向
- in 表示数据只能由客户端流向服务端,表现为服务端将会接收到一个那个对象的完整数据,但是客户端的那个对象不会因为服务端对传参的修改而发生变动
- out 表示数据只能由服务端流向客户端,表现为服务端将会接收到那个对象的的空对象,但是在服务端对接收到的空对象有任何修改,之后客户端将会同步变动
- inout 则表示数据可在服务端与客户端之间双向流通,表现为服务端将会接收到客户端传来对象的完整信息,并且客户端将会同步服务端对该对象的任何变动
使用步骤
声明文件
创建AIDL文件,用来声明需要传输的数据,传输调用的接口,创建好后系统会自动生成一个默认示例方法,该方法展示了所支持的基本数据类型,一般直接删除然后声明自己需要的方法即可。
// Declare any non-default types here with import statements
interface IAidlInterface {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
void log(String msg);
}
编译AIDL
我们所创建的AIDL文件其实并不是他最终的样子,使用Make Project
会将其编译成对应的 Java 文件。
public interface IAidlInterface extends android.os.IInterface
{
/** Default implementation for IAidlInterface. */
public static class Default implements com.whr.aidldemo.IAidlInterface
{
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
@Override public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException
{
}
@Override
public android.os.IBinder asBinder() {
return null;
}
}
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.whr.aidldemo.IAidlInterface
{
private static final java.lang.String DESCRIPTOR = "com.whr.aidldemo.IAidlInterface";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.whr.aidldemo.IAidlInterface interface,
* generating a proxy if needed.
*/
public static com.whr.aidldemo.IAidlInterface asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.whr.aidldemo.IAidlInterface))) {
return ((com.whr.aidldemo.IAidlInterface)iin);
}
return new com.whr.aidldemo.IAidlInterface.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_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();
java.lang.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);
}
}
}
private static class Proxy implements com.whr.aidldemo.IAidlInterface
{
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;
}
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
@Override public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException
{
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();
}
}
public static com.whr.aidldemo.IAidlInterface sDefaultImpl;
}
static final int TRANSACTION_basicTypes = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
public static boolean setDefaultImpl(com.whr.aidldemo.IAidlInterface impl) {
if (Stub.Proxy.sDefaultImpl == null && impl != null) {
Stub.Proxy.sDefaultImpl = impl;
return true;
}
return false;
}
public static com.whr.aidldemo.IAidlInterface getDefaultImpl() {
return Stub.Proxy.sDefaultImpl;
}
}
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException;
}
该类比较复杂,仅简单讲解,包含以下成员:
- 静态匿名内部类 Default,是 AIDL 所定义接口的默认实现类。
- 抽象静态匿名内部类 Stub,继承自 Binder,实现 AIDL 定义接口。该类是 AIDL 最核心的一个类,AIDL 底层实现是基于 Binder,而具体的 Binder 实现代码就是在该类中被编译器自动补全的。
声明服务
AIDL 本质上只是一个接口,用于定义规则,而具体的实现就需要依赖于四大组件的Service
了。由于是多进程交互,我们需要在Manifest
中对该 Service 声明 process
属性,以此在别的进程中创建该 Service,该属性有两种声明方式,一种是以:
开头连接子进程名,这样声明出来的进程前缀会和应用包名保持一致。另一种则是完全重写,这样声明出来的进程名就是所声明的名字。
<service
android:name=".RemoteService"
android:enabled="true"
android:exported="true"
android:process=":remote">
<intent-filter>
<action android:name="com.whr.aidldemo.RemoteService" />
</intent-filter>
</service>
另外这里还加了一个intent-filter:这是为了隐式启动远程服务所用。
讲到intent-filter就得讲还有一个属性android:exported:代表是否能被其他应用隐式调用,其默认值是由service中有无intent-filter决定的,如果有intent-filter,默认值为true,否则为false。手动设置false的情况下,即使有intent-filter匹配,也无法打开,即无法被其他应用隐式调用。
创建服务
Service
是抽象类,当实现类继承它的时候需要实现其onBind()
方法,该方法返回IBinder
对象,用于和绑定的Activity
进行交互,通常在非IPC
场景下随便声明一个Binder
的实现类就可以了,而在这里,则需要用到之前声明的AIDL
接口。
由于需要的是 IBinder
对象,这样显然是不能将 AIDL 定义的接口直接进行实现后返回的,那要怎么办呢,这就涉及到 AIDL 文件自动编译后生成的抽象静态内部类 Stub
了,对于该类的作用这里不过多介绍。总之该类继承自Binder
并且要求实现 AIDL 定义好的接口,正好符合这里的需要。
public class RemoteService extends Service {
private Binder stub = new com.whr.aidldemo.IMyAidlInterface.Stub() {
@Override
public void log(String msg) throws RemoteException {
Log.i("common", "pid:" + Process.myPid() + "msg:" + msg);
}
};
@Override
public IBinder onBind(Intent intent) {
return stub;
}
}
绑定服务
客户端绑定远程Service,并获得远程服务的代理对象,即获取远程Binder对象的接口的本地实现。
客户端便可以通过接口调用远程Service提供的方法。
因为不处于一个进程,推荐隐式启动,当前其实在同一个应用内不用隐式也可以正常启动。
注意在android5.0以前,我们只要设置Action就行了,但是之后需要把包名也一并设置,从源码可以看出如果启动service的intent的component和package都为空并且版本大于LOLLIPOP(5.0)的时候,直接抛出异常。
通过实现回调 ServiceConnection
可以获得远程服务的IBinder
对象,从而调用我们定义的方法,但是这里返回的引用是个顶层接口,无法知道我们定义的具体的能力,通常我们会想到用强转,但是当我们点进去看生成的 IMyAidlInterface
会发现其根本不是IBinder的子类,因此肯定是不行的。
这里就需要用的Stub
类了。
private void initAIDL() {
Intent intent = new Intent();
intent.setAction("com.whr.aidldemo.RemoteService");
intent.setPackage(getPackageName());
bindService(intent, new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
IMyAidlInterface myAidlInterface = IMyAidlInterface.Stub.asInterface(iBinder);
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
}, Context.BIND_AUTO_CREATE);
}
然后就可以调用我们定义好的方法进行跨进程交互了。
findViewById(R.id.log).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
try {
myAidlInterface.log("click");
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
打印出来的效果如下:
com.whr.aidldemo:remote I/common: pid:4021 msg:click
写在最后
总结一下,使用 AIDL 可以让我们非常方便的实现本来复杂的进程间通讯,主要包含以下步骤:
- 声明 AIDL 接口。
- 创建多进程服务并实现 AIDL 接口。
- 绑定服务,通过 AIDL 生成的 Binder 实例进行多进程交互。
而这其中的难点其实也就是集中在系统编译 AIDL 之后,为我们自动实现的 Binder 那部分逻辑里面了,但这部分逻辑其实不用太过深究也能满足大部分的操作,想自己基于 Binder 实现跨进程交互的童鞋可以研究下这部分源码,本篇文章由于侧重点不同便不作解析。
遇到的坑
原本在一开始时笔者是准备用 kotlin 进行所以代码的编写的,但是写完在编译时发生了一个问题:kotlin 代码中的引用 AIDL 时文件资源找不到。
经过反复 clean 、build、清理缓存后依旧存在这个问题,去搜索了一波好像也没有别人遇到过这个问题,姑且就没有过多研究。如果有碰到过的童鞋希望能告知一下。